image download: memory leak using LazyList - android
I've got memory leak using LazyList.
I use one instance of ImageLoader in whole app, I create it in Application.onCreate(), because I need image downloading in several activities: list activity, one activity with gallery widget and full-screen gallery activitiy(all of them use same cache)
I modified image loader so it uses SoftReference-based HashMap. Here's code for SoftHashMap:
public class SoftHashMap extends AbstractMap {
private final Map hash=new HashMap();
private final int HARD_SIZE;
private final LinkedList hardCache=new LinkedList();
private final ReferenceQueue queue=new ReferenceQueue();
public SoftHashMap(){
this(100);
}
public SoftHashMap(int hardSize){
HARD_SIZE=hardSize;
}
public Object get(Object key){
Object result=null;
SoftReference soft_ref=(SoftReference)hash.get(key);
if(soft_ref!=null){
result=soft_ref.get();
if(result==null){
hash.remove(key);
}else{
hardCache.addFirst(result);
if(hardCache.size()>HARD_SIZE){
hardCache.removeLast();
}
}
}
return result;
}
private static class SoftValue extends SoftReference{
private final Object key;
public SoftValue(Object k, Object key, ReferenceQueue q) {
super(k, q);
this.key=key;
}
}
private void processQueue(){
SoftValue sv;
while((sv=(SoftValue)queue.poll())!=null){
hash.remove(sv.key);
}
}
public Object put(Object key, Object value){
processQueue();
return hash.put(key, new SoftValue(value, key, queue));
}
public void clear(){
hardCache.clear();
processQueue();
hash.clear();
}
public int size(){
processQueue();
return hash.size();
}
public Set entrySet() {
throw new UnsupportedOperationException();
}
}
ImageLoader class:
public class ImageLoader {
private SoftHashMap cache=new SoftHashMap(15);
private File cacheDir;
final int stub_id=R.drawable.stub;
private int mWidth, mHeight;
public ImageLoader(Context context, int h, int w){
mWidth=w;
mHeight=h;
photoLoaderThread.setPriority(Thread.NORM_PRIORITY);
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"CacheDir");
else
cacheDir=context.getCacheDir();
if(!cacheDir.exists())
cacheDir.mkdirs();
}
public void DisplayImage(String url, Activity activity, ImageView imageView)
{
Log.d("IMAGE LOADER", "getNativeHeapSize()-"+String.valueOf(Debug.getNativeHeapSize()/1024)+" kb");
Log.d("IMAGE LOADER", "getNativeHeapAllocatedSize()-"+String.valueOf(Debug.getNativeHeapAllocatedSize()/1024)+" kb");
Log.d("IMAGE LOADER", "getNativeHeapFreeSize()-"+String.valueOf(Debug.getNativeHeapFreeSize()/1024)+" kb");
if(cache.get(url)!=null){
imageView.setImageBitmap((Bitmap)cache.get(url));
}
else
{
queuePhoto(url, activity, imageView);
imageView.setImageResource(stub_id);
}
}
private void queuePhoto(String url, Activity activity, ImageView imageView)
{
//This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p=new PhotoToLoad(url, imageView);
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
//start thread if it's not started yet
if(photoLoaderThread.getState()==Thread.State.NEW)
photoLoaderThread.start();
}
private Bitmap getBitmap(String url)
{
//I identify images by hashcode. Not a perfect solution, good for the demo.
String filename=String.valueOf(url.hashCode());
File f=new File(cacheDir, filename);
//from SD cache
Bitmap b = decodeFile(f);
if(b!=null)
return b;
//from web
try {
Bitmap bitmap=null;
InputStream is=new URL(url).openStream();
OutputStream os = new FileOutputStream(f);
Utils.CopyStream(is, os);
os.close();
bitmap = decodeFile(f);
return bitmap;
} catch (Exception ex){
ex.printStackTrace();
return null;
}
}
//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){
Bitmap b=null;
try {
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis=new FileInputStream(f);
BitmapFactory.decodeStream(fis,null,o);
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Find the correct scale value. It should be the power of 2.
//final int REQUIRED_SIZE=mWidth;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true){
if(width_tmp/2<=mWidth || height_tmp/2<=mHeight)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
//o2.inPurgeable=true;
fis=new FileInputStream(f);
b=BitmapFactory.decodeStream(fis, null, o2);
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return b;
} catch (FileNotFoundException e) {}
return null;
}
class PhotoToLoad{
public String url;
public ImageView imageView;
public PhotoToLoad(String u, ImageView i){
url=u;
imageView=i;
}
}
PhotosQueue photosQueue=new PhotosQueue();
public void stopThread()
{
photoLoaderThread.interrupt();
}
class PhotosQueue{
private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>();
public void Clean(ImageView image)
{
for(int j=0 ;j<photosToLoad.size();){
if(photosToLoad.get(j).imageView==image)
photosToLoad.remove(j);
else
++j;
}
}
}
class PhotosLoader extends Thread{
public void run(){
try {
while(true)
{
//thread waits until there are any images to load in the queue
if(photosQueue.photosToLoad.size()==0)
synchronized(photosQueue.photosToLoad){
photosQueue.photosToLoad.wait();
}
if(photosQueue.photosToLoad.size()!=0)
{
PhotoToLoad photoToLoad;
synchronized(photosQueue.photosToLoad){
photoToLoad=photosQueue.photosToLoad.pop();
}
Bitmap bmp=getBitmap(photoToLoad.url);
cache.put(photoToLoad.url, bmp);
Object tag=photoToLoad.imageView.getTag();
if(tag!=null && ((String)tag).equals(photoToLoad.url)){
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
Activity a=(Activity)photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
if(Thread.interrupted())
break;
}
} catch (InterruptedException e) {
//allow thread to exit
}
}
}
PhotosLoader photoLoaderThread=new PhotosLoader();
class BitmapDisplayer implements Runnable
{
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i){bitmap=b;imageView=i;}
public void run()
{
if(bitmap!=null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(stub_id);
}
}
public void clearCache() {
//clear memory cache
cache.clear();
//clear SD cache
File[] files=cacheDir.listFiles();
for(File f:files)
f.delete();
}
}
And my Application class, not the best way to do it, though:
public class MyApplication extends Application {
ImageLoader mImageLoader;
#Override
public void onCreate(){
int h =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getHeight();
int w =((WindowManager)getApplicationContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay().getWidth();
mImageLoader=new ImageLoader(getApplicationContext(), h, w);
super.onCreate();
public ImageLoader getImageLoader(){
return mImageLoader;
}
#Override
public void onLowMemory(){
mImageLoader.clearCache();
Log.d("MY APP", "ON LOW MEMORY");
super.onLowMemory();
}
}
And the worst part: after some time I receive OOM exception when ImageLoader tries to decode another bitmap.
I'll appreciate any your help. Thanks.
EDIT I've got rid of hard cache, but i still get this OOM exception. It seems to me that I'm doing smth funny. I don't even know what extra information should I provide...
The images which i download from server are pretty big, though. And app fails to allocate appr. 1.5 mb, that's what I see in LogCat. But I just can't figure out why doesn't vm clear my SoftHashMap if there is need for memory...
onLowMemory will be of no help to you as it is not generated when your app is running out of memory, it is called when the Android system would like memory for a different application or itself before it kills off processes.
I don't see the need for hard cache - this is preventing drawables from being recycled. Just leave the drawables in the soft cache - the GC won't collect the memory while the drawables have references that are not soft so you don't need to worry about a drawable currently set in an ImageView being recycled.
Also how many images are you displaying on screen at once? How large are they?
Here's an amazing article on analyzing memory leaks. It can definitely help you. http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html.
Are you absolutely sure your SoftHashMap implementation works fine? Looks rather complicated. You can use debugger to ensure SoftHashMap never holds more than 15 bitmaps. MAT can also help you to identify how many bitmaps are there in memory.
You can also comment cache.put(photoToLoad.url, bmp) call. This way you'll disable in-memory caching to identify if it's a cause of a problem or not.
Yes it may be an Activity leak. You can identify that. If you just scroll around in the same activity and get OOM it means that something else is leaking not activity. If you stop/start activity several times and get OOM it means activity is leaking.
Also you can definitely say is activity leaking or not if you take a look at MAT histogram.
As you use inSampleSize the image size doesn't matter. It should work fine even with 5mpx images.
You could try to replace your SoftHashMap implementation with just HashMap<String, SoftReference<Bitmap>>. Read about SoftReference. It is good for very simple implementation of in-memory cache. It holds objects in memory if there's enough memory. If there's too little memory SoftReference releases objects.
I can also recommend you to use LinkedHashMap for in-memory cache. It has a special constuctor to iterate items in the order in which its entries were last accessed. So when you have more than 15 items in cache you can remove least-recently accessed items. As documentation says:
This kind of map is well-suited to building LRU caches.
You know my implementation was designed with small images in mind, something like 50*50. If you have larger images you should think how much memory they consume. If they take too much you could just cache them to SD card but not to memory. The performance may be slower but OOM would not be a problem any more.
Not related to OOM. I can see you call clearCache() in onLowMemory(). Not good because clearCache() also removes cache from SD. You should only clear in-memory cache not SD cache.
I seen that below line is commented in your code, First of All uncomment that line plz.
//o2.inPurgeable=true;
In the NonPurgeable case, an encoded bitstream is decoded to a different Bitmap over and over again until out-of-memory occurs. In Purgeable case, The memory allocated by one image will be shared by any new image whenever required and later any time if the older image reference is activiated in that case the OS will manage the memory reference it self by using a space of another image and vice verce and this way it always avoid the out of memory error.
If even you have purgeable case and still facing that memory leak then now use below try catch blog to trace the error and let me know the Stack Trace Detail.
try{
//your code to download or decode image
}catch(Error e){
//Print the stack trace
}
It looks like you're creating bitmaps, but never calling Bitmap.recycle().
EDIT: To elaborate, Bitmap.recycle() frees memory currently being used to store the Bitmap. Since Bitmaps created by the BitmapFactory are immutable, (you can check this with Bitmap.isMutable() on your freshly created bitmaps), simply removing a reference to them from the hashtable and waiting on the garbage collector isn't enough to free up the memory.
Call Bitmap Recycle whenever you're done with a specific bitmap (such as in the "clean" method of your photoQueue, or clearCache()).
Related
optimize android code snippet - better design approach?
I've this snippet of code which I would like to optimize it. I've a method which is been called regularly by the OSMdroid library to load tons of maptiles. This method directly invokes the filestream and load the bitmap directly and will return the bitmap once loaded on the main UI thread. Although I've managed to run in the background using AsyncTask with parallel executor. Sometimes with plenty number of overlays (itemized) in the mapview this snippet of code runs slower as GC_FO_ALLOC is triggered regularly for allocation, and in my log messages I get Grow Heap (frag case). I tried many ways to work around, but not effective enough. For some reason this task is being executed on main thread is my feeling as in my log messages I also get Skipped xx frames, the application may be doing lot of task. Any idea how can this be made better? The thing is method has to return back, as soon as it loads, there by how can I allow this method to wait until mapview is not panned or zoomed, then load the tiles? #SuppressWarnings("deprecation") #Override public Drawable getDrawable(final InputStream aFileInputStream) throws LowMemoryException { try { df = new DisplayFile(); df.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, aFileInputStream); return new BitmapDrawable(df.get()); } catch (final OutOfMemoryError e) { System.gc(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } return null; } private class DisplayFile extends AsyncTask<InputStream, Bitmap, Bitmap> { InputStream path; #Override protected Bitmap doInBackground(InputStream... arg0) { path = arg0[0]; BitmapFactory.Options mBitOpt = new BitmapFactory.Options(); mBitOpt.inDither = false; mBitOpt.inSampleSize = 1; mBitOpt.inPurgeable = true; mBitOpt.inInputShareable = true; mBitOpt.inPreferredConfig = Bitmap.Config.ARGB_8888; final Bitmap mBitmap = BitmapFactory.decodeStream(path,null,mBitOpt); return mBitmap; } }
As far as I understand, you are trying to display an offline map using osmdroid. Android is unable to load "tons of map tiles" in memory, because it just doesn't provide enough memory heap to each application. This is why osmdroid has advanced mechanisms (background loading, LRU cache, ...) Why are you not just using these standard osmdroid mechanisms? Look at this post for more details about off-line maps on osmdroid: Download maps for osmdroid
Android OOM errors due to image cacheing?
It will be a little easier to understand this if you think if the app I'm working on like facebook (at least that will give you a context for what i'm doing). I have one activity that uses a navigationdrawer to swap between fragments each of these fragments could have from one to several "subFragments" --- think of these as individual posts with things like a textview, a few buttons and an ImageView in them. When the "container fragment" is created I call to the server and get the data I need to make all the "sub fragments". I then iterate through the data and add all the "sub fragments" to the "container fragment"'s view. I have noticed that I seem to be using an excessively high amount of memory (around 129 mb). When the subfragments are created I then call this async task which pulls the images each fragment will need from a server and places them in their ImageView. public class URLImageFactory extends AsyncTask<String, Void, Bitmap> { ImageView imgView; private static final ImgCache mCache = new ImgCache(); public URLImageFactory(ImageView bmImage) { this.imgView = bmImage; } #Override protected Bitmap doInBackground(String... urls) { String urldisplay = Config.SERVER_URL + urls[0].replaceAll("\\s+","%20"); Bitmap bitmap = null; //If it is in the cache don't bother pulling it from the server if(bitmap != null) { return bitmap; } try { InputStream in = new java.net.URL(urldisplay).openStream(); //This is in case we are using match_parent/wrap content if(imgView.getWidth() == 0 || imgView.getHeight() == 0) { bitmap = BitmapFactory.decodeStream(in); } else { bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeStream(in), imgView.getWidth(), imgView.getHeight(), false); } mCache.put(urldisplay,bitmap); } catch (Exception e) { Log.e("Error", e.getMessage()); e.printStackTrace(); } return bitmap; } #Override protected void onPostExecute(Bitmap result) { imgView.setImageBitmap(result); } } I have made a rudimentary attempt at caching the images to speed up the process public class ImgCache extends LruCache { public ImgCache() { super( calculateCacheSize() ); } public static int calculateCacheSize() { int maxMemory = (int) Runtime.getRuntime().maxMemory() / 8; int cacheSize = maxMemory; return cacheSize; } #Override protected int sizeOf( String key, Bitmap value ) { return value.getByteCount() / 1024; } } My app is crashing with outOfMemory exception. I have also noticed that when my "container fragment" is swapped out for a different one ... usually a similarly structured fragment... the onPause() and onStop() of these sub fragments are not being fired. If it helps the sub fragments are static inner classes while the container fragment is not. I think it is a bitmap related issue, but I'm not sure. I have attempted to use TransactionManager.remove(fragment) on all the sub fragments when the parent hits onPause but it doesn't seem to help.
You're dividing the byte count of each object by 1024, but only dividing the available memory by 8; the result is that the LRU cache could fill up to 128 times the amount of available memory you have. Remove the / 1024 from sizeOf and you should be good.
Android loading image from url performance
I have classic AsyncTask to load image: private class DownloadImageTask extends AsyncTask<String,Void,Bitmap> { Bitmap bitmap = null; #Override protected Bitmap doInBackground(String... str) { try{ InputStream in = new java.net.URL(picture).openStream(); bitmap = BitmapFactory.decodeStream(new SanInputStream(in)); //viewPicture.setImageBitmap(bitmap); viewPicture.setBackgroundDrawable(new BitmapDrawable(bitmap)); } catch(Exception e){ e.printStackTrace(); } return bitmap; } } But loading of image is to long. When I start this activity, everything is loaded besides image and only after waiting a second I can see it. What is the problem?
viewPicture.setBackgroundDrawable(new BitmapDrawable(bitmap)); should be done on the UI thread, in the onPostExecute method of your AsyncTask. Also, close the stream (if !null) you use with a nice finally block of your try/catch. And don't worry about the time, it's the way to go.
Simple... because it takes time to open connection to the resource URL and download all the required bytes. Moreover, the performance may also vary on the speed of your Internet connection.
android java.lang.OutOfMemoryError: bitmap size exceeds VM budget
i've some leaks problems. What i'm doing is loading some pictures in a table. I've created an asyncronous class for loading images. In my table while i'm cycling the array I add my class final LoaderImageView image = new LoaderImageView(getContext(),vgood[i].getImageUrl(),(int)(ratio *80),(int)(ratio *80)); row.addView(image); In my asyncronous class i'm loading images from this return Drawable.createFromStream(((java.io.InputStream)new java.net.URL(url).getContent()), "name"); Called by public void setImageDrawable(final String imageUrl) { mDrawable = null; mSpinner.setVisibility(View.VISIBLE); mImage.setVisibility(View.GONE); new Thread(){ public void run() { try { mDrawable = getDrawableFromUrl(imageUrl); imageLoadedHandler.sendEmptyMessage(COMPLETE); } catch (MalformedURLException e) { imageLoadedHandler.sendEmptyMessage(FAILED); e.printStackTrace(); } catch (IOException e) { imageLoadedHandler.sendEmptyMessage(FAILED); e.printStackTrace(); } }; }.start(); } When i've a lot of images to load the app crashes due to out of memory caused by this line return Drawable.createFromStream(((java.io.InputStream)new java.net.URL(url).getContent()), "name"); How can i fix that? where i'm going wrong? thank you
Never load any tiles that you don't need. "am collecting all bitmaps from sdcard" suggests that you are decompressing every image you might ever need, which would be the wrong approach. Use smaller tiles. 256x256 is a good size. Load them as Config.RGB565 (i.e. 16bpp). Regularly recycle() Bitmaps you don't need. Basically you need to implement some sort of most-recently-used (MRU) Bitmap cache Extrated From Android: "java-lang-outofmemoryerror-bitmap-size-exceeds-vm-budget-android" While loading bitmaps from sdcard?
How to lazy load images in ListView in Android
I am using a ListView to display some images and captions associated with those images. I am getting the images from the Internet. Is there a way to lazy load images so while the text displays, the UI is not blocked and images are displayed as they are downloaded? The total number of images is not fixed.
Here's what I created to hold the images that my app is currently displaying. Please note that the "Log" object in use here is my custom wrapper around the final Log class inside Android. package com.wilson.android.library; /* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ import java.io.IOException; public class DrawableManager { private final Map<String, Drawable> drawableMap; public DrawableManager() { drawableMap = new HashMap<String, Drawable>(); } public Drawable fetchDrawable(String urlString) { if (drawableMap.containsKey(urlString)) { return drawableMap.get(urlString); } Log.d(this.getClass().getSimpleName(), "image url:" + urlString); try { InputStream is = fetch(urlString); Drawable drawable = Drawable.createFromStream(is, "src"); if (drawable != null) { drawableMap.put(urlString, drawable); Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", " + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", " + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth()); } else { Log.w(this.getClass().getSimpleName(), "could not get thumbnail"); } return drawable; } catch (MalformedURLException e) { Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e); return null; } catch (IOException e) { Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e); return null; } } public void fetchDrawableOnThread(final String urlString, final ImageView imageView) { if (drawableMap.containsKey(urlString)) { imageView.setImageDrawable(drawableMap.get(urlString)); } final Handler handler = new Handler(Looper.getMainLooper()) { #Override public void handleMessage(Message message) { imageView.setImageDrawable((Drawable) message.obj); } }; Thread thread = new Thread() { #Override public void run() { //TODO : set imageView to a "pending" image Drawable drawable = fetchDrawable(urlString); Message message = handler.obtainMessage(1, drawable); handler.sendMessage(message); } }; thread.start(); } private InputStream fetch(String urlString) throws MalformedURLException, IOException { DefaultHttpClient httpClient = new DefaultHttpClient(); HttpGet request = new HttpGet(urlString); HttpResponse response = httpClient.execute(request); return response.getEntity().getContent(); } }
I made a simple demo of a lazy list (located at GitHub) with images. Basic Usage ImageLoader imageLoader=new ImageLoader(context); ... imageLoader.DisplayImage(url, imageView); Don't forget to add the following permissions to your AndroidManifest.xml: <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> Please create only one instance of ImageLoader and reuse it all around your application. This way image caching will be much more efficient. It may be helpful to somebody. It downloads images in the background thread. Images are being cached on an SD card and in memory. The cache implementation is very simple and is just enough for the demo. I decode images with inSampleSize to reduce memory consumption. I also try to handle recycled views correctly.
I recommend open source instrument Universal Image Loader. It is originally based on Fedor Vlasov's project LazyList and has been vastly improved since then. Multithread image loading Possibility of wide tuning ImageLoader's configuration (thread executors, downloader, decoder, memory and disc cache, display image options, and others) Possibility of image caching in memory and/or on the device's file system (or SD card) Possibility to "listen" loading process Possibility to customize every display image call with separated options Widget support Android 2.0+ support
Multithreading For Performance, a tutorial by Gilles Debunne. This is from the Android Developers Blog. The suggested code uses: AsyncTasks. A hard, limited size, FIFO cache. A soft, easily garbage collect-ed cache. A placeholder Drawable while you download.
Update: Note that this answer is pretty ineffective now. The Garbage Collector acts aggressively on SoftReference and WeakReference, so this code is NOT suitable for new apps. (Instead, try libraries like Universal Image Loader suggested in other answers.) Thanks to James for the code, and Bao-Long for the suggestion of using SoftReference. I implemented the SoftReference changes on James' code. Unfortunately, SoftReferences caused my images to be garbage collected too quickly. In my case, it was fine without the SoftReference stuff, because my list size is limited and my images are small. There's a discussion from a year ago regarding the SoftReferences on google groups: link to thread. As a solution to the too-early garbage collection, they suggest the possibility of manually setting the VM heap size using dalvik.system.VMRuntime.setMinimumHeapSize(), which is not very attractive to me. public DrawableManager() { drawableMap = new HashMap<String, SoftReference<Drawable>>(); } public Drawable fetchDrawable(String urlString) { SoftReference<Drawable> drawableRef = drawableMap.get(urlString); if (drawableRef != null) { Drawable drawable = drawableRef.get(); if (drawable != null) return drawable; // Reference has expired so remove the key from drawableMap drawableMap.remove(urlString); } if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "image url:" + urlString); try { InputStream is = fetch(urlString); Drawable drawable = Drawable.createFromStream(is, "src"); drawableRef = new SoftReference<Drawable>(drawable); drawableMap.put(urlString, drawableRef); if (Constants.LOGGING) Log.d(this.getClass().getSimpleName(), "got a thumbnail drawable: " + drawable.getBounds() + ", " + drawable.getIntrinsicHeight() + "," + drawable.getIntrinsicWidth() + ", " + drawable.getMinimumHeight() + "," + drawable.getMinimumWidth()); return drawableRef.get(); } catch (MalformedURLException e) { if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e); return null; } catch (IOException e) { if (Constants.LOGGING) Log.e(this.getClass().getSimpleName(), "fetchDrawable failed", e); return null; } } public void fetchDrawableOnThread(final String urlString, final ImageView imageView) { SoftReference<Drawable> drawableRef = drawableMap.get(urlString); if (drawableRef != null) { Drawable drawable = drawableRef.get(); if (drawable != null) { imageView.setImageDrawable(drawableRef.get()); return; } // Reference has expired so remove the key from drawableMap drawableMap.remove(urlString); } final Handler handler = new Handler() { #Override public void handleMessage(Message message) { imageView.setImageDrawable((Drawable) message.obj); } }; Thread thread = new Thread() { #Override public void run() { //TODO : set imageView to a "pending" image Drawable drawable = fetchDrawable(urlString); Message message = handler.obtainMessage(1, drawable); handler.sendMessage(message); } }; thread.start(); }
Picasso Use Jake Wharton's Picasso Library. (A Perfect ImageLoading Library from the developer of ActionBarSherlock) A powerful image downloading and caching library for Android. Images add much-needed context and visual flair to Android applications. Picasso allows for hassle-free image loading in your application—often in one line of code! Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView); Many common pitfalls of image loading on Android are handled automatically by Picasso: Handling ImageView recycling and download cancellation in an adapter. Complex image transformations with minimal memory use. Automatic memory and disk caching. Picasso Jake Wharton's Library Glide Glide is a fast and efficient open-source media management framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy-to-use interface. Glide supports fetching, decoding, and displaying video stills, images, and animated GIFs. Glide includes a flexible API that allows developers to plug into almost any network stack. By default, Glide uses a custom HttpUrlConnection based stack but also includes utility libraries plug-in to Google's Volley project or Square's OkHttp library instead. Glide.with(this).load("your-url-here").into(imageView); Glide's primary focus is on making scrolling any kind of a list of images as smooth and fast as possible, but Glide is also effective for almost any case where you need to fetch, resize, and display a remote image. Glide Image Loading Library Fresco by Facebook Fresco is a powerful system for displaying images in Android applications. Fresco takes care of image loading and display, so you don't have to. It will load images from the network, local storage, or local resources, and display a placeholder until the image has arrived. It has two levels of cache; one in memory and another in internal storage. Fresco Github In Android 4.x and lower, Fresco puts images in a special region of Android memory. This lets your application run faster - and suffer the dreaded OutOfMemoryError much less often. Fresco Documentation
High-performance loader - after examining the methods suggested here, I used Ben's solution with some changes - I realized that working with drawable is faster than with bitmaps so I uses drawable instead Using SoftReference is great, but it makes the cached image to be deleted too often, so I added a Linked list that holds images references, preventing the image to be deleted, until it reached a predefined size To open the InputStream I used java.net.URLConnection which allows me to use web cache (you need to set a response cache first, but that's another story) My code: import java.util.Map; import java.util.HashMap; import java.util.LinkedList; import java.util.Collections; import java.util.WeakHashMap; import java.lang.ref.SoftReference; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import android.graphics.drawable.Drawable; import android.widget.ImageView; import android.os.Handler; import android.os.Message; import java.io.InputStream; import java.net.MalformedURLException; import java.io.IOException; import java.net.URL; import java.net.URLConnection; public class DrawableBackgroundDownloader { private final Map<String, SoftReference<Drawable>> mCache = new HashMap<String, SoftReference<Drawable>>(); private final LinkedList <Drawable> mChacheController = new LinkedList <Drawable> (); private ExecutorService mThreadPool; private final Map<ImageView, String> mImageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>()); public static int MAX_CACHE_SIZE = 80; public int THREAD_POOL_SIZE = 3; /** * Constructor */ public DrawableBackgroundDownloader() { mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE); } /** * Clears all instance data and stops running threads */ public void Reset() { ExecutorService oldThreadPool = mThreadPool; mThreadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE); oldThreadPool.shutdownNow(); mChacheController.clear(); mCache.clear(); mImageViews.clear(); } public void loadDrawable(final String url, final ImageView imageView,Drawable placeholder) { mImageViews.put(imageView, url); Drawable drawable = getDrawableFromCache(url); // check in UI thread, so no concurrency issues if (drawable != null) { //Log.d(null, "Item loaded from mCache: " + url); imageView.setImageDrawable(drawable); } else { imageView.setImageDrawable(placeholder); queueJob(url, imageView, placeholder); } } private Drawable getDrawableFromCache(String url) { if (mCache.containsKey(url)) { return mCache.get(url).get(); } return null; } private synchronized void putDrawableInCache(String url,Drawable drawable) { int chacheControllerSize = mChacheController.size(); if (chacheControllerSize > MAX_CACHE_SIZE) mChacheController.subList(0, MAX_CACHE_SIZE/2).clear(); mChacheController.addLast(drawable); mCache.put(url, new SoftReference<Drawable>(drawable)); } private void queueJob(final String url, final ImageView imageView,final Drawable placeholder) { /* Create handler in UI thread. */ final Handler handler = new Handler() { #Override public void handleMessage(Message msg) { String tag = mImageViews.get(imageView); if (tag != null && tag.equals(url)) { if (imageView.isShown()) if (msg.obj != null) { imageView.setImageDrawable((Drawable) msg.obj); } else { imageView.setImageDrawable(placeholder); //Log.d(null, "fail " + url); } } } }; mThreadPool.submit(new Runnable() { #Override public void run() { final Drawable bmp = downloadDrawable(url); // if the view is not visible anymore, the image will be ready for next time in cache if (imageView.isShown()) { Message message = Message.obtain(); message.obj = bmp; //Log.d(null, "Item downloaded: " + url); handler.sendMessage(message); } } }); } private Drawable downloadDrawable(String url) { try { InputStream is = getInputStream(url); Drawable drawable = Drawable.createFromStream(is, url); putDrawableInCache(url,drawable); return drawable; } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } private InputStream getInputStream(String urlString) throws MalformedURLException, IOException { URL url = new URL(urlString); URLConnection connection; connection = url.openConnection(); connection.setUseCaches(true); connection.connect(); InputStream response = connection.getInputStream(); return response; } }
I have followed this Android Training and I think it does an excellent job at downloading images without blocking the main UI. It also handles caching and dealing with scrolling through many images: Loading Large Bitmaps Efficiently
1. Picasso allows for hassle-free image loading in your application—often in one line of code! Use Gradle: implementation 'com.squareup.picasso:picasso:(insert latest version)' Just one line of code! Picasso.get().load("http://i.imgur.com/DvpvklR.png").into(imageView); 2. Glide An image loading and caching library for Android focused on smooth scrolling Use Gradle: repositories { mavenCentral() google() } dependencies { implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' } // For a simple view: Glide.with(this).load("http://i.imgur.com/DvpvklR.png").into(imageView); 3. fresco is a powerful system for displaying images in Android applications.Fresco takes care of image loading and display, so you don't have to. Getting Started with Fresco
I've written a tutorial that explains how to do lazy-loading of images in a listview. I go into some detail about the issues of recycling and concurrency. I also use a fixed thread pool to prevent spawning a lot of threads. Lazy loading of images in Listview Tutorial
The way I do it is by launching a thread to download the images in the background and hand it a callback for each list item. When an image is finished downloading it calls the callback which updates the view for the list item. This method doesn't work very well when you're recycling views however.
I just want to add one more good example, XML Adapters. As it's is used by Google and I am also using the same logic to avoid an OutOfMemory error. Basically this ImageDownloader is your answer (as it covers most of your requirements). Some you can also implement in that.
This is a common problem on Android that has been solved in many ways by many people. In my opinion the best solution I've seen is the relatively new library called Picasso. Here are the highlights: Open source, but headed up by Jake Wharton of ActionBarSherlock fame. Asynchronously load images from network or app resources with one line of code Automatic ListView detection Automatic disk and memory caching Can do custom transformations Lots of configurable options Super simple API Frequently updated
I have been using NetworkImageView from the new Android Volley Library com.android.volley.toolbox.NetworkImageView, and it seems to be working pretty well. Apparently, this is the same view that is used in Google Play and other new Google applications. Definitely worth checking out. Google I/O 2013 volley image cache tutorial Developers Google events
Well, image loading time from the Internet has many solutions. You may also use the library Android-Query. It will give you all the required activity. Make sure what you want to do and read the library wiki page. And solve the image loading restriction. This is my code: #Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.row, null); } ImageView imageview = (ImageView) v.findViewById(R.id.icon); AQuery aq = new AQuery(convertView); String imageUrl = "http://www.vikispot.com/z/images/vikispot/android-w.png"; aq.id(imageview).progress(this).image(imageUrl, true, true, 0, 0, new BitmapAjaxCallback() { #Override public void callback(String url, ImageView iv, Bitmap bm, AjaxStatus status) { iv.setImageBitmap(bm); } )); return v; } It should be solve your lazy loading problem.
I think this issue is very popular among Android developers, and there are plenty of such libraries that claims to resolve this issue, but only a few of them seems to be on the mark. AQuery is one such library, but it is better than most of them in all aspects and is worth trying for.
You must try this Universal Loader is best. I am using this after done many RnD on lazy loading . Universal Image Loader Features Multithread image loading (async or sync) Wide customization of ImageLoader's configuration (thread executors, downloader, decoder, memory and disk cache, display image options, etc.) Many customization options for every display image call (stub images, caching switch, decoding options, Bitmap processing and displaying, etc.) Image caching in memory and/or on disk (device's file system or SD card) Listening loading process (including downloading progress) Android 2.0+ support
Have a look at Shutterbug, Applidium's lightweight SDWebImage (a nice library on iOS) port to Android. It supports asynchronous caching, stores failed URLs, handles concurrency well, and helpful subclasses are included. Pull requests (and bug reports) are welcome, too!
DroidParts has ImageFetcher that requires zero configuration to get started. Uses a disk & in-memory Least Recently Used (LRU) cache. Efficiently decodes images. Supports modifying bitmaps in background thread. Has simple cross-fade. Has image loading progress callback. Clone DroidPartsGram for an example:
Novoda also has a great lazy image loading library and many apps like Songkick, Podio, SecretDJ and ImageSearch use their library. Their library is hosted here on Github and they have a pretty active issues tracker as well. Their project seems to be pretty active too, with over 300+ commits at the time of writing this reply.
Just a quick tip for someone who is in indecision regarding what library to use for lazy-loading images: There are four basic ways. DIY => Not the best solution but for a few images and if you want to go without the hassle of using others libraries Volley's Lazy Loading library => From guys at android. It is nice and everything but is poorly documented and hence is a problem to use. Picasso: A simple solution that just works, you can even specify the exact image size you want to bring in. It is very simple to use but might not be very "performant" for apps that has to deal with humongous amounts of images. UIL: The best way to lazy load images. You can cache images(you need permission of course), initialize the loader once, then have your work done. The most mature asynchronous image loading library I have ever seen so far.
If you want to display Shimmer layout like Facebook there is a official facebook library for that. FaceBook Shimmer Android It takes care of everything, You just need to put your desired design code in nested manner in shimmer frame. Here is a sample code. <com.facebook.shimmer.ShimmerFrameLayout android:id=“#+id/shimmer_view_container” android:layout_width=“wrap_content” android:layout_height="wrap_content" shimmer:duration="1000"> <here will be your content to display /> </com.facebook.shimmer.ShimmerFrameLayout> And here is the java code for it. ShimmerFrameLayout shimmerContainer = (ShimmerFrameLayout) findViewById(R.id.shimmer_view_container); shimmerContainer.startShimmerAnimation(); Add this dependency in your gradle file. implementation 'com.facebook.shimmer:shimmer:0.1.0#aar' Here is how it looks like.
Check my fork of LazyList. Basically, I improve the LazyList by delaying the call of the ImageView and create two methods: When you need to put something like "Loading image..." When you need to show the downloaded image. I also improved the ImageLoader by implementing a singleton in this object.
All above code have their own worth but with my personal experience just give a try with Picasso. Picasso is a library specifically for this purpose, in-fact it will manage cache and all other network operations automatically.You will have to add library in your project and just write a single line of code to load image from remote URL. Please visit here : http://code.tutsplus.com/tutorials/android-sdk-working-with-picasso--cms-22149
Use the glide library. It worked for me and will work for your code too.It works for both images as well as gifs too. ImageView imageView = (ImageView) findViewById(R.id.test_image); GlideDrawableImageViewTarget imagePreview = new GlideDrawableImageViewTarget(imageView); Glide .with(this) .load(url) .listener(new RequestListener<String, GlideDrawable>() { #Override public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) { return false; } #Override public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { return false; } }) .into(imagePreview); }
I can recommend a different way that works like a charm: Android Query. You can download that JAR file from here AQuery androidAQuery = new AQuery(this); As an example: androidAQuery.id(YOUR IMAGEVIEW).image(YOUR IMAGE TO LOAD, true, true, getDeviceWidth(), ANY DEFAULT IMAGE YOU WANT TO SHOW); It's very fast and accurate, and using this you can find many more features like animation when loading, getting a bitmap (if needed), etc.
Give Aquery a try. It has amazingly simple methods to load and cache images asynchronously.
URLImageViewHelper is an amazing library that helps you to do that.
public class ImageDownloader { Map<String, Bitmap> imageCache; public ImageDownloader() { imageCache = new HashMap<String, Bitmap>(); } // download function public void download(String url, ImageView imageView) { if (cancelPotentialDownload(url, imageView)) { // Caching code right here String filename = String.valueOf(url.hashCode()); File f = new File(getCacheDirectory(imageView.getContext()), filename); // Is the bitmap in our memory cache? Bitmap bitmap = null; bitmap = (Bitmap) imageCache.get(f.getPath()); if (bitmap == null) { bitmap = BitmapFactory.decodeFile(f.getPath()); if (bitmap != null) { imageCache.put(f.getPath(), bitmap); } } // No? download it if (bitmap == null) { try { BitmapDownloaderTask task = new BitmapDownloaderTask( imageView); DownloadedDrawable downloadedDrawable = new DownloadedDrawable( task); imageView.setImageDrawable(downloadedDrawable); task.execute(url); } catch (Exception e) { Log.e("Error==>", e.toString()); } } else { // Yes? set the image imageView.setImageBitmap(bitmap); } } } // cancel a download (internal only) private static boolean cancelPotentialDownload(String url, ImageView imageView) { BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); if (bitmapDownloaderTask != null) { String bitmapUrl = bitmapDownloaderTask.url; if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { bitmapDownloaderTask.cancel(true); } else { // The same URL is already being downloaded. return false; } } return true; } // gets an existing download if one exists for the imageview private static BitmapDownloaderTask getBitmapDownloaderTask( ImageView imageView) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof DownloadedDrawable) { DownloadedDrawable downloadedDrawable = (DownloadedDrawable) drawable; return downloadedDrawable.getBitmapDownloaderTask(); } } return null; } // our caching functions // Find the dir to save cached images private static File getCacheDirectory(Context context) { String sdState = android.os.Environment.getExternalStorageState(); File cacheDir; if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) { File sdDir = android.os.Environment.getExternalStorageDirectory(); // TODO : Change your diretcory here cacheDir = new File(sdDir, "data/ToDo/images"); } else cacheDir = context.getCacheDir(); if (!cacheDir.exists()) cacheDir.mkdirs(); return cacheDir; } private void writeFile(Bitmap bmp, File f) { FileOutputStream out = null; try { out = new FileOutputStream(f); bmp.compress(Bitmap.CompressFormat.PNG, 80, out); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) out.close(); } catch (Exception ex) { } } } // download asynctask public class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { private String url; private final WeakReference<ImageView> imageViewReference; public BitmapDownloaderTask(ImageView imageView) { imageViewReference = new WeakReference<ImageView>(imageView); } #Override // Actual download method, run in the task thread protected Bitmap doInBackground(String... params) { // params comes from the execute() call: params[0] is the url. url = (String) params[0]; return downloadBitmap(params[0]); } #Override // Once the image is downloaded, associates it to the imageView protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { bitmap = null; } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); // Change bitmap only if this process is still associated with // it if (this == bitmapDownloaderTask) { imageView.setImageBitmap(bitmap); // cache the image String filename = String.valueOf(url.hashCode()); File f = new File( getCacheDirectory(imageView.getContext()), filename); imageCache.put(f.getPath(), bitmap); writeFile(bitmap, f); } } } } static class DownloadedDrawable extends ColorDrawable { private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { super(Color.WHITE); bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>( bitmapDownloaderTask); } public BitmapDownloaderTask getBitmapDownloaderTask() { return bitmapDownloaderTaskReference.get(); } } // the actual download code static Bitmap downloadBitmap(String url) { HttpParams params = new BasicHttpParams(); params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); HttpClient client = new DefaultHttpClient(params); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; try { inputStream = entity.getContent(); final Bitmap bitmap = BitmapFactory .decodeStream(inputStream); return bitmap; } finally { if (inputStream != null) { inputStream.close(); } entity.consumeContent(); } } } catch (Exception e) { // Could provide a more explicit error message for IOException or // IllegalStateException getRequest.abort(); Log.w("ImageDownloader", "Error while retrieving bitmap from " + url + e.toString()); } finally { if (client != null) { // client.close(); } } return null; } }
I had this issue and implemented lruCache. I believe you need API 12 and above or use the compatiblity v4 library. lurCache is fast memory, but it also has a budget, so if you're worried about that you can use a diskcache... It's all described in Caching Bitmaps. I'll now provide my implementation which is a singleton I call from anywhere like this: //Where the first is a string and the other is a imageview to load. DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar); Here's the ideal code to cache and then call the above in getView of an adapter when retrieving the web image: public class DownloadImageTask { private LruCache<String, Bitmap> mMemoryCache; /* Create a singleton class to call this from multiple classes */ private static DownloadImageTask instance = null; public static DownloadImageTask getInstance() { if (instance == null) { instance = new DownloadImageTask(); } return instance; } //Lock the constructor from public instances private DownloadImageTask() { // 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 loadBitmap(String avatarURL, ImageView imageView) { final String imageKey = String.valueOf(avatarURL); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.ic_launcher); new DownloadImageTaskViaWeb(imageView).execute(avatarURL); } } private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /* A background process that opens a http stream and decodes a web image. */ class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap> { ImageView bmImage; public DownloadImageTaskViaWeb(ImageView bmImage) { this.bmImage = bmImage; } protected Bitmap doInBackground(String... urls) { String urldisplay = urls[0]; Bitmap mIcon = null; try { InputStream in = new java.net.URL(urldisplay).openStream(); mIcon = BitmapFactory.decodeStream(in); } catch (Exception e) { Log.e("Error", e.getMessage()); e.printStackTrace(); } addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon); return mIcon; } /* After decoding we update the view on the main UI. */ protected void onPostExecute(Bitmap result) { bmImage.setImageBitmap(result); } } }