3

I'm using View Pager to show images which are downloaded from the network in my application. The number of images could be from 5 to 20. I'm using Volley library to do the network operations. The app wasn't taking much memory before but now after adding the view pager, the app takes a lot of memory and every time i open this activity, the memory used in heap increase (checked from the log messages). I also used Eclipse Memory analyzer to check where the leak was and it is definitely the bitmaps and the multiple instances of this activity. There is definitely a leak, as this activity isn't getting GC'ed, some references are keeping this from getting garbage collected. I've added my implementation of the view pager here.

public class ViewPagerAdapter extends PagerAdapter {
        Context context;

        public ViewPagerAdapter(Context context) {
            this.context = context;
        }

        @Override
        public int getCount() {
            return photoReferences.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == ((RelativeLayout) object);
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            final ImageView im;
            final ProgressBar pb;

            View itemView = inflater.inflate(R.layout.place_photos_item, container, false);

            im = (ImageView) itemView.findViewById(R.id.placeImage);
            attributes = (TextView) itemView.findViewById(R.id.placeAttributes);
            pb = (ProgressBar) itemView.findViewById(R.id.progressBarPhoto);

            imageLoader.get(url, new ImageListener() {

                public void onErrorResponse(VolleyError arg0) {
                    im.setImageResource(R.drawable.onErrorImage); 
                }

                public void onResponse(ImageContainer response, boolean arg1) {
                    if (response.getBitmap() != null) {
                        im.startAnimation(AnimationUtils.loadAnimation(context, android.R.anim.fade_in));
                        im.setImageBitmap(response.getBitmap());
                        pb.setVisibility(View.GONE);
                    } 
                }
            });

            ((ViewPager) container).addView(itemView);

            return itemView;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            ((ViewPager) container).removeView((RelativeLayout) object);
        }

    }

Also, I'm using the Bitmap Cache of size 3 times the number of screenBytes(screenWidth * screenHeight * 4). I'm testing on Nexus 4 running 4.3 and I never run into a OOM exception cause the heap size is huge on this device but the app can take more than 100 mb of memory(it will crash on most devices) if I open the activity again and again, and before it used to take around 16-20 mbs of memory no matter what. Here's the cache code.

public class BitmapCache extends LruCache<Object, Object> implements ImageCache {
        public BitmapCache(int maxSize) {
            super(maxSize);
        }

        @Override
        public Bitmap getBitmap(String url) {
            return (Bitmap) get(url);
        }

        @Override
        public void putBitmap(String url, Bitmap bitmap) {
            put(url, bitmap);
        }
    }

Could anyone please suggest me what should I do to catch the leak? Is there anything wrong in the View Pager or my Volley usage? I'm not happy with the transition of the Pager as well, lags a bit, is that related?

Update: Here's the screenshot of MAT, possible leak. This is on every activity that uses Volley library. I've been reading a lot but I couldn't solve the problem. Is volley causing leak or am I doing something terribly wrong?

Rahul Sainani
  • 3,437
  • 1
  • 34
  • 48

4 Answers4

3

You can find your leak by using MAT. First you run your app and leak a few activity instances. Then you grab a snapshot of the heap and look for those leaked Activity objects... you can use 'Object Query Language' (OQL) to find them by type (e.g. "SELECT * FROM com.foo.FooActivity").

Once you've found a leaked object, right-click on it and ask MAT to trace all its incoming references back to their GC roots. The leaked reference will be one of those.

For a better introduction to the technique you could try this article:

http://android-developers.blogspot.co.uk/2011/03/memory-analysis-for-android.html

