Android ListView和GridView异步加载图片

来源:互联网 发布:时间穿梭机 淘宝 编辑:程序博客网 时间:2024/06/05 08:56

Android ListView和GridView异步加载图片

一般大家在加载图片的时候都是从网络或者SD卡硬盘上,这种操作是耗时操作,是不能在UI线程进行的所以需要异步加载图片,这样我们就会用到Android的异步任务AsyncTask类。不会使用这个的可以去网上百度。

但是当我们使用ListView的时候,加载Item里面的图片就会出问题了。因为ListView的item是随着我们手指滑动,item的在屏幕上的显示和消失而回收利用。举个例子:加入现在我们的屏幕最多可以显示8个listview,当我们向上滑动的时候,第一个Item会消失,这个item不会被垃圾回收器回收,而是被用来显示第九个Item(这样我们就可以减少创建Item对象的开销),当第一个图片出现的时候会打开一个异步任务我们暂且就叫他TASK1,在TASK1还未加载完成的时候我们将ListView向下滑这个时候Item1消失被用来显示Item9,这个时候又启动了一个任务TASK9,这两个Item使用的是同一个View对象那么就会同时有两个TASK在操纵这个对象。这样就有可能在Item9上显示Item1的图片,在Item1上显示Item9的图片。也就是我们开发中可能遇到的ListView图片错乱。

针对这个问题,官方文档给了一种解决办法大致就是下面这样:

Created with Raphaël 2.1.0Item显示将Item的ImageView和Task绑定,并开始进行异步加载用户手指滑动使得旧的Item划出屏幕新的Item进入屏幕检查新的Item所使用的View是否已经开启了一个Task如果已经开启,关闭旧的Task,开启新的Task并和Item的Imageview绑定End

这样就不会出现ListView图片乱序的问题了。
下面我们看一下官方文档给出的代码(删去了一些关系不大的代码)。

class BitmapWorkerTask extends AsyncTask<URL, Void, Bitmap> {private final WeakReference<ImageView> imageViewReference;private URL data = null;public BitmapWorkerTask(ImageView imageView) {    imageViewReference = new WeakReference<ImageView>(imageView);}@Overrideprotected Bitmap doInBackground(URL... params) {    data = params[0];    return ImageUtils.getBitmap(data);}@Overrideprotected void onPostExecute(Bitmap bitmap) {    if (imageViewReference != null && bitmap != null) {        final ImageView imageView = imageViewReference.get();        if (imageView != null) {            imageView.setImageBitmap(bitmap);        }    }}}

这段代码是一个异步加载图片的任务。

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

这里复写了一个Drawable类AsyncDrawable,我们可以在它的构造函数里面传入异步加载任务bitmapWorkerTask。

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

这里new出来一个BitmapWorkerTask,然后把BitmapWorkerTask放到上面的AsyncDrawable对象里面然后调用imageView.setImageDrawable(asyncDrawable);这样ImageView就和BitmapWorkerTask绑定到一起了,然后开始执行Task也就是开始加载图片。

public static boolean cancelPotentialWork(int data, ImageView imageView) {final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);if (bitmapWorkerTask != null) {    final URL bitmapData = bitmapWorkerTask.data;     if (bitmapData == 0 || bitmapData != data) {        bitmapWorkerTask.cancel(true);    } else {        return false;    }}return true;}

这段代码是上面那段代码的一函数,如果现在已经有了一个Task并且和我们将要执行的那个Task不一样的话,我们就取消原来的Task。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...@Overrideprotected 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);        }    }}}

最后在Task执行完毕之后,在onPostExecute里面得到Bitmap并且设置到imageView里面就好了。如果看不懂的话就再看几遍上面的流程图,流程图就是全部过程

最后总结一下:把View比作一个人的话,Item1就是他白天的职业——软件工程师,Item9就是他晚上的职业——出租车司机。白天的时候只敲代码(Task1),晚上的时候他就是出租车司机了,脑子里不去想代码(关闭Task1)开始集中精力开车(开启Task9)。这样他就不会感到职业混乱啦~~你是不是也明白了呢 ^_^。

0 0
原创粉丝点击