创建带有动画的APP 之 高效的显示位图:在UI线程之外处理位图

在我们上一节讨论的课程里面,BitmapFactory.decode*这个方法,是从磁盘或者网络(或者任何其他不是从内存读取的位置)读取位图资源的时候,不可以在主UI线程中进行。载入数据的花费的时间是不可预测的,这个耗时和很多因素有关系,如从磁盘或者网络读取的数据的速度,图像的大小,CPU的执行速度等等因素。如果一旦解码图像的任务在主UI线程里面阻塞了,系统就会把你的APP标示为无响应,会提示用户去关闭这个程序。想了解更多,阅读:Designing for Responsiveness

这个章节将引导力利用AsyncTask来后台处理图片操作以及如果处理并发的问题。

使用AsyncTask

AsyncTask这个类提供一种简单的途径,可以让一些工作在后台运行,运行完成后在把后台运行的结果返回给主UI线程。使用这个类的方式是我们要实现一个这个类的子类,然后重写里面的一些方法。下面例子就是使用AsyncTaskdecodeSampledBitmapFromResource()方法来载入一张大的图片:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

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


 WeakReference 引用到 ImageView保证了AsyncTask不会阻碍ImageView以及任何它的相关信息都不会被垃圾回收。没有办法保证在这个AsyncTask结束的时候,ImageView还是存在的,所以你必须在onPostExecute()里面检查这一项。ImageView也许不会一直存在,例如,用户跳转到另外一个页面,或者是用户界面在这个任务结束之前发生了改变。要载入一个bitmap,只需要简单的创建这个任务,并且执行它:

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


处理并发

一般的视图组件,如ListViewGridView当和AsyncTask关联起来,如上面的例子中使用的时候,会产生一些其他的问题。为了高效的利用内存,这些组件的子视图在用户进行滚动操作时候,会被回收利用。如果每一个子视图都触发一个AsyncTask,那么就无法保证,当这个执行完成的时候,这个子视图没有被系统回收作为另外一个视图的子视图来使用。此外,也无法保证异步启动任务在完成的时候的顺序也是按照他们启动的顺序完成的。

文章将进一步讨论并发的处理,并提供一种解决方法在与之关联,在AsyncTask在任务完成的时候,对与之关联存放ImageView的位置进行检查。使用类似的方法,我们把上面的代码扩展一下,得到下面的代码段。

创建一个专用的Drawable的子类,来存放工作任务返回时候的信息。在这个例子里面, 使用了一个BitmapDrawable,这样在任务完成的时候,可以在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(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.如果有其他的任务在使用这个ImageView,它会努力去尝试调用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<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 and GridView组件,以及其他的会遇到子视图被回收的组件。当你需要给你的ImageView设置一张图片的时候,那么简单的调用loadBitmap就可以了。

比如,在一个GridView里面,这个调用要在它的适配器的getView() 方法里面。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值