Reuben Scratton
  • 38,595
  • 9
  • 77
  • 86
  • Hey @Reuben Thanks for the quick reply. I tried Mat and have added a link to the screenshot in the question. The activities with volley code are leaking (2 of those have bitmaps also). I got to this from histogram and dominator tree both. I still can't figure out the problem. Any suggestions? – Rahul Sainani Nov 12 '13 at 12:19
  • That looks like a bitmap cache, presumably perfectly valid allocations. You want to focus on the leaked Activity instances... – Reuben Scratton Nov 12 '13 at 13:57
  • I got here from the Histogram View where there were more than one instance of this activity. I selected the list objects with incoming references and then on one of those instances, I selected path to GC roots excluding weak references. I also saw the Google IO talk by Patrick Dubroy on memory management for better understanding. Only when the volley code is run, multiple instances of these activities are reported. I face this problem in 3 activities with Volley, 2 of those have bitmaps also. – Rahul Sainani Nov 12 '13 at 14:28
  • So you pick a leaked Activity object in MAT, and then by inspecting the objects that hold references to it you find out what's leaking it... – Reuben Scratton Nov 12 '13 at 16:58
  • Yes I did that, I get the similar path to GC, like in the screenshot. EVerytime I get mQueue from Volley or cache. Did you find anything peculiar in this code? I'm trying to find where I would've referenced the activity (context) that's preventing the GC from collecting it. I haven't till now. But there are multiple instances of activities, which aren't right, right? – Rahul Sainani Nov 12 '13 at 17:08
  • Well the screenshot you posted is showing one particular reference chain leading to a Bitmap, which is why I thought you were barking up the wrong tree. I am saying you should look closely at a leaked Activity, i.e. pick one that has definitely leaked and examine every reference chain keeping it alive. If you are creating a new activity and then backing out of it, and its not being GC'd, then yes that's definitely a leak. It's astonishingly easy to leak Activities. – Reuben Scratton Nov 12 '13 at 19:36
  • Alright! I'll scan my code more. I guess there's no other way to do this. Thanks for the replies, I'll get back to you as I solve this. – Rahul Sainani Nov 12 '13 at 19:39
  • 1
    You may well be inadvertently leaking a Context in the above code btw. You are passing a new ImageListener into Volley which as an inner anonymous class is holding a reference on its containing ViewPagerAdapter which in turn holds a Context which I assume is your activity class. You don't know how long Volley will be using that ImageListener for. I suggest losing the Context context member completely and using View.getContext() instead. The only place you use it (for AnimationUtil) you could do container.getContext() instead. – Reuben Scratton Nov 12 '13 at 19:43
  • Removed the context, the activity is still leaking. The view pager adapter is inside the activity that's leaking. – Rahul Sainani Nov 12 '13 at 20:46
  • Well there's your problem right there. If the adapter is an inner class then it implicitly holds a reference on it's outer class, i.e the Activity. So your ImageListener has a ref to the Adapter, the Adapter has a ref to the Activity. And you have many ImageListeners. There is your leak. – Reuben Scratton Nov 13 '13 at 00:38
  • Hey @Reuben, I moved the adapter. It's no longer an inner class. But the leak is still there. I'm passing the Image Loader Object in the constructor of the adapter so it can be used in instantiate item. `requestQueue = Volley.newRequestQueue(getApplicationContext()); imageLoader = new ImageLoader(requestQueue, new BitmapCache(1));` When I use the image Loader to get images in instantiate item, there's a memory leak, when I don't there isn't. So it's the listener that's leaking memory, is it? How do i stop it? there will be 10 listeners in case i have 10 images. – Rahul Sainani Nov 14 '13 at 12:43
  • It might be a Volley bug. Your listeners should become GC'able once the images have been loaded, and yet they're not... they're hanging around causing this leak. This is exactly why I avoid 3rd party frameworks btw... they *always* cause trouble. To try and fix it, yes you need to move ImageListener somewhere else... maybe implement it on your Activity. ImageLoader.get() is returning an ImageContainer which you need to associate with the ImageView... you could use a HashMap for that. Then in onResponse() you use the map to find the ImageView corresponding to the ImageContainer param – Reuben Scratton Nov 14 '13 at 14:50
  • I solved it! It was totally my mistake, Google recommends to create a Volley Singleton and I was creating queues for every activity, which is not good. I searched about it and read about [Volley Singleton on this link](http://cypressnorth.com/mobile-application-development/setting-android-google-volley-imageloader-networkimageview/). Your inputs were crucial for me finding the issue, so if you could edit the answer to include this link and whatever you feel like is important, I'll mark your answer as correct, so other don't have to read through all our comments. :) Cheers! – Rahul Sainani Nov 14 '13 at 15:46
2

I guess you are using using Viewpager and Imageviews

About image views you are using powerful image downloading and caching library like latest Volley Imageloading(really helpful for large size images) to improve the image loading capabilities in a efficient way.

About Viewpager you have to use efficient adapter FragmentStatePagerAdapter: This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.

please think before you are using FragmentPagerAdapter becouse it stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager. In contrary its sibling, FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus. Therefore FragmentStatePagerAdapter should be used when we have to use dynamic fragments, like fragments with widgets, as their data could be stored in the savedInstanceState. Also it wont affect the performance even if there are large number of fragments. In contrary its sibling FragmentPagerAdapter should be used when we need to store the whole fragment in memory. When I say the whole fragment is kept in memory it means, its instances wont be destroyed and would create a memory overhead. Therefore it is advised to use FragmentPagerAdapter only when there are low number of fragments for ViewPager. It would be even better if the fragments are static, since they would not be having large amount of objects whose instances would be stored. Hope this clears out the difference between Android FragmentPagerAdapter and FragmentStatePagerAdapter.

Try to learn Google android gallary app example, use image view loading animations to make a great user experience.

I hope this will solves your grow heap problems.

Credits:FragmentPagerAdapter vs FragmentStatePagerAdapter

LOG_TAG
  • 19,894
  • 12
  • 72
  • 105
  • I'm using the normal Pager Adapter, as I just need to display an image and some text. A fragment state pager adapter seemed like overkill, don't you think? But I don't like The instantiateItem method in the normal pager adaper, it is called for the next page also i.e if I'm on page 1 and have no plans to go to page 2, but instantiate item will be called for page 2, that's a bit unnecessary I think. – Rahul Sainani Nov 14 '13 at 12:51
  • 1
    FragmentStatePagerAdapter is like recycled listview! say you have 10 pages on viewpager if you are on page 6 only 7 and 5th page lives in memory unlike normal pageradapter! about your last point you can load the view by implementing onpageselected method of the viewpager!! – LOG_TAG Nov 15 '13 at 04:31
  • Yeah I did that, on page selected. View pager is working alright for me. Hardly any memory is used. – Rahul Sainani Nov 15 '13 at 10:37
  • 1
    @when you have more than 5 or 10 page you try what I suggested ! Good luck! – LOG_TAG Nov 15 '13 at 11:33
  • Excellent answer. I have an app with 1000+ pages and works like a charm with FragmentStatePageAdapter – Sebastián Guerrero Apr 09 '15 at 17:36
0

You forget to recycle your downloaded Bitmaps as they become unneeded.

Basically, every Bitmap you handle manually, you have to recyle().

That being said, your destroyItem() method should look something like this:

public void destroyItem(ViewGroup container, int position, Object object) {

    RelativeLayout rl = (RelativeLayout) object;
    ImageView im = rl.findViewById(R.id.image_view);

    bitmapDrawable = (BitmapDrawable) im.getDrawable();
    if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
        bitmap = bitmapDrawable.getBitmap();
        bitmap.recycle();
    }
    container.removeView(rl);
}
Ivan Bartsov
  • 19,664
  • 7
  • 61
  • 59
0

You should check out the new version of Volley , old version did cause the leak problem. In old version ,Volley has 4 thread do request , And each of them will keep a request , and request keep strong reference of listener , and your response listener do something with the ImageView , ImageView keep the Activity context. so all of your View is leaked. In MAT use select * from instanceof android.app.Activity you will see your Activity is leaked.

New Version of Volley has fixed this problem . please check out here

And use this will help your find out your leaked Activity , leakcanary

Fantasy_RQG
  • 143
  • 1
  • 13