I might just be confused about how LruCache is supposed to work, but are does it not allow accessing objects from one instance that were saved on another instance? Surely this is not the case otherwise it kind of defeats the purpose of having cache.
Example:
class CacheInterface {
private val lruCache: LruCache<String, Bitmap>
init {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
// Use 1/8th of the available memory for this memory cache.
val cacheSize = maxMemory / 8
lruCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, value: Bitmap): Int {
return value.byteCount / 1024
}
}
}
fun getBitmap(key: String): Bitmap? {
return lruCache.get(key)
}
fun storeBitmap(key: String, bitmap: Bitmap) {
lruCache.put(key, bitmap)
Utils.log(lruCache.get(key))
}
}
val bitmap = getBitmal()
val instance1 = CacheInterface()
instance1.storeBitmap("key1", bitmap)
log(instance1.getBitmap("key1")) //android.graphics.Bitmap#6854e91
log(CacheInterface().getBitmap("key1")) //null
As far as I understand, cache is stored until it's deleted by the user (manually or uninstalling the app), or cleared by the system when it exceeds the allowed space. What am I missing?
An LruCache object just stores references to objects in memory. As soon as you lose the reference to the LruCache, the LruCache object and all of the objects within that cache are garbage collected. There's nothing stored to disk.
Yes it is. I'll just share here what I was confused about in case anyone also is.
Initially because of this guide (Caching Bitmaps) that reccomends using LruCache, I was left under the impression that LruCache was an interface to access app's cache, but like #CommonsWare mentioned it has no I/O in it - it's just a utility class to hold memory using the LRU policy. To access your app's cache you need to use Context.getCacheDir(), good explanation here. In my case I ended up using a singleton of LruCache, since I already have a service running most of the time the app will not be killed every time it's closed.
log(CacheInterface().getBitmap("key1")) //null
equals
val instance2 = CacheInterface()
log(instance2 .getBitmap("key1"))
instance1 != instance2
change to Singleton
object CacheInterface{
...
}
use
CacheInterface.storeBitmap("key1",bitmap)
CacheInterface.getBitmap("key1")
Related
When the activity or fragment is destroyed I want to write a large 1-10MB file. Since I want to store the file when the user closes the application I have to do that inside the onPause() method. I use DataOutputStream because it has methods for writing different types: Integer, Float, ByteArray and many more.
Lets say I need to write a large file and it take 2-3 seconds. The code in method onPause() is executed on the main thread. Lets say I freeze the thread for 6s to simulate the thread doing work, using Thread.sleep(6000). If the user try to return to the application the application is not responding for those 6 seconds, because all the work is done in the main thread.
If I start a AsyncTask that run the task on separate thread and do the writing of the file there, the problem with the freezing UI is solved. But as far as I know if the task takes 6s, and the application is destroyed before the thread has finished working, there is a big problem with memory leaks!
So my question is how to write a big file, on separate thread when the user is about to close the application and prevent memory leaks???
Below is example code of writing everything on the main thread:
override fun onStop() {
// code is run in the main thread
saveFile(context!!, "test", "testFile.txt")
}
fun saveFile(context: Context, fileDirectory: String, fileName: String) {
// path to /data/data/yourAppName/app_data/imageDir
val directory: File = context.getDir(fileDirectory, Context.MODE_PRIVATE)
val file = File(directory, fileName)
val fileOutputStream: FileOutputStream
try {
fileOutputStream = FileOutputStream(file)
val dataOutputStream = DataOutputStream(fileOutputStream)
dataOutputStream.writeInt(11) // write integer
dataOutputStream.writeFloat(1.8f) // write float
dataOutputStream.write(byteArrayOf(1, 33, 124, 41)) // write byte array
dataOutputStream.flush()
dataOutputStream.close()
fileOutputStream.flush()
fileOutputStream.close()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
Since I need the context to generate the File and if the activity is destroyed before the thread has finished working that would lead to memory leaks. If I wrap the context in WeakReference does that protect it from memory leaks?? And will the thread finish its work even if the activity/fragment is destroyed??
Below is example of using AsyncTask, and wrap the context in WeakReference!
class SaveFile(context: Context, var fileDirectory: String, var fileName: String) : AsyncTask<Void, Void, Int>() {
private var contextWrapper: WeakReference<ContextWrapper>
init {
val contextWrapper = ContextWrapper(context)
this.contextWrapper = WeakReference(contextWrapper)
}
override fun doInBackground(vararg params: Void): Int {
val directory: File = contextWrapper.get()!!.getDir(fileDirectory, Context.MODE_PRIVATE)
val file = File(directory, fileName)
val fileOutputStream: FileOutputStream
try {
fileOutputStream = FileOutputStream(file)
val dataOutputStream = DataOutputStream(fileOutputStream)
dataOutputStream.writeInt(11) // write integer
dataOutputStream.writeFloat(1.8f) // write float
dataOutputStream.write(byteArrayOf(1, 33, 124, 41)) // write byte array
dataOutputStream.flush()
dataOutputStream.close()
fileOutputStream.flush()
fileOutputStream.close()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return 0
}
}
You can use an applicationContext (which you can get from any Context) if you want to make sure you're not holding onto an Activity or whatever. But holding onto an Activity for a few extra seconds isn't really a leak - there's no guarantee you'd even get a garbage collection event in that timespan anyway! It's long-lived references you need to be careful of.
Your WeakReference idea does prevent memory leaks by releasing the Context when nothing else is holding it - the problem is you're treating it as though it always returns the context, by saying
contextWrapper.get()!!
In the event your weak reference does its job and releases the context, this call returns null, and your non-null assertion (!!) fails and crashes your app. So you're swapping your potential memory usage issue for a much bigger one!
If you're ever doing this kind of thing, you need to handle the situation where the object with the weak reference has been garbage collected - that's what you're doing with the weak reference, making it so you can't guarantee you'll have access to it.
So you want something like this instead
val directory: File? = contextWrapper.get()?.getDir(fileDirectory, Context.MODE_PRIVATE)
if (directory == null) return
...
it's up to you how you handle the "no context" path, here I'm just giving up on the file stuff. If that's not an option, and you need that Context so you can do something important, then you can't just hold a weak reference to one.
I know you've already worked something out with services, but this is an important thing to know!
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 installed a xamarin Picasso in my application, but wanted to clear the cached when the user logout my application. I can't find a ClearCache method which original Picasso library has one.
Not sure if you mean the memory or disk cache, so:
Disk cache:
Picasso defines its disk cache name as (PICASSO_CACHE = "picasso-cache"), so if you are not using a custom disk cache, you can delete the application's Picasso defined disk cache directly:
_picasso.Dispose(); // Done using Picasso
var cache = new File(BaseContext.ApplicationContext.CacheDir, "picasso-cache");
if (cache.Exists())
{
cache.Delete();
}
// Recreate if needed, but Picasso Build() will recreate it if it does not exist
if (!cache.Exists())
{
cache.Mkdirs();
}
If you are using your own LruCache or custom disk Cache implementation:
var cache = new File(BaseContext.ApplicationContext.CacheDir, "picasso-cache");
if (!cache.Exists())
cache.Mkdirs();
_lruCache = new LruCache((int)Runtime.GetRuntime().MaxMemory() / 1024 * 8);
_diskLruCache = new DiskLruCache(cache, 10 * 1024 * 1024);
_picasso = new Picasso.Builder(BaseContext).MemoryCache(_lruCache).Downloader(new OkHttpDownloader(_OkHttp3Client)).IndicatorsEnabled(true).Build();
Clear memory:
_lruCache.Clear();
Clear disk cache via your custom disk cache implementation:
_diskLruCache.Delete(); // Assumes Android style DiskLruCache
Hello I have a Hasmap of bitmaps which I need to store on the Android device to be used when next the application starts.
My hashmap looks like this, and contains up to 1000 Bitmaps:
private static HashMap <String, Bitmap> cache = new HashMap<String, Bitmap>();
You might want to consider create extension of Map (by using AbstractMap) and override the related functions. In general the structure of the extension should have:
An in memory hard cache using regular Map. This should be a size bound cache object. You could leverage LinkedHashMap and override removeEldesEntry() to check if the size is exceeded
this.objectMap = Collections.synchronizedMap(new LinkedHashMap() {
#Override
protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// remove from cache, pass to secondary SoftReference cache or directly to the disk
}
}
});
If the cache is exceeded, then put it to disk
Override the get function to do the following : On initial get, load the bitmap from disk based on certain naming convention (related to the key) and store it in memory. Roughly something like (please forgive any syntax error)
#Override
public Bitmap get(Object key) {
if(key != null) {
// first level, hard cache
if(objectMap.containsKey(key)) {
return objectMap.get(key);
}
// soft reference cache
if(secondaryCache.containsKey(key)) {
return secondaryCache.get(key);
}
// get from disk if it is not in hard or soft cache
String fileName = "Disk-" + key + ".txt";
File f = new File(cacheDir, fileName);
if(f.exists()) {
// put this back to the hard cache
Bitmap object = readFromReader(f);
if(object != null) {
objectMap.put((String)key, object);
return object;
}
}
}
return null; // unable to get from any data source
}
Similarly your put has to be override to put to the disk for later use, so when you reinitialize your app you could just create an instance of the map extension. If you want, you could also preload the hashmap by most recently used items in the app. Basically, by extending the AbstractMap, you get the flexibilities without killing your memory with that 1000 bitmaps. Hope this helps
My application is using bitmaps and every time the user come to the specific activity where it shows an image the second time it stops working.
Bitmap bm = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"//Pics/"Image.jpg");
I have tried using things like...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];
Not sure what to set it too. But this doesnt help. Once the user leaves this activity is there not a way to clear the bitmap etc? thanks
Call Bitmap.recycle() when you are done using the Bitmap to free the memory.
Besides using Bitmap.recycle() as suggested (which is not suitable for all situations and it's a pain in the neck to be asking: "do I still need this bitmap?"), I always use this technique which works really fine:
// 1. create a cache map
private WeakHashMap<String, SoftReference<Bitmap>> mCache;
As you can see, it's a hash map of WeakReferences with a SoftReference as the values.
//2. when you need a bitmap, ask for it:
public Bitmap get(String key){
if( key == null ){
return null;
}
if( mCache.containsKey(key) ){
SoftReference<Bitmap> reference = mCache.get(key);
Bitmap bitmap = reference.get();
if( bitmap != null ){
return bitmap;
}
return decodeFile(key);
}
// the key does not exists so it could be that the
// file is not downloaded or decoded yet...
File file = new File(Environment.getExternalStorageDirectory(), key);
if( file.exists() ){
return decodeFile(key);
} else{
throw new RuntimeException("Boooom!");
}
}
This will check the cache map. If the file was already decoded, it will be returned; otherwise it will be decoded and cached.
//3. the decode file will look like this in your case
private Bitmap decodeFile(String key) {
Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"//Pics/"+key);
mCache.put(key, new SoftReference<Bitmap>(bitmap));
return bitmap;
}
Working with soft references is nice because you shift the responsibility of removing bitmaps from memory to the OS.
Be aware.
When we think about softreferences we think that the OS will remove the softreferenced objects from memrory before reporting an outofmemory exception.
In android this is not always true. I had to implement my own caching system for images and I can assure you softreferenced objects were not removed from memory when memory was almost full.
Finally I had to switch to hard references (the normal ones) but used android.support.v4.util.LruCache for managing the cached objects. I would call recycle on the onRemoved callback from the lru cache. Its definetely more convenient.
Cheers.