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.
Related
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;
}
}
i have the following code to cache bitmaps , when i try to retrieve them using their key . They always return null . Please help me . Thank You
final int memClass = ((ActivityManager) mcontext.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
final int cacheSize = 1024 * 1024 *memClass ;
Toast.makeText(mcontext,"Max Cache "+cacheSize,Toast.LENGTH_LONG).show();
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
Functions to get and set cache
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
Toast.makeText(mcontext,"CEO "+key+" - "+bitmap,Toast.LENGTH_LONG).show();
mMemoryCache.put(key, bitmap);
}
else
{
Toast.makeText(mcontext,"NULL",Toast.LENGTH_LONG).show();
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
-----------UPDATE-----------------------------------------
Well after some debugging found out that it is inserting the value , but i cannot access it after i have exited the app. When i try to relaunch the app the cache is lost. Please could someone help me how to retain that value . Thank You
I may be wrong, but from my understunding the cache is made to mantain some data in your RAM to remove charge from the CPU. Cache is acumulated during application lifetime (load same photos multiple times, etc). It is normal behaviour to clear the cache after the application is shot down.
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.
I was using UniversalImageDownloader for my app.in UIL we can save images from cache memory.
File cachedImage = ImageLoader.getInstance().getDiscCache().get(imageUrl);
if (cachedImage.exists())
{// code for save 2 sd
}
Is it possible in picasso?
There is a private Method in Picasso -
Bitmap quickMemoryCacheCheck(String key) {
Bitmap cached = cache.get(key);
if (cached != null) {
stats.dispatchCacheHit();
} else {
stats.dispatchCacheMiss();
}
return cached;
}
Modify the source according to your need.
You can do like this , Use OkHttp & Picasso:
public class APP extends Application{
public static OkHttpDownloader okHttpDownloader;
#Override
public void onCreate() {
super.onCreate();
Picasso.Builder b = new Picasso.Builder(this);
okHttpDownloader = new OkHttpDownloader(this);
b.downloader(okHttpDownloader);
Picasso.setSingletonInstance(b.build());
}
}
Then get the File from OkHttp local cache:
Downloader.Response res = APP.okHttpDownloader.load(Uri.parse(your image Url),0);
Log.i(TAG,"Get From DISK: " + res.isCached() );
storeImageFile(res.getInputStream());
You can get bitmap from ImangeView onSuccess() callback
Picasso.with(context).load(path).into(imageView, new Callback(){
public void onSuccess() {
Bitmap bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
//save bitmap to sdcard
}
public void onError() {}
}
WARNING: Saved bitmap may be different to original bitmap.
Below code snippet will first make Picasso load image from cache, if failed then download and display image in imageView
You can debug memory performance with
Picasso.with(appContext).setIndicatorsEnabled(true);
green (memory, best performance)
blue (disk, good performance)
red (network, worst performance).
//Debugging memory performance https://futurestud.io/tutorials/picasso-cache-indicators-logging-stats
//TODO remove on deployment
Picasso.with(appContext).setIndicatorsEnabled(true);
//Try to load image from cache
Picasso.with(appContext)
.load(imageUrl)
.networkPolicy(NetworkPolicy.OFFLINE).placeholder(R.drawable.ic_launcher)
.placeholder(R.drawable.ic_launcher)
.resize(100, 100)
.error(R.drawable.ic_drawer)
.into(markerImageView, new Callback() {
#Override
public void onSuccess() {
}
#Override
public void onError() {
// Try online if cache failed
Picasso.with(appContext)
.load(imageUrl)
.placeholder(R.drawable.ic_launcher)
.resize(100, 100)
.error(R.drawable.ic_drawer)
.into(markerImageView);
}
});
I would like to load images into a gallery view from a url?
I first make them a bitmap using this.
URL aURL = new URL(myRemoteImages[position]);
URLConnection conn = aURL.openConnection();
conn.setUseCaches(true);
conn.connect();
Object response = conn.getContent();
if (response instanceof Bitmap) {
Bitmap bm = (Bitmap)response;
InputStream is = conn.getInputStream();
/* Buffered is always good for a performance plus. */
BufferedInputStream bis = new BufferedInputStream(is);
/* Decode url-data to a bitmap. */
bm = BitmapFactory.decodeStream(bis);
bis.close();
is.close();
Log.v(imageUrl, "Retrieving image");
/* Apply the Bitmap to the ImageView that will be returned. */
i.setImageBitmap(bm);
How could I go about caching this bitmap? So when the user swipes the screen it doesn't reload over and over?
EDIT: I CALL getImage() to retreive the text url for each url.
i use both of these in a asyncTask. preExecute i call getImage()
and doInBackground i set the gallery to the imageAdapter.
private class MyTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... arg0) {try {
getImages();
Log.v("MyTask", "Image 1 retreived");
getImage2();
Log.v("MyTask", "Image 2 retreived");
getImage3();
Log.v("MyTask", "Image 3 retreived");
getImage4();
Log.v("MyTask", "Image 4 retreived");
} catch (IOException e) {
Log.e("MainMenu retreive image", "Image Retreival failed");
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void notUsed){
((Gallery) findViewById(R.id.gallery))
.setAdapter(new ImageAdapter(MainMenu.this));
}
}
EDIT: getView() method
public View getView(int position, View convertView, ViewGroup parent) {
ImageView i = new ImageView(this.myContext);
try {
URL aURL = new URL(myRemoteImages[position]);
URLConnection conn = aURL.openConnection();
conn.setUseCaches(true);
conn.connect();
Object response = conn.getContent();
if (response instanceof Bitmap) {
Bitmap bm = (Bitmap)response;
You could store your images on the SDCard and on the launch of your application you need to initialize a component that keeps a HashMap<String,Bitmap> and initialize the map with the contents of a folder from the SDCard.
When you will need an image, you will first check if your HashMap contains the key of that image, let say myMap.contains(myFileName) and if it does you will fetch the image from the map, and if the image is not contained in your map you will need to download it, store id on the SDCard and put in in your map.
I'm not sure if this is the best solution, since if you have a large number of Bitmaps your application can run out of resources. Also I think storing Drawable instead of Bitmap will be less memory consuming.
EDIT:For your problem you need create a custom class that has a member Drawable and execute the URLConnection just when you first create your objects. After that in the getView() method you will just use myObj.getMyDrawable() to access the drawable for that specific object.
Since Android 4 it is possible to cache HTTP responses directly by the HttpUrlConnection. See this article: http://practicaldroid.blogspot.de/2013/01/utilizing-http-response-cache.html
Caching images on android is an level oriented task:
Generally at Caching at two levels:
Runtime Heap memory in a form key-value, where key being a identifier for image and value is object of bitmap.(Refer Here)
Most optimised Way for implementing it is LRUCache:
Which Basically maintains a LinkedList for recently accessed items where dump the items accessed at earliest due to memory limitation.
As this backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
private LruCache<String, Bitmap> mMemoryCache;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
#Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
};
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
Disk Storage: As memory has limited storage with restricted lifecycle.
Memory Cache is good for speeding up in accessing recently viewed images but you can not rely on this for images available on this cache.
UIComponents with indefinite and large data set can easily fill up memory and resulting in loss of images.
Memory cache may get effected in situations like going in background while on call.
Disk Cache can help you to make images persist for longer.
One of it's Optimized way of using it is DiskLruCache
So When you look up for a bitmap in memory cache is result is nil you can try to access it from disk cache and incase you don't find it here too and then just load it from internet.
For Implementation Refer here