Android 有效地展示图片(二)Processing Bitmaps Off the UI Thread 在ui线程外处理bitmap

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

我们在上节课讨论了BitmapFactory.decode系列的方法,但是如果原图的数据需要从硬盘或者网络或者别的途径而非内存读取时,那么这个方法就不应该放在UI线程里执行,因为读取该图片的时间收到多方面影响,(如:网速或者硬盘读取速度的大小,图片的大小,cpu的速度大小等等)。如果这些因素中的任何一个堵住了UI线程,那么你的程序就会出现“无反应”,用户也就可以关闭它。

这节课我们将学习怎样在后台用AsyncTask来处理图片并教你如何处理并发事件。

Use an AsyncTask 用AsyncTask类

AsyncTask类可以方便的实现在后台线程中处理一些工作并把结果传回UI线程。要用此类,我们需要创建该类的子类并复写其中的方法。下面就是一个用AsyncTask 和 decodeSampledBitmapFromResource()来下载图片到一个ImageView的例子。

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



在AsyncTask执行时,ImagaeView有可能会被垃圾回收机制回收,为了不阻碍垃圾回收机制,所以就把ImageView放
入了软引用。这样的话当任务执行完毕时,ImageView 可能就不在那里了,所以你必须在onPostExecute()方法中检查
该引用。有时在任务完成之前,比如当用户已经离开activity或者有别的变化,ImageView 就可能不存在了。
异步的加载图片通常另起一个任务执行:

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

 

处理并发

普通的视图组件如 ListViewGridView 在和上述的AsyncTask联合使用时带来了其他的问题。为了有效利用内存,当用户滚动屏幕时,这些组件都是在循环利用他们的子View.如果每个子View触发一个AsyncTask,那么当该AsyncTask完成时,谁都没法保证当初触发它的那个View没有被重复利用去绘制另一个子View。况且,这些AsyncTask开始的顺序和它们完成的顺序并不一定一致。
创建一个专门的Drawable类存储AsyncTask的引用。在此例中,用的是一个BitmapDrawable类,这样的话,在任务完成时一个占位的图片就会显示在ImageView上。
private 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绑定,暂时先不用理会if条件
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);
    }
}
然后我们在task执行 onPostExecute方法时不仅要判断ImageView相对应,还要判断这个imageview绑定的task是不是当前的task
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;
}

上述方法是得到image view绑定的task方法。那么我们就要更正Task中的onPostExecute方法如下
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);
            }
        }
    }
}
ok,现在我们在回看上面loadbitmap方法中的 if条件, if判断的是这样一个方法

 
  
 
 

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

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // 如果bitmap还没有被设置或者bitmapdata数据和正在执行的data的不一样
        if (bitmapData == 0 || bitmapData != data) {
            // 那么就把之前的task取消掉
            bitmapWorkerTask.cancel(true);
        } else {
            //如果一样那么就是说当前的task就是image的task 就无需重新绑定了
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}


上面方法就是说在listview中的一条imageView准备发起task时,首先检查imageview有没有绑定的另外的task,如果有绑定了,那么就应该先取消以前的,在执行当前的,如果绑定的就是当前的那么就不用取消了。这就是为什么我们还要在上述onPostExecte方法中加上如下代码


protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }



}




这样的实现现在就适合ListView控件和GridView控件,也适合与其他的循环利用子view的控件,通常在将图片设置到ImageView上的时候调用loadBitmap,比如,在GridView中,就应该在getView()方法中调用此方法。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值