Adrian
Adrian

Reputation: 407

Can't Beat 'Out Of Memory' Exceptions

I am developing an app with Mono for Android.

I have been struggling with Out of memory exceptions for the last few days and am starting to lose hope!

I have a ListView displaying anything from 200 to 600 items. These items consist of a bitmap thumbnail and some text.

I am decoding the Bitmap asynchronously using an AsyncTask, here is the code:

public class BitmapWorkerTask : AsyncTask
    {
        private WeakReference imageViewReference;
        public string thisURL = "";
        private int sampleSize = 0;
        private int reqHeight = 0;
        private int reqWidht = 0;

        public BitmapWorkerTask(ImageView imageView, int pSampleSize, int pReqWidth, int pReqHeight)
        {
            //_____________________________________________________________________
            // Use a WeakReference to ensure the ImageView can be garbage collected
            imageViewReference = new WeakReference(imageView);

            reqHeight = pReqHeight;
            reqWidht = pReqWidth;
            sampleSize = pSampleSize;
        }

        protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] @params)
        {
            string strUrl = @params[0].ToString();

            try
            {
                return DecodeSampleBitmapFromStream(strUrl, reqWidht, reqHeight);
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        protected override void OnPostExecute(Java.Lang.Object result)
        {
            base.OnPostExecute(result);

            if (IsCancelled)
            {
                result = null;
                Log.Debug("TT", "OnPostExecute - Task Cancelled");
            }
            else
            {
                Bitmap bmpResult = result as Bitmap;

                if (imageViewReference != null && bmpResult != null)
                {
                    ImageView view = imageViewReference.Target as ImageView;

                    if (view != null)
                    {
                        view.SetImageBitmap(bmpResult);
                    }
                }
            }
        }

        public static int CalculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight)
        {
            //_____________________________
            // Raw height and width of image
            int height = options.OutHeight;
            int width = options.OutWidth;
            int inSampleSize = 1;

            if (height > reqHeight || width > reqWidth)
            {
                if (width > height)
                {
                    inSampleSize = (int)Math.Round((float)height / (float)reqHeight);
                }
                else
                {
                    inSampleSize = (int)Math.Round((float)width / (float)reqWidth);
                }
            }

            return inSampleSize;

        }

        public static Bitmap DecodeSampleBitmapFromStream(string URL, int reqWidth, int reqHeight)
        {
            URL url = new URL(URL);

            try
            {
                //______________________________________________________________
                // First decode with inJustDecodeBounds=true to check dimensions
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.InJustDecodeBounds = true;
                BitmapFactory.DecodeStream(url.OpenConnection().InputStream, null, options);

                //______________________
                // Calculate inSampleSize
                options.InSampleSize = CalculateInSampleSize(options, reqWidth, reqHeight);

                //____________________________________
                // Decode bitmap with inSampleSize set
                options.InJustDecodeBounds = false;
                return BitmapFactory.DecodeStream(url.OpenConnection().InputStream, null, options);
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                url.Dispose();
            }
        }

I am starting this AsyncTask from the Lists GetView() function using this Method:

public void loadBitmap(string url, ImageView imageView)
        {
            if (Common.cancelPotentialWork(url, imageView))
            {
                BitmapWorkerTask task = new BitmapWorkerTask(imageView, 2, 80,80);

                AsyncDrawable asyncDrawable = new AsyncDrawable(null, null, task);

                imageView.SetImageDrawable(asyncDrawable);

                task.Execute(url);
            }
        }

Everything works as expected for a period of time, but if I continuously scroll up and down through my list I eventually start getting OutOfMemoryExceptions and the app crashes. My understanding of how the Android list works is it disposes of the ListItem views as they move off screen, but it feels as though this is not happening!

It feels like all those bitmaps I am decoding as I scroll through the list are for whatever reason being held in memory? What could I be missing here that is preventing those bitmaps from being disposed of? Where could I implement a call to Bitmap.Recycle() to ensure the bitmaps are cleared?

I did a test whereby I made a call to GC.Collect on every call to GetView which did seem to keep my memory usage fairly consistent, but I know this shouldn't be needed and it affects scrolling performance.

Why when I scroll through my list without the call to GC.Collect() am I not seeing those garbage collection message indicating that the system is, in fact, doing routine collections?

Any help is appreciated, I am losing the will to code!

Upvotes: 1

Views: 2982

Answers (1)

Stuart
Stuart

Reputation: 66882

My understanding of how the Android list works is it disposes of the List item views as they move off screen, but It feels as though this is not happening!

This isn't correct.

What Android does is it hold on to the set of item views and it tries to reuse them after they have gone off screen. This is what the convertView parameter is for.

I can't see your Adapter code posted in the question, so I'm not sure what your code is for using the convertView parameter, but I'd guess what it should do in the case of a convertView is:

  • it should cancel any existing async image fetch/conversion
  • it should start a new one

The MvvmCross code may be a little too complicated for you as a reference/example here, but you can at least see convertView in use in MvxBindableListAdapter.cs - see protected virtual View GetBindableView(View convertView, object source, int templateId)

Upvotes: 1

Related Questions