Android进阶练习 - 高效显示Bitmap(在UI主线程外处理Bitmap)

在UI主线程外处理Bitmap

     BitmapFactory.decode*  系列的方法,讨论的是怎么样去高效的加载大图片,但是不管图片数据的来源,这些方法都不应该在UI主线程上使用。因为这些方法耗费的时间是不可预估的,图片加载耗费的时间依赖于很多的因素(网络或硬盘的读写速度,图片的大小,手机CPU的处理能力等等)。在程序中,只要其中一个图片加载任务阻塞了UI主线程,那么你的应用将会无响应(ANR,不能再与用户进行交互了),Android系统会显示通知用户,用户将会选择关闭你的应用,这是一项非常不好的用户体验。

     出于这种原因,你可以使用    AsyncTask 来在UI主线程外的线程中处理图片加载,但还有一个问题,要妥善处理好并发的问题,下面将介绍这两种问题的处理方法

使用AsyncTask

         AsyncTask  类为我们提供了一种很好的方式来在UI主线程之外的线程中执行一些任务,并且把任务的结果返回到UI主线程上。你需要继承 AsyncTask  类并重载一些提供的方法来使用这种异步任务方式,下面是一个示例程序 
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.
    // 此方法运行在UI主线程上
    @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  引用的对象。不能保证当任务完成的时候 ImageView  引用的对象没有被回收,所以呢你必须在 onPostExecute()  方法中进行检查是否为null。 ImageView  对象可能会不存在了,例如,在一个任务结束前,用户可能已经离开了当前的用户界面(Activity被destroy)或者一个配置项(例如横竖屏切换)发生改变 ,所以在使用的时候必须对 ImageView  对象做必要的检测。  

     你可以直接创建一个 AsyncTask  对象并调用execute()方法来异步加载图片
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

处理并发问题

      像 ListView和 GridView 这种常见的视图组件,结合 AsyncTask  一起使用的时候会产生另外一个问题。为了尽可能的提高内存的使用效率,当用户滚动组件时,这类视图组件会循环利用它的子视图,如果每个子视图都触发一个   AsyncTask ,那么将不会有保证相关联的视图是否已经回收供另外一个子视图循环使用了。而且,异步任务的执行顺序并不意味着任务的执行结束时的顺序也是这样的。结合这两种原因,引发的这个并发问题有可能会导致图片设置到子视图上时会发生错位。

      针对这种并发、错位问题,我们可以让 ImageView 对象存储一个最近使用的 AsyncTask 的引用,这样我们可以去检测任务是否完成

     创建一个专用的 Drawable 子类来存储一个与之对应的 AsyncTask (这个 AsyncTask 实际上是用来加载 Drawable 的任务),当图片加载任务还在执行时,就可以使用这个 Drawable 来显示一张默认的图片了,当任务完成时这个 Drawable随之被替换

//存储着一个与之对应的task, 在任务还在执行时充当一个占位符,当任务完成时,随之被替换
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();
    }
}

    在执行任务之前,先设置占位符, ImageView  将显示默认图片
public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        // mPlaceHolderBitmap 引用的就是默认要显示的图片对象
        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) {
    // 取得当前与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()  方法的定义
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   // imageView有可能被回收
   if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}     

     现在在   onPostExecute()  方法中我们就需要检测任务是否被取消了,还有需要检测下当前的任务是否是与imageView相关联的任务 
class BitmapWorkerTask extends AsyncTask {
    ...

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        // if task cancelled , this method is never invoked
        // why check here ? 
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            // 判断当前task是否是相关联的task
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

     前面的ImageView加载的实现方式适用于像   ListView  and  GridView  这类的UI视图组件,我们可以在Adapter中的 getView()    方法中调用 loadBitmap  方法 







你可能感兴趣的:(android)