Reputation: 6612
I acknoledged a memory leak when I saw that my small service, without any activity running, took about 15-16 MB. Normally (at the system boot) its memory usage was about 3MB, but if I opened my app memory usage increased, even after closing it!
Looking closer this memory usage went up with number of activities I opened! So I payed attention to my activities: in my app I have 3 of them and everyone has a listview, whose cells' contents are loaded from the net and each element is made like this:
|imageview |some text |
I think problem is in the async task in the adapter, so heres the code:
public class MyAdapter extends CursorAdapter{
private final LayoutInflater mInflater;
private LruCache<String, Bitmap> cachedBitmaps;
private SimpleDateFormat srcFormat; //I have to convert dates
private SimpleDateFormat dstFormat;
@Override
public void bindView(View view, Context context, Cursor c) {
ViewHolder holder = (ViewHolder)view.getTag();
if(holder == null){
holder = new ViewHolder();
holder.cellImg = (ImageView)view.findViewById(R.id.cellImg);
holder.cellProgress = view.findViewById(R.id.cellProgress);
holder.titleText = (TextView)view.findViewById(R.id.titleText);
view.setTag(holder);
}
try{
String imageLink = c.getString(c.getColumnIndex("image_link"));
String guid = c.getString(c.getColumnIndex("guid"));
Bitmap bitmap = cachedBitmaps.get(guid);
if(bitmap == null){ //need to download
ImageView image = holder.cellImg;
image.setTag(guid);
DownloadBitmapTask task = new DownloadBitmapTask(context,image,
holder.cellProgress);
task.execute(new String[]{imageLink});
//else I just get it from cache
Using the holder and the cache I make sure to efficiently load the image...I don't see any errors here but I shared this with you because I'd like to give you a complete view! Now here's the task, where I think there's a problem:
private class DownloadBitmapTask extends AsyncTask<String, Void, Bitmap>{
private ImageView img;
private View progress;
private String tag;
//normally you see a progress bar, when image is loaded I substitute it
public DownloadBitmapTask(Context c,ImageView img,View progress){
this.progress = progress;
this.img = img;
tag = new String(img.getTag().toString());
}
@Override
protected void onPreExecute() {
progress.setVisibility(View.VISIBLE);
img.setVisibility(View.INVISIBLE);
}
private final int MAX_TRIES = 3;
@Override
protected Bitmap doInBackground(String... args) {
Bitmap bitmap = null;
int failedTries = 0;
boolean done = false;
while((!done) && (failedTries<MAX_TRIES)){
try{
URLConnection conn = (new URL(args[0])).openConnection();
bitmap = BitmapFactory.decodeStream(
conn.getInputStream());
done = true;
}catch(IOException ex){
failedTries++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
progress.setVisibility(View.INVISIBLE);
if( (result != null) && (img.getTag().toString().equals(tag)) ){
cachedBitmaps.put((String)img.getTag(), result);
img.setImageBitmap(result);
img.setVisibility(View.VISIBLE);
img.getParent().recomputeViewAttributes(img);
notifyDataSetChanged();
}
}
}
I can't see any memory leak...but I'm sure there is! Please help me :(
EDIT: MAT tool gives me this:
The class "android.content.res.Resources", loaded by "", occupies 5.033.704 (38,96%) bytes. The memory is accumulated in one instance of "java.lang.Object[]" loaded by "".
They are all bitmaps, of curse :(
Upvotes: 3
Views: 1659
Reputation: 1045
I think you should close the Input stream every time you open as shown below.
InputStream iStream = null;
try{
URLConnection conn = (new URL(args[0])).openConnection();
iStream = conn.getInputStream();
itmap = BitmapFactory.decodeStream(iStream);
done = true;
}catch(IOException ex){
failedTries++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
} finally {
if(iStream != null)
iStream.close();
}
NOTE:- And it is advisable to close any streams after it is used.
Upvotes: 0
Reputation: 12048
Try to set your task created in:
DownloadBitmapTask task = new DownloadBitmapTask(context,image,
holder.cellProgress);
to null when the bitmap download is completed. You are not closing/disposing all resources allocated in AsyncTask and GC can't release them.
Upvotes: 0
Reputation: 3605
I think the problem is with BitmapFactory in the doInBackground method. This decoding consumes a lot of memory and also has some known leaks. Instead of decode the entire image, I always scale it in order to reduce the memory consumption. There is an example of this:
//decodes image and scales it to reduce memory consumption
private static Bitmap decodeImage(InputStream in, InputStream in2){
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in,null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=100;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
o2s.inTempStorage = new byte[16*1024];
return BitmapFactory.decodeStream(in2, null, o2);
}
NOTE: You'll need open two instances of your inputstream.
Other advice is to call the method clear() in Bitmap every time the image is no longer useful.
I hope this helps you!
Upvotes: 1