java内存优化实例----在非UI线程中处理图片

来源:互联网 发布:鸳鸯 知乎 编辑:程序博客网 时间:2024/06/06 09:27

课程内容

  1. 使用 AsyncTask
  2. 处理并发情况

您还应该阅读

  • Designing for Responsiveness
  • Multithreading for Performance

在 高效的加载大尺寸图片 中介绍的 用来解析图片的 BitmapFactory.decode* 函数,需要在非UI线程中调用。
如果是读取网络图片或者磁盘图片,在UI线程中可能会导致程序ANR;如果是解析已经在内存中的图片,则可以在UI线程中调用这些函数。

这节内容介绍如何使用AsyncTask 来处理图片,以及如何处理并发访问。

使用 AsyncTask

AsyncTask 类是一个在后台线程中处理任务,并把结果反馈给UI线程的工具类。使用该类,只需要继承她并且重写对应的函数即可。
下面的代码演示了如何用这个类来载入一个大尺寸图片并显示在
ImageView 中:

class BitmapWorkerTask extends AsyncTask {    private final WeakReference imageViewReference;    private int data = 0;    public BitmapWorkerTask(ImageView imageView) {        // Use a WeakReference to ensure the ImageView can be garbage collected        imageViewReference = new WeakReference(imageView);    }    // Decode image in background.    @Override    protected Bitmap doInBackground(Integer... params) {        data = params[0];        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));    }    // Once complete, see if ImageView is still around and set bitmap.    @Override    protected void onPostExecute(Bitmap bitmap) {        if (imageViewReference != null && bitmap != null) {            final ImageView imageView = imageViewReference.get();            if (imageView != null) {                imageView.setImageBitmap(bitmap);            }        }    }}

引用到
ImageView
的 WeakReference
确保
AsyncTask
不会直接引用 ImageView 从而导致其无法被垃圾回收。
当解析完图片后,无法确保 ImageView 是否还存在,所以需要在 onPostExecute()
函数中检查下,如果用户在图片加载期间离开了当前界面,则当图片加载完后用来显示图片的 ImageView可能已经不存在了。

只要创建该Task并执行即可开始异步加载图片了:

public void loadBitmap(int resId, ImageView imageView) {    BitmapWorkerTask task = new BitmapWorkerTask(imageView);    task.execute(resId);}

处理并发访问

像 ListView 和 GridView
这种控件使用上面介绍的方式来载入图片可能会引入新的问题。为了提高内存的使用率,当用户做滚动操作的时候这些控件会重复利用子控件,如果每个子控件都触发一个AsyncTask,当任务完成的时候 无法保证该子控件是否已经被重用了。甚至,这些任务开始的顺序和完成的顺序也是不一样的。

这篇博文Multithreading for Performance 进一步
讨论了如何处理并发操作,并且提供了一个解决方案:
ImageView 中保存了最近触发的AsyncTask对象,当任务完成的时候可以用来检测该引用的对象。
使用相似的函数,在前面介绍的AsyncTask对象可以使用相似的模式来扩展。

创建一个特殊的 Drawable 子类来保存载入图片的Task引用。
这里使用 BitmapDrawable 类,这样当任务完成的时候可以直接在
ImageView 中显示:

static class AsyncDrawable extends BitmapDrawable {    private final WeakReference bitmapWorkerTaskReference;    public AsyncDrawable(Resources res, Bitmap bitmap,            BitmapWorkerTask bitmapWorkerTask) {        super(res, bitmap);        bitmapWorkerTaskReference =            new WeakReference(bitmapWorkerTask);    }    public BitmapWorkerTask getBitmapWorkerTask() {        return bitmapWorkerTaskReference.get();    }}

在执行 BitmapWorkerTask之前,您需要创建一个
AsyncDrawable 并且绑定到需要显示该图片的那个 ImageView中:

public void loadBitmap(int resId, ImageView imageView) {    if (cancelPotentialWork(resId, imageView)) {        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);        final AsyncDrawable asyncDrawable =                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);        imageView.setImageDrawable(asyncDrawable);        task.execute(resId);    }}

上面代码中的 cancelPotentialWork 函数用来检测是否已经有一个Task绑定到 ImageView了。
如果已经有个Task了就尝试取消这个Task(调用 cancel()函数)。
在一些情况下,如果新的任务和已经存在任务的数据一样,则不需要额外的处理。下面是 cancelPotentialWork 函数的一种实现方式:

public static boolean cancelPotentialWork(int data, ImageView imageView) {    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);    if (bitmapWorkerTask != null) {        final int bitmapData = bitmapWorkerTask.data;        if (bitmapData != data) {            // Cancel previous task            bitmapWorkerTask.cancel(true);        } else {            // The same work is already in progress            return false;        }    }    // No task associated with the ImageView, or an existing task was cancelled    return true;}

上面用到的助手函数 getBitmapWorkerTask(),用来获取和 ImageView关联的任务:

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {   if (imageView != null) {       final Drawable drawable = imageView.getDrawable();       if (drawable instanceof AsyncDrawable) {           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;           return asyncDrawable.getBitmapWorkerTask();       }    }    return null;}

最后,需要更新 BitmapWorkerTask类的 onPostExecute() 函数。
用来检测任务是否取消了,以及当前的任务和 ImageView引用的任务是否为同一个任务:

class BitmapWorkerTask extends AsyncTask {    ...    @Override    protected void onPostExecute(Bitmap bitmap) {        if (isCancelled()) {            bitmap = null;        }         if (imageViewReference != null && bitmap != null) {            final ImageView imageView = imageViewReference.get();            final BitmapWorkerTask bitmapWorkerTask =                    getBitmapWorkerTask(imageView);            if (this == bitmapWorkerTask && imageView != null) {                imageView.setImageBitmap(bitmap);            }        }    }}

这样就可以在 ListView 和 GridView
控件中使用该图片加载任务了。只需要在您设置
ImageView图片的地方调用
loadBitmap 函数即可。
例如:在 GridView 中可能需要在Adapter中的 getView() 函数中调用
loadBitmap 函数。


Read more: http://blog.chengyunfeng.com/?p=394#ixzz2Ww5pPAPc

Read more: http://blog.chengyunfeng.com/?p=394#ixzz2Ww5wXqK
J