1

我正在为 Android 开发一个带有 Mono 的应用程序。

在过去的几天里,我一直在与内存不足异常作斗争,并且开始失去希望!

我有一个 ListView 显示从 200 到 600 项的任何内容。这些项目由位图缩略图和一些文本组成。

我正在使用 AsyncTask 异步解码位图,代码如下:

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();
            }
        }

我使用此方法从 Lists GetView() 函数启动此 AsyncTask:

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);
            }
        }

在一段时间内一切都按预期工作,但如果我在列表中不断上下滚动,我最终会开始收到OutOfMemoryExceptions 并且应用程序崩溃。我对 Android 列表如何工作的理解是它会在ListItem视图移出屏幕时处理它们,但感觉好像这没有发生!

感觉就像我在滚动列表时正在解码的所有位图出于某种原因被保存在内存中?我可能会错过什么阻止这些位图被处理掉?我在哪里可以调用以Bitmap.Recycle()确保位图被清除?

我做了一个测试,在每次调用 GetView 时都调用了 GC.Collect,这似乎使我的内存使用保持相当一致,但我知道这不应该是必需的,它会影响滚动性能。

为什么当我在没有调用的情况下滚动列表时,GC.Collect()我没有看到那些表明系统实际上正在执行例行收集的垃圾收集消息?

感谢任何帮助,我失去了编码的意愿!

4

1 回答 1

1

我对 Android 列表如何工作的理解是,当列表项视图移出屏幕时,它会处理它们,但感觉好像这没有发生!

这是不正确的。

Android 所做的是保留一组项目视图,并在它们离开屏幕后尝试重用它们。这就是 convertView 参数的用途。

我看不到您在问题中发布的适配器代码,因此我不确定您的代码用于使用 convertView 参数是什么,但我猜想在 convertView 的情况下它应该做什么:

  • 它应该取消任何现有的异步图像获取/转换
  • 它应该开始一个新的

MvvmCross 代码作为参考/示例对您来说可能有点过于复杂,但您至少可以看到在MvxBindableListAdapter.cs中使用的 convertView - 请参阅protected virtual View GetBindableView(View convertView, object source, int templateId)

于 2012-11-13T12:12:23.267 回答