Android如何高效的加载图片(2)---在ui线程中处理Bitmaps

来源:互联网 发布:新概念英语网络课程 编辑:程序博客网 时间:2024/05/21 07:38

前面我们总结了Androd中如何去加载尺寸比较大的图片。现在我们需要考虑的问题是当我们从磁盘或者网络中加载图片时,由于磁盘的读取速度或者网速的原因导致话费很长的时间去加载。如果吧这些耗时的代码放在ui线程,会导致ANR异常
所以。这篇文章中,我们将讨论使用AsyncTask在后台线程中去加载图片,并且最后将会教你如果处理并发问题

使用AsyncTask

AsyncTask类提供了一些的方法在后台线程执行一些耗时操作,并且把最终的执行结果发布到ui线程。
AsyncTask使用步骤,首先,创建一个类去继承AsyncTask。然后重写它的一些方法,下面这断代码使用了AsyncTaskdecodeSampledBitmapFromResource()(注:此方法为以合适的缩放比加载bitmap图片,具体请看上一节Android中Bitmaps 处理详解(1)中此方法的创建过程)把一张尺寸较大的图片加载进ImageView。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {    private final WeakReference<ImageView> imageViewReference;    private int data = 0;    public BitmapWorkerTask(ImageView imageView) {        // 使用软引用是为了确保ImageView可以被及时的回收        imageViewReference = new WeakReference<ImageView>(imageView);    }    // 在后台线程中获取图片并转为bitmap.    @Override    protected Bitmap doInBackground(Integer... params) {        data = params[0];        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));    }    // 获取到bitmap后,判断如果ImageView没有被回收,则吧图片加载进ImageView    @Override    protected void onPostExecute(Bitmap bitmap) {        if (imageViewReference != null && bitmap != null) {            final ImageView imageView = imageViewReference.get();            if (imageView != null) {                imageView.setImageBitmap(bitmap);            }        }    }}

在上面的代码中,对ImageView使用了软引用WeakReference,目的是为了确保AsyncTask不会阻止ImageView和ImageView持有的其他引用被及时回收。所以在后台执行完任务后,我们不确定ImageView是否被回收,所以在onPostExecute()方法中判断ImageView是否为null, 比如,如果用户在AsyncTask执行结束前关闭了Activity界面。

通过以上封装,我们在后台线程中加载图片可以简答的用下面的代码就可完成:

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

处理并发问题

常见的视图组件,如ListView和GridView,在使用前面AsyncTask操作时面对同样的问题就是,为了消耗内存,这些组件在滚动的时候会去循环利用他们的子View,如果每个子View都执行了AsyncTask,所以我们不能确定,某个item执行了AsyncTask,当Async执行完毕后,该item是否还存在,同样,我们也不能保证AsyncTask的启动顺序就是AsyncTask的完成顺序。
有国外大牛给出了解决方法,用ImageView存储最近的AsyncTask引用(如果这个现在不好理解,可窘继续往下看)。
首先,创建一个Drawable的子类来存储对任务(即AsyncTask)的引用,在本例中,使用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();    }}

我们看到上面代码中,先不管它的父类BitmapDrawable和弱引用WeakReference;我们简化上面的代码就是:

    static class AsyncDrawable {        BitmapWorkerTask bitmapWorkerTaskReference;        public AsyncDrawable( BitmapWorkerTask bitmapWorkerTask) {            bitmapWorkerTaskReference = bitmapWorkerTask;        }        public BitmapWorkerTask getBitmapWorkerTask() {            return bitmapWorkerTaskReference;        }    }

通过简化的代码,我们知道,上步我们只是创建了一个类,然后在类中创建一个成员变量BitmapWorkerTask,然后添加set/get方法来设置获取BitmapWorkerTask。

回到我们的问题,我们前面说过,要把任务与ImageView绑定到一起,即把
BitmapWorkerTask和ImageView绑定到一起,测试我们想到的ImageView有setTag()和getTag()方法,可以完成,当然,这也是一种思路。
现在国外大牛提供了一种更巧妙的解决方法就是把通过ImageView的setImageDrawable()方法和getDrawable()方法来进行绑定,这样绑定的好处因为他的参数是Drawable对象,我们可以通过在Drawable的子类当中来设置任务,然后当任务执行前,ImageView就去加载Drawable子类所对应的图片,任务执行结束后,ImageView已经加载了需要去加载的图片。这样的Drawable就相当于一个placeholder占位图 ,实现了ImageView对任务的绑定。

所以我们上面类 需要去继承BitmapDrawable类。而用软引用的作用前面已经说过了,这里再不做重复说明了。

这会儿我们看看这个并发问题中处理加载图片的最终代码:

public void loadBitmap(int resId, ImageView imageView) {    if (cancelPotentialWork(resId, imageView)) {        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);        // 创建AsyncDrawable对象        final AsyncDrawable asyncDrawable =                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);        // 绑定ImageView与任务        imageView.setImageDrawable(asyncDrawable);        task.execute(resId);    }}

我们看到,在执行任务前,我们首先调用了cancelPotentialWork(resId, imageView)方法,那么这个方法是干什么用的呢?
下面为cancelPotentialWork(resId, imageView)方法的代码:

public static boolean cancelPotentialWork(int data, ImageView imageView) {    // 获取当前ImageView绑定的任务    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);    if (bitmapWorkerTask != null) {        // 获取到任务执行的资源Id        final int bitmapData = bitmapWorkerTask.data;        // 判断id是否被设置过,跟当前的id是否相同        if (bitmapData == 0 || bitmapData != data) {            // 如果不相同,退出任务            bitmapWorkerTask.cancel(true);        } else {            // 如果相同,则说明当前的任务正在运行。            return false;        }    }     // 当前的ImageView没有绑定任务,或者任务已经运行完成    return true;}

我们看到上面代码中有个方法,getBitmapWorkerTask(imageView),这个方法就是获取传入的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;}

下面来解释一下代码:
首先,我们会调用loadBitmap(int resId, ImageView imageView)传入要加载的图片的资源ID,和图片显示的控件去加载图片, 先去获取控件ImageView所绑定的加载任务,判断当前ImageView所绑定的任务(如果绑定了任务)中加载的图片资源Id是否与当前即将要被加载的图片资源ID相同,如果不同,则退出当前ImageView所绑定的任务,重新开始新的任务去加载当前传入的资源id。
如果当前的资源id与ImageView所绑定的任务正在加载的资源ID相同,则让它继续执行任务,不去干预。

最后我们重新去修改 BitmapWorkerTask中的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);            }        }    }}

修改的代码主要作用是检查当前的任务是否被取消,和当前任务是否是ImageView控件所绑定的任务。

经过以上的封装,我们就可以在诸如ListView、GridView和其他任何复用子View的控件去加载图片。

请继续阅读下篇Android如何高效的加载图片(3)— 图片的缓存

阅读全文
0 0
原创粉丝点击