I am currently using Picasso to download and cache images in my app inside multiple recycler views. So far Picasso has used around 49MB cache size and i am worried that as more images come into play, this will become much higher.
I am using the default Picasso.with(context) object. Please answer the following:
1) Is there a way to restrict the Size of Picasso's cache. MemoryPolicy and NetworkPolicy set to NO_CACHE isn't an option. I need caching but upto a certain level (60MB max)
2) Is there a way in picasso to store Resized/cropped images like in Glide DiskCacheStrategy.RESULT
3) If the option is to use OKHTTP, please guide me to a good tutorial for using it to limit Picasso's cache size. (Picasso 2.5.2)
4) Since i am using a Gradle dependency of Picasso, how can i add a clear Cache function as shown here:
Clear Cache memory of Picasso
Please try this one, it does seem to work great for me:
I use it as a Singleton.
Just put 60 where DISK/CACHE size parameters are.
//Singleton Class for Picasso Downloading, Caching and Displaying Images Library
public class PicassoSingleton {
private static Picasso mInstance;
private static long mDiskCacheSize = CommonConsts.DISK_CACHE_SIZE * 1024 * 1024; //Disk Cache
private static int mMemoryCacheSize = CommonConsts.MEMORY_CACHE_SIZE * 1024 * 1024; //Memory Cache
private static OkHttpClient mOkHttpClient; //OK Http Client for downloading
private static Cache diskCache;
private static LruCache lruCache;
public static Picasso getSharedInstance(Context context) {
if (mInstance == null && context != null) {
//Create disk cache folder if does not exist
File cache = new File(context.getApplicationContext().getCacheDir(), "picasso_cache");
if (!cache.exists())
cache.mkdirs();
diskCache = new Cache(cache, mDiskCacheSize);
lruCache = new LruCache(mMemoryCacheSize);
//Create OK Http Client with retry enabled, timeout and disk cache
mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(CommonConsts.SECONDS_TO_OK_HTTP_TIME_OUT, TimeUnit.SECONDS);
mOkHttpClient.setRetryOnConnectionFailure(true);
mOkHttpClient.setCache(diskCache);
//For better performence in Memory use set memoryCache(Cache.NONE) in this builder (If needed)
mInstance = new Picasso.Builder(context).memoryCache(lruCache).
downloader(new OkHttpDownloader(mOkHttpClient)).
indicatorsEnabled(CommonConsts.SHOW_PICASSO_INDICATORS).build();
}
}
return mInstance;
}
public static void updatePicassoInstance() {
mInstance = null;
}
public static void clearCache() {
if(lruCache != null) {
lruCache.clear();
}
try {
if(diskCache != null) {
diskCache.evictAll();
}
} catch (IOException e) {
e.printStackTrace();
}
lruCache = null;
diskCache = null;
}
}
1) Yeah, easy: new com.squareup.picasso.LruCache(60 * 1024 * 1024). (just use your Cache instance in your Picasso instance like new Picasso.Builder(application).memoryCache(cache).build())
2) Picasso automatically uses the resize() and other methods' parameters as part of the keys for the memory cache. As for the disk cache, nope, Picasso does not touch your disk cache. The disk cache is the responsibility of the HTTP client (like OkHttp).
3) If you are talking about disk cache size: new OkHttpClient.Builder().cache(new Cache(directory, maxSize)).build(). (now you have something like new Picasso.Builder(application).memoryCache(cache).downloader(new OkHttp3Downloader(client)).build())
4) Picasso's Cache interface has a clear() method (and LruCache implements it, of course).
Ok, I did a lot of digging inside Picasso, and OKHTTP's internal working to find out how caching happens, whats the policy etc.
For people trying to use latest picasso 2.5+ and Okhttp 3+, the accepted answer WILL NOT WORK!! (My bad for not checking with the latest :( )
1) The getSharedInstance was not Thread safe, made it synchronized.
2) If you don't to do this calling everytime, do a Picasso.setSingletonInstance(thecustompicassocreatedbygetsharedinstance)
P.S. do this inside a try block so as to avoid illegalstateexception on activity reopening very quickly after a destroy that the static singleton is not destroyed. Also make sure this method gets called before any Picasso.with(context) calls
3) Looking at the code, I would advise people not to meddle with LruCache unless absolutely sure, It can very easily lead to either waste of unused RAM or if set low-> Outofmemoryexceptions.
4)It is fine if you don't even do any of this. Picasso by default tries to make a disk cache from it's inbuilt okhttpdownloader. But this might or might not work based on what picasso version you use. If it doesn't work, it uses default java URL downloader which also does some caching of its own.
5) Only main reason i see to do all this is to get the Clear Cache functionality. As we all know Picasso does not give this easily as it is protected inside the package. And most mere mortals like me use gradle to include the package leaving us out in the dust to not have cache clearing access.
Here is the code along with all the options for what i wanted. This will use Picasso 2.5.2 , Okhttp 3.4.0 and OkHttp3Downloader by jakewharton.
package com.example.project.recommendedapp;
import android.content.Context;
import android.util.Log;
import com.jakewharton.picasso.OkHttp3Downloader;
import com.squareup.picasso.LruCache;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
//Singleton Class for Picasso Downloading, Caching and Displaying Images Library
public class PicassoSingleton {
private static Picasso mInstance;
private static long mDiskCacheSize = 50*1024*1024; //Disk Cache limit 50mb
//private static int mMemoryCacheSize = 50*1024*1024; //Memory Cache 50mb, not currently using this. Using default implementation
private static OkHttpClient mOkHttp3Client; //OK Http Client for downloading
private static OkHttp3Downloader okHttp3Downloader;
private static Cache diskCache;
private static LruCache lruCache;//not using it currently
public static synchronized Picasso getSharedInstance(Context context)
{
if(mInstance == null) {
if (context != null) {
//Create disk cache folder if does not exist
File cache = new File(context.getApplicationContext().getCacheDir(), "picasso_cache");
if (!cache.exists()) {
cache.mkdirs();
}
diskCache = new Cache(cache, mDiskCacheSize);
//lruCache = new LruCache(mMemoryCacheSize);//not going to be using it, using default memory cache currently
lruCache = new LruCache(context); // This is the default lrucache for picasso-> calculates and sets memory cache by itself
//Create OK Http Client with retry enabled, timeout and disk cache
mOkHttp3Client = new OkHttpClient.Builder().cache(diskCache).connectTimeout(6000, TimeUnit.SECONDS).build(); //100 min cache timeout
//For better performence in Memory use set memoryCache(Cache.NONE) in this builder (If needed)
mInstance = new Picasso.Builder(context).memoryCache(lruCache).downloader(new OkHttp3Downloader(mOkHttp3Client)).indicatorsEnabled(true).build();
}
}
return mInstance;
}
public static void deletePicassoInstance()
{
mInstance = null;
}
public static void clearLRUCache()
{
if(lruCache!=null) {
lruCache.clear();
Log.d("FragmentCreate","clearing LRU cache");
}
lruCache = null;
}
public static void clearDiskCache(){
try {
if(diskCache!=null) {
diskCache.evictAll();
}
} catch (IOException e) {
e.printStackTrace();
}
diskCache = null;
}
}
Related
I am very confused with exoplayer and their documentation, They have explained everything in very short.
Can anyone please tell me what exactly leastRecentlyUsedCacheEvictor is and how it work? use case and methods?
ExoPlayer video cache uses a CacheEvictor instance to tell the library when to delete cached files. LeastRecentlyUsedCacheEvictor as the name represents declares that policy in a least recently used order.
Assuming you have watched video A, B, C, A (again) and D (order matters) and you hit the maximum cache capacity passed in LeastRecentlyUsedCacheEvictor constructor. The evictor instance lists the cache usages and finds video B as the least recently used one and deletes it to free space.
Here is a simple usage example:
public class VideoCacheSingleton {
private static final int MAX_VIDEO_CACHE_SIZE_IN_BYTES = 200 * 1024 * 1024; // 200MB
private static Cache sInstance;
public static Cache getInstance(Context context) {
if (sInstance != null) return sInstance;
else return sInstance = new SimpleCache(new File(context.getCacheDir(), "video"), new LeastRecentlyUsedCacheEvictor(MAX_VIDEO_CACHE_SIZE_IN_BYTES), new ExoDatabaseProvider(context)));
}
}
I have the following requirements for image download:
ignoring SSL errors (yes I am aware of the risks)
using a session cookie
I tried to adapt Picasso 2.4.0 to do that, below is my approach:
public static Picasso getPicasso(Context context) {
/* an OkHttpClient that ignores SSL errors */
final OkHttpClient client = getUnsafeOkHttpClient();
return new Picasso.Builder(context)
.downloader(new OkHttpDownloader(client) {
#Override
public Response load(Uri uri, boolean localCacheOnly) throws IOException {
final String RESPONSE_SOURCE_ANDROID = "X-Android-Response-Source";
final String RESPONSE_SOURCE_OKHTTP = "OkHttp-Response-Source";
HttpURLConnection connection = openConnection(uri);
connection.setRequestProperty("Cookie", getCookieHandler().
getCookieStore().getCookies().get(0).toString());
connection.setUseCaches(true);
if (localCacheOnly)
connection.setRequestProperty("Cache-Control", "only-if-cached,max-age=" + Integer.MAX_VALUE);
int responseCode = connection.getResponseCode();
if (responseCode == 401)
relogin();
else if (responseCode >= 300) {
connection.disconnect();
throw new ResponseException(responseCode + " " + connection.getResponseMessage());
}
String responseSource = connection.getHeaderField(RESPONSE_SOURCE_OKHTTP);
if (responseSource == null)
responseSource = connection.getHeaderField(RESPONSE_SOURCE_ANDROID);
long contentLength = connection.getHeaderFieldInt("Content-Length", -1);
boolean fromCache = parseResponseSourceHeader(responseSource);
return new Response(connection.getInputStream(), fromCache, contentLength);
}
}).build();
}
The only thing that I changed from the original source is adding a Cookie for the HttpURLConnection. I also copied (unchanged) the parseResponseSourceHeader() method since it has private access.
Note that the approach given here does NOT work (response code 401).
The image loading basically works, but there are major issues:
caching doesn't work (fromCache is always false and Picasso always reloads an image which has already been downloaded)
there's no "Content-Length" header, so contentLength is always -1
though the cache doesn't work, the RAM usage increases when loading next image (into exactly the same or any other ImageView), it seems the Bitmap object stays somewhere in the memory
when used inside the BaseAdapter of a GridView, it seems that Picasso tries to load all (or at least as many as the number of times getView() was called) images at the same time. Those images appear, then the app freezes and closes with the following (OOM?) log:
A/Looper﹕ Could not create wake pipe. errno=24
or
A/Looper﹕ Could not create epoll instance. errno=24
The described issues occur no matter if I use a custom Target of just an ImageView.
It seems I have broken some of Picasso mechanisms by overriding the load() method of the OkHttpDownloader, but I'm not getting what's wrong since I did minimal changes. Any suggestions are appreciated.
In case someone has a similar problem: it was a really lame mistake of mine. I was creating multiple Picasso instances which is complete nonsense. After ensuring the singleton pattern with a helper class that returns a single Picasso instance everything works as intended.
I'm using BitmapLRUCache by Trey Robinson for image caching in my Android app. It's an implementation of LRU cache for Volley as it doesn't provide any image caching by itself.
Though it does use DiskBasedCache for caching HTTP requests. Now coming to the problem, I get FileNotFoundExceptions repeatedly when DiskBasedCache tries to get or remove cache entries.
Sample log below.
23833 Volley D [47291] DiskBasedCache.remove: Could not delete cache entry for key=http://a2.mzstatic.com/us/r30/Music1/v4/69/66/0b/69660b50-7771-a43a-919f-26d8b6ae37aa/UMG_cvrart_00602537957941_01_RGB72_1500x1500_14UMGIM31675.400x400-75.jpg, filename=1509125231-2004731303
23833 Volley D [47291] DiskBasedCache.get: /data/data/com.vibin.billy.debug/cache/volley/6408634861932551223: java.io.FileNotFoundException: /data/data/com.vibin.billy.debug/cache/volley/6408634861932551223: open failed: ENOENT (No such file or directory)
23833 Volley D [47291] DiskBasedCache.remove: Could not delete cache entry for key=http://a2.mzstatic.com/us/r30/Music4/v4/99/f7/ac/99f7ac13-0dd6-8841-96e0-2a1c18041d84/UMG_cvrart_00602537854097_01_RGB72_1800x1800_14UMGIM03851.400x400-75.jpg, filename=6408634861932551223
Why is DiskBasedCache handling image caching when I'm initializing the ImageLoader with BitmapLRUcache (see below)?
ImageLoader imageLoader = new ImageLoader(Volley.newRequestQueue(this), new BitmapLruCache());
Below is the code I'm using for caching.
package com.vibin.billy;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.Log;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.ImageLoader;
/**
* Basic LRU Memory cache.
*
* #author Trey Robinson
*/
public class BitmapLruCache
extends LruCache<String, Bitmap>
implements ImageLoader.ImageCache {
private static final String TAG = BitmapLruCache.class.getSimpleName();
public BitmapLruCache() {
this(getDefaultLruCacheSize());
}
public BitmapLruCache(int sizeInKiloBytes) {
super(sizeInKiloBytes);
}
#Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
#Override
public Bitmap getBitmap(String url) {
//Log.d(TAG, "Grab "+url);
return get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
//Log.d(TAG, "Put "+url);
put(url, bitmap);
}
public static int getDefaultLruCacheSize() {
final int maxMemory =
(int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
Log.d(TAG, "cachesize is " + cacheSize);
Log.d(TAG,cacheSize+" is cache Size");
return cacheSize;
}
}
Why is DiskBasedCache handling image caching when I'm initializing the
ImageLoader with BitmapLRUcache (see below)?
for image caching volley use 2 level cache mechanism, that means one level is in your RAM BitmapLRUcache and another one is on your Disk DiskBasedCache. why? because of reading and writing images from disk takes longer time than just simply reading and writing some Strings and gives poor performance. so at the first time when you request Volley to download your images Volley first looks at the cache level one, if your images are not there Volley looks at cache level 2 and if your images are not there Volley sends your download request to the server.
I get FileNotFoundExceptions repeatedly when DiskBasedCache tries to
get or remove cache entries.
because your DiskBasedCache size is limited(by default is 5MB) and also it is LRU, which means if Volley wants to store an image on the DiskBasedCache and it dose not have any space, it is going to delete some of the old entries that it holds and dose not references recently.(LRU=Least Recently Used) So the remove function is called.
From what I understand we need to provide the implementation of LRU cache and pass the same as constructor to the image loader.
There is also a default disk based cache present in volley. This disk based cache is used for caching HTTP responses.
**Which cache will be used when the image downloaded contains cache headers ??
LRU(own implementation)
Default disc cache implementation present inside volley toolbox package*strong text* ** to
public class BitmapLruCache extends LruCache implements ImageLoader.ImageCache
{
public static int getDefaultLruCacheSize() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
return cacheSize;
}
public BitmapLruCache() {
this(getDefaultLruCacheSize());
}
public BitmapLruCache(int sizeInKiloBytes) {
super(sizeInKiloBytes);
}
#Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
#Override
public Bitmap getBitmap(String url) {
return get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}}
This is the call which is used to set the Image in the network view
ImageLoader mImageLoader = new ImageLoader(ApplicationController.getInstance().getRequestQueue(), new BitmapLruCache());
holder.imageicon.setImageUrl(i.getThumb_image(), mImageLoader);
The LRU cache is used for caching the images. See the source code for ImageLoader - uses an ImageCache, which is an interface volley provides you to implement in your extended LruCache. So, your cache would both extend LruCache<String,Bitmap> and implement an ImageCache.
The default cache that volley provides is used to every response it gets, with the default strat of caching according to cache headers of the HTTP response. So, if they have the right cache headers, they'll be cached on disk, if they don't, they won't.
So, to answer your question in full, potentially both caches will be used -- but as far as retrieving the images go, the in memory cache (or your lru cache) will be used.
I am getting OutOfMemoryError on my application. When i went through some tutorials, i came to know that, I can solve this issue by using Softreference/Weakreference. But I don't know that how to use Softreference/Weakreference.
Please suggest me some tutorials that providing examples for the Softreference or Weakreference.
Thank you...
package com.myapp;
import java.io.File;
import java.lang.ref.SoftReference;
import java.util.WeakHashMap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
public class BitmapSoftRefrences {
public static String SDPATH = Environment.getExternalStorageDirectory()
+ "/MYAPP";
// 1. create a cache map
public static WeakHashMap<String, SoftReference<Bitmap>> mCache = new WeakHashMap<String, SoftReference<Bitmap>>();
public static String TAG = "BitmapSoftRefrences";
// 2. ask for bitmap
public static Bitmap get(String key) {
if (key == null) {
return null;
}
try {
if (mCache.containsKey(key)) {
SoftReference<Bitmap> reference = mCache.get(key);
Bitmap bitmap = reference.get();
if (bitmap != null) {
return bitmap;
}
return decodeFile(key);
}
} catch (Exception e) {
// TODO: handle exception
Logger.debug(BitmapSoftRefrences.class,
"EXCEPTION: " + e.getMessage());
}
// the key does not exists so it could be that the
// file is not downloaded or decoded yet...
File file = new File(SDPATH + "/" + key);
if (file.exists()) {
return decodeFile(key);
} else {
Logger.debug(BitmapSoftRefrences.class, "RuntimeException");
throw new RuntimeException("RuntimeException!");
}
}
// 3. the decode file will return bitmap if bitmap is not cached
public static Bitmap decodeFile(String key) {
// --- prevent scaling
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inScaled = false;
Bitmap bitmap = BitmapFactory.decodeFile(SDPATH + "/" + key, opt);
mCache.put(key, new SoftReference<Bitmap>(bitmap));
return bitmap;
}
public static void clear() {
mCache.clear();
}
}
See the following tutorial
How to use SoftReference
To create a WeakReference, the syntax is WeakReference<SomeType> myWeakReference = new WeakReference<SomeType>(actualObject);. To retrieve the object via that WeakReference, do the check if (weakWidget == null). That way, you will avoid a NullPointerException if it has been garbage-collected already.
This Java.net article by Ethan Nicholas explains why you would want to use a WeakReference instead of a strong one. It provides the example of a final (unextendible) class called Widget that has no defined serial UID, presuming that the developer decides to define a serial UID to track each Widget instance. They do so by creating a new HashMap and doing something like serialNumberMap.put(widget, widgetSerialNumber); which is a strong reference. That means it must be explicitly cleaned up when no longer needed. The developer is responsible for knowing exactly when to manually "garbage-collect" that reference and remove it from the HashMap, which should be done only when they're really sure it's not needed anymore. This may be the problem you ran into in your application.
In this particular case, as the article explains, the developer could use the WeakHashMap class instead (as #NayAneshGupte put in his example), wherein the key is actually a WeakReference. This would allow the JVM nullify the keys to old Widgets as it saw fit, so that the garbage collector could come along and destroy their associated objects.
The article also goes on to talk about SoftReferences and PhantomReferences (which I've never used). You can read more about all of these in this javapapers.com article and this Rally blog.