• Android中高效的显示图片之二——在非UI线程中处理图片


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

    一、使用一个AsyncTask

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

    1. class BitmapWorkerTask extends AsyncTask {  
    2.     private final WeakReference imageViewReference;  
    3.     private int data = 0;  
    4.   
    5.     public BitmapWorkerTask(ImageView imageView) {  
    6.         //使用弱引用保证ImageView可以被正常回收  
    7.         imageViewReference = new WeakReference(imageView);  
    8.     }  
    9.   
    10.     // 在后台加载图片  
    11.     @Override  
    12.     protected Bitmap doInBackground(Integer... params) {  
    13.         data = params[0];  
    14.         return decodeSampledBitmapFromResource(getResources(), data, 100, 100));  
    15.     }  
    16.   
    17.     // 一旦完成,如果ImageView还存在,将图片设置给它  
    18.     @Override  
    19.     protected void onPostExecute(Bitmap bitmap) {  
    20.         if (imageViewReference != null && bitmap != null) {  
    21.             final ImageView imageView = imageViewReference.get();  
    22.             if (imageView != null) {  
    23.                 imageView.setImageBitmap(bitmap);  
    24.             }  
    25.         }  
    26.     }  
    27. }  


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

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

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


    二、处理并发

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

        创建一个专用的Drawable的子类用来保存任务的引用,这里使用了一个BitmapDrawable,因此当任务完成时占位图片可以在ImageView中显示。

    1. static class AsyncDrawable extends BitmapDrawable {  
    2.     private final WeakReference bitmapWorkerTaskReference;  
    3.   
    4.     public AsyncDrawable(Resources res, Bitmap bitmap,  
    5.             BitmapWorkerTask bitmapWorkerTask) {  
    6.         super(res, bitmap);  
    7.         bitmapWorkerTaskReference =  
    8.             new WeakReference(bitmapWorkerTask);  
    9.     }  
    10.   
    11.     public BitmapWorkerTask getBitmapWorkerTask() {  
    12.         return bitmapWorkerTaskReference.get();  
    13.     }  
    14. }  


    在执行BitmapWorkerTask之前,创建一个AsyncDrawable并且把它绑定到目标ImageView上:

    1. public void loadBitmap(int resId, ImageView imageView) {  
    2.     if (cancelPotentialWork(resId, imageView)) {  
    3.         final BitmapWorkerTask task = new BitmapWorkerTask(imageView);  
    4.         final AsyncDrawable asyncDrawable =  
    5.                 new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);  
    6.         imageView.setImageDrawable(asyncDrawable);  
    7.         task.execute(resId);  
    8.     }  
    9. }  

    上面代码中的cancelPotentialWork方法用来检查是否已经有另外一个与ImageView关联的任务正在运行。如果有,通过调用cancel()方法取消之前的任务。有一定概率,新任务的数据与已存在的一致,此时没有必要再做其他工作,下面是方法的实现:

    1. public static boolean cancelPotentialWork(int data, ImageView imageView) {  
    2.     final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);  
    3.   
    4.     if (bitmapWorkerTask != null) {  
    5.         final int bitmapData = bitmapWorkerTask.data;  
    6.         if (bitmapData != data) {  
    7.             // Cancel previous task  
    8.             bitmapWorkerTask.cancel(true);  
    9.         } else {  
    10.             // The same work is already in progress  
    11.             return false;  
    12.         }  
    13.     }  
    14.     // No task associated with the ImageView, or an existing task was cancelled  
    15.     return true;  
    16. }  


    上面代码使用的一个helper方法getBitmapWorkerTask(),用来获取于特定ImageView相关联的任务:

    1. private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {  
    2.    if (imageView != null) {  
    3.        final Drawable drawable = imageView.getDrawable();  
    4.        if (drawable instanceof AsyncDrawable) {  
    5.            final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;  
    6.            return asyncDrawable.getBitmapWorkerTask();  
    7.        }  
    8.     }  
    9.     return null;  
    10. }  


    最后一步是更新BitmapWorkerTask中的onPostExecute()方法,这样它可以检查任务是否被取消或者当前任务是否与ImageView关联:

    1. class BitmapWorkerTask extends AsyncTask {  
    2.     ...  
    3.   
    4.     @Override  
    5.     protected void onPostExecute(Bitmap bitmap) {  
    6.         if (isCancelled()) {  
    7.             bitmap = null;  
    8.         }  
    9.   
    10.         if (imageViewReference != null && bitmap != null) {  
    11.             final ImageView imageView = imageViewReference.get();  
    12.             final BitmapWorkerTask bitmapWorkerTask =  
    13.                     getBitmapWorkerTask(imageView);  
    14.             if (this == bitmapWorkerTask && imageView != null) {  
    15.                 imageView.setImageBitmap(bitmap);  
    16.             }  
    17.         }  
    18.     }  
    19. }  


    这样下来就可以适用于ListView和GridView及其他任何重复利用子视图的控件。只需要在你通常给ImageView设置图片的地方调用loadBitmap方法即可。

  • 相关阅读:
    删除 SQL Server 2005 Express 工具
    静态和非静态
    C#中的托管和非托管
    类和结构的区别
    asp.net URL DES加密 什在URL中的使用
    正则替换图片路径
    Oracle 正则 一行转多行
    Oracle 存储过程
    HTTP SOAP Request
    jquery 高亮
  • 原文地址:https://www.cnblogs.com/chengzhengfu/p/4579268.html
Copyright © 2020-2023  润新知