Reputation: 17274
I'm having a memory leak in my app which is triggering GC a number of times and causing performance issues. I generated a leak suspect report
using MAT
. Here is the report:
Problem Suspect 1: One instance of "android.graphics.Bitmap" loaded by "" occupies 4,194,368 (20.13%) bytes. The memory is accumulated in one instance of "byte[]" loaded by "".
Problem Suspect 2: The class "android.content.res.Resources", loaded by "", occupies 3,962,504 (19.02%) bytes. The memory is accumulated in one instance of "java.lang.Object[]" loaded by "".
Problem Suspect 3: One instance of "android.graphics.Bitmap" loaded by "" occupies 3,145,792 (15.10%) bytes. The memory is accumulated in one instance of "byte[]" loaded by "".
Judging from the reports its obvious that memory leak is because of bitmap. I've researched a lot but couldn't rectify this leak. Please help me out. I'm using ImageLoader
class to download and display bitmaps. To use this class I simply call the displayImage()
method. Here is the code:
public class ImageLoader {
private static ImageLoader imageLoader;
private int maxNoOfConnections = 4;
FileCache fileCache;
ExecutorService executorService;
HttpURLConnection conn;
InputStream is;
OutputStream os;
PhotosLoader photosLoader;
Handler handler;
Bitmap bitmap;
private ImageLoader(Context context) {
fileCache = new FileCache(context);
executorService = Executors.newFixedThreadPool(maxNoOfConnections);
handler = new Handler();
}
public static ImageLoader getInstance(Context context) {
if (imageLoader == null)
imageLoader = new ImageLoader(context);
return imageLoader;
}
public void displayImage(String url, ProgressBar pBar, ImageView imageView) {
photosLoader = new PhotosLoader(url, imageView, pBar);
executorService.submit(photosLoader);
}
private Bitmap getBitmap(String url) {
File f = fileCache.getFile(url);
bitmap = decodeFile(f);
if (bitmap != null)
return bitmap;
try
{
URL imageUrl = new URL(url);
conn = (HttpURLConnection) imageUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
is = conn.getInputStream();
os = new FileOutputStream(f);
Utils.CopyStream(is, os);
os.close();
bitmap = decodeFile(f);
return bitmap;
} catch (Exception ex)
{
Log.e("inNews", "Image Url Malformed");
return null;
}
}
private Bitmap decodeFile(File f) {
try
{
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
final int REQUIRED_SIZE = 70;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1;
while (true)
{
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e)
{
}
return null;
}
class PhotosLoader implements Runnable {
String url;
ImageView imageView;
ProgressBar pBar;
Bitmap bmp;
public PhotosLoader(String url, ImageView imageView, ProgressBar pBar) {
this.url = url;
this.imageView = imageView;
this.pBar = pBar;
}
@Override
public void run() {
bmp = getBitmap(url);
handler.post(new Runnable() {
@Override
public void run() {
if (bmp != null)
{
pBar.setVisibility(View.GONE);
imageView.setImageBitmap(bmp);
} else
{
pBar.setVisibility(View.GONE);
imageView.setImageResource(R.drawable.img_no_image_grid);
}
}
});
}
}
}
Please help me rectify my code. Thanks!
Note: I've not used bitmap.recycle()
since, the documentation says that post-Honeycomb the GC collects bitmaps and its no longer necessary to forcefully recycle it !
Upvotes: 4
Views: 5265
Reputation: 249
when you have to initialise a library in an activity, always pass the application context, not the activity context.
Upvotes: 0
Reputation: 2847
I think the answer is simple, just keep clearing cache when you dont need memory/when OOM exception arises. I did that for you
MemoryCache memoryCache = new MemoryCache();
try {
Bitmap bitmap = null;
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) imageUrl
.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
InputStream is = conn.getInputStream();
OutputStream os = new FileOutputStream(f);
Utils.CopyStream(is, os);
os.close();
conn.disconnect();
bitmap = decodeFile(f);
return bitmap;
} catch (Throwable ex) {
ex.printStackTrace();
if (ex instanceof OutOfMemoryError)
memoryCache.clear();
return null;
}
Upvotes: 0
Reputation: 2771
I think the problem is the Singleton instance... I made a fork of the LazyList project, check this:
https://github.com/nicolasjafelle/LazyList
I have the same Memory Leak but, and maybe I am wrong, this singleton will never be garbage collected unless you kill the process with System.exit().
That is why the original LazyList project not use Singleton. I also think that if you need a cache you will need to be fast and the same for all the application.
The important thing in this ImageLoader is the FileCache and MemoryCache, when you call clearCache the bitmaps where collected.
Upvotes: 0
Reputation: 1570
Memory leak problem always Java's problems. I understand your code, code simple imagecache tool. Check SampleSize value and executor service run on only one thread. Four Thread have big memory and this background thread action. Your "handler" exchange to "runOnUIThread"
You should use;
Activity activity = (Activity)imageView.getContext();
__activity__.runOnUIThread(new Runnable()
{
if (bmp != null)
{
pBar.setVisibility(View.GONE);
imageView.setImageBitmap(bmp);
} else
{
pBar.setVisibility(View.GONE);
imageView.setImageResource(R.drawable.img_no_image_grid);
}
});
Upvotes: 0