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

在 高效的加载大尺寸图片 中介绍的 用来解析图片的 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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值