在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘、网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行。因为它的加载时间不可预测且依赖于一系列因素(磁盘读写速度、图片大小、CPU频率等)。如果在主线程中执行这个操作,一旦它阻塞了主线程,就会导致系统ANR。本节介绍使用AsyncTask在后台处理图片和演示怎么处理并发问题。

一、使用一个AsyncTask

AsyncTask类提供一个简易的方法在后台线程中执行一些任务并把结果发布到UI线程。使用它只需要创建一个它的子类并重写它的几个方法即可。下面是一个使用AsyncTask和decodeSampleBitmapFromResource加载大图到ImageView的例子:


class BitmapWorkerTask extends AsyncTask {    private final WeakReference imageViewReference;    private int data = 0;    public BitmapWorkerTask(ImageView imageView) {        //使用弱引用保证ImageView可以被正常回收        imageViewReference = new WeakReference(imageView);    }    // 在后台加载图片    @Override    protected Bitmap doInBackground(Integer... params) {        data = params[0];        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));    }    // 一旦完成,如果ImageView还存在,将图片设置给它    @Override    protected void onPostExecute(Bitmap bitmap) {        if (imageViewReference != null && bitmap != null) {            final ImageView imageView = imageViewReference.get();            if (imageView != null) {                imageView.setImageBitmap(bitmap);            }        }    }}

对ImageView使用弱引用可以确保AsyncTask不会阻止ImageView和它的任何引用被垃圾回收器回收。因为不能保证当任务完成后ImageView还存在,所以在onPostExecute()方法中需要对引用进行检查。

想要异步加载图片,只需要创建一个任务并执行它:

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

二、处理并发

上面讲述的使用AsyncTask的方法加载图片,对于有些控件例如ListView和GridView来说会产生新的问题。为了优化内存使用,当用户拖动时这些控件重复使用它的子视图。如果每个子视图触发一个AsyncTask,无法确保当它在后台执行完毕后,与它相关联的子视图没有被其它子视图重复使用。另外,也无法保证这些任务开始的顺序与它们完成的顺序一致。下面的方法可以解决这个问题:

创建一个专用的Drawable的子类用来保存任务的引用,这里使用了一个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方法用来检查是否已经有另外一个与ImageView关联的任务正在运行。如果有,通过调用cancel()方法取消之前的任务。有一定概率,新任务的数据与已存在的一致,此时没有必要再做其他工作,下面是方法的实现:


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

上面代码使用的一个helper方法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方法即可。

更多相关文章

  1. 【Android语音合成TTS】百度语音接入方法,和使用技巧详解
  2. Android Studio中新建assets文件的两种方法
  3. 多态在android中(利用接口调用服务中方法)的应用
  4. 什么是Android静默拍摄 Android静默拍摄app制作方法
  5. Eclipse在线升级Android ADT和SDK(4.x)方法介绍
  6. Android中的WebView进行直接加载网页

随机推荐

  1. SE75 采购申请创建抬头文本
  2. (BADI)Copy PR header text to PO header w
  3. Submit report 很实用FM:RS_REFRESH_FROM_
  4. 修改盘点数量MI04过账
  5. Dialog屏幕调用选择屏幕
  6. 创建采购申请(BAPI_REQUISITION_CREATE /
  7. (EXIT)Copy PR header text to PO header w
  8. 选择屏幕折叠功能
  9. PO text copy rules :copy PR item text t
  10. Checkpoint group (transaction code: SA