[Developer Android] Processing Bitmaps Off the UI Thread

原文链接:https://developer.android.com/training/displaying-bitmaps/process-bitmap.html

慢慢积累Google官方的文档,以后方便快速查阅。

不要在UI线程中处理位图

位图的加载时间因种种因素的影响(磁盘读取的速度,网络图片获取的速度,图片的大小,CPU等)是不可预期的。如果主线程获取位图的地方被堵塞,那APP将出现ANR的状况导致用户不得不关掉APP。

使用AsyncTask

---------------------------------------------------------------------------------------

AsyncTask提供简单的方式去执行子线程并且将运行结果返回给UI线程。自定义自己的子类并重写AsyncTask的方法就可以使用它了。下面通过使用AsyncTask加载一张大图到ImageView中作为例子:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private String picUrl;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(String... params) {
        picUrl = params[0];
        return decodeSampledBitmapFromResource(getResources(), picUrl, 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使用弱引用是为了 不妨碍该对象引用的回收。因为AsyncTask的任务执行结束后,并不能保证ImageView的引用还存在(比如用户退出了当前的Activity界面),所以你必须在onPostExecute()方法中检查ImageView是否为空。注意:ImageView可能因为用户在AsyncTask加载图片的任务完成之前就离开了当前的Activity或者一些其他配置的改变(比如横竖屏幕的切换导致视图的重绘)而出现空的情况。

接下来就是通过简单的使用上面的BitmapWorkerTask类来实现异步加载Bitmap对象。

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

处理并发

---------------------------------------------------------------------------------------

使用上述的BitmapWorkerTask对象来为ListView、GridView控件显示图片会出现另外一种情况。下面开始介绍:为了更高的内存使用效益,当用户滚动ListView、GridView的时候一般会回收复用子孩子视图(ListView的子视图)。如果每个子视图都使用AsyncTask(并发加载位图)加载视图,则无法保证每个AsyncTask在任务完成时所关联的视图不会被回收。而且,也无法保证异步AsyncTask的完成顺序与启动的顺序一致。

Multithreading For Performance 这篇博客(作者是Android Group的开发成员之一)就讨论了处理并发显示位图的问题,它提供了一种解决方案:通过使用ImageView占位图来存储最新的AsyncTask
引用来保证最后一个AsyncTask的完成。我们使用同样的BitmapWorkerTask来继续下文。

为了更加理解下文,摘抄博客的一段,个人感觉最好看看大神的博客的原文,才能更全面更好的理解(这段是我另外添加的,非官方插文):

Handling concurrency

To solve this issue, we should remember the order of the downloads, so that the last started one is the one that will 

effectively be displayed. It is indeed sufficient for each ImageView to remember its last download. We will add this extra 

information in the ImageView using a dedicated Drawable subclass, which will be temporarily bind to the ImageView while 

the download is in progress. Here is the code of our DownloadedDrawable class:

作者所提出的方案是,记录下载位图任务的顺序,保证最后一个位图下载任务能有效的显示,如何记录呢?通过添加一个自定义的Drawable子类来作为位图下载时,ImageView的临时占位图(因为ListView快速滚动的时候,被复用的子视图会不断的提交AsyncTask下载位图任务,因此作者就给用这种方法给ImageView设置占位图(Drawable的子类),再根据占位图的url来判断是否当前子视图已有AsyncTask加载任务,有就取消前一个加载任务,并使用最后一个提交的AsyncTask任务,关于这个方法的实现,看下面cancelPotentialWork()方法的实现)。

继续译文......


创建一个自定义的Drawable子类来储存自定义AsyncTask的引用,这种情况,比如下面的代码,AsyncDrawable其实就相当于在Task完成前ImageView显示的一个图片占位符。

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}
在执行BitmapWorkerTask之前,创建一个AsyncDrawable并将其绑定到对应的ImageView。
public void loadBitmap(String picUrl, ImageView imageView) {
    if (cancelPotentialWork(picUrl, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(picUrl);
    }
}
cancelPotentialWork():检查子视图的ImageView是否已经有另外一个正在运行的BitmapWorkerTask关联,如果是,它将通过 cancle()方法去取消前一个AsyncTask任务。

public static boolean cancelPotentialWork(String picUrl, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapDataUrl = bitmapWorkerTask.data;
        // If bitmapData is not yet set or it differs from the new data
        if (picUrl == null || bitmapDataUrl != picUrl) {
            // 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;
}
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;
}
最后一步需要在   onPostExecute() 检测是否任务被取消,并检测当前的任务是否最新关联到ImageView的任务。
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...

    @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以及任何会回收复用子视图的控件,都可以简单的调用 loadBitmap 方法来设置ImageView中的图片。例如在GridView设置的Adapter中,通过getView调用loadBitmap就可以实现子视图图片的设置了。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值