上节课我们介绍了如何加载和显示大图,这节课我们就要把这个技巧与实际开发联系起来,在实际的开发过程中,最常见的场景就是用ListView,GridView等集合显示控件

来呈现图片,这节课,我们就要用这些控件来高效的显示图片。

       实际的使用环境中,如果图片来源是SD卡或者网络,那那么加载图片的过程一定不要放在UI线程中,这样会严重的阻塞UI线程,出现ANR,程序就废了。因此我们首先要实现异步加载。

第一步:利用AsyncTask实现图片的异步加载

将decodeSampledBitmapFromResource方法放入Task的doInBackground中后台执行。不熟悉AsyncTask的同学可以学习AsyncTask的相关知识,这里不再过多介绍。

代码:


1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
2. <ImageView> imageViewReference;  
3. data = 0;  
4.   
5.   
6.     public BitmapWorkerTask(ImageView imageView) {  
7.         // Use a WeakReference to ensure the ImageView can be garbage collected  
8. imageViewReference = new WeakReference<ImageView>(imageView);  
9.     }  
10.   
11.   
12.     // Decode image in background.  
13.     @Override  
14.     protected Bitmap doInBackground(Integer... params) {  
15. data = params[0];  
16.         return decodeSampledBitmapFromResource(getResources(), data, 100, 100));  
17.     }  
18.   
19.   
20.     // Once complete, see if ImageView is still around and set bitmap.  
21.     @Override  
22.     protected void onPostExecute(Bitmap bitmap) {  
23.         if (imageViewReference != null && bitmap != null) {  
24. imageView = imageViewReference.get();  
25.             if (imageView != null) {  
26.                 imageView.setImageBitmap(bitmap);  
27.             }  
28.         }  
29.     }  
30. }



注意,这里对ImageView使用 WeakReference弱引用的目的是确保 AsyncTask不会妨碍系统对ImageView必要时候的垃圾回收。否则可能会出现内存泄露,同时,我们一定要在Task执行完毕后对ImageView的存在性进行判断,因为不能保证Task执行完毕后,ImageView还会存在。



下来我们按照下面的代码就可以使用这个Task了:



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


第二步:处理并发情况


ListView与GridView这种多子视图的控件会出现两个问题,

第一,一个ListView会有众多的ChildView,为了更高效的利用内存,控件会自动回收掉被用户滑动过去,不在当前有显示的ChildView,如果每一个ChildView都开启一个Task去加载图片,这样就不能保证开启Task的ChildView在Task执行完毕后没有被回收掉(很有可能用户滑动到其他地方去了)。


第二,因为每张图片的处理时间是不同的,因此同样不能保证加载完成的次序与开始的次序一致。


下来我们开始着手解决这些问题,我们要让ImageView与Task形成一种绑定的关系。

我们先来创建一个特殊的Drawable,这个Drawable有两个功能,一个是与Task形成一种绑定的关系,另外也充当了ImageView的临时占位图像,该Drawable的代码如下:


1. static class AsyncDrawable extends BitmapDrawable {  
2. <BitmapWorkerTask> bitmapWorkerTaskReference;  
3.   
4.     public AsyncDrawable(Resources res, Bitmap bitmap,  
5.             BitmapWorkerTask bitmapWorkerTask) {  
6.         super(res, bitmap);  
7. bitmapWorkerTaskReference =  
8. new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);  
9.     }  
10.   
11.     public BitmapWorkerTask getBitmapWorkerTask() {  
12.         return bitmapWorkerTaskReference.get();  
13.     }  
14. }


在该Drawable中通过弱引用能与对应的Task形成一种一一对应的捆绑关系。

我们可以这样使用它,在执行Task之前,先创建一个对应的Drawable,并把它当成将要呈现实际图片的ImageView占位图片,同时也与ImageView形成了绑定关系。


1. public void loadBitmap(int resId, ImageView imageView) {  
2.     if (cancelPotentialWork(resId, imageView)) {  
3. task = new BitmapWorkerTask(imageView);  
4. asyncDrawable =  
5. new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);  
6.         imageView.setImageDrawable(asyncDrawable);  
7.         task.execute(resId);  
8.     }  
9. }


当然,我们需要判断下ImageView之前是否已经绑定了,如果之前绑定过但与本次的图片不同,那我们就要按最新的需要从新绑定下,如果之前与现在的一致,则保持原状,不再从新绑定,代码中的cancelPotentialWork就是做这个工作的,其代码如下:


1. public static boolean cancelPotentialWork(int data, ImageView imageView) {  
2. bitmapWorkerTask = getBitmapWorkerTask(imageView);  
3.   
4.     if (bitmapWorkerTask != null) {  
5. bitmapData = bitmapWorkerTask.data;  
6.         if (bitmapData != data) {  
7.             // Cancel previous task  
8.             bitmapWorkerTask.cancel(true);  
9.         } else {  
10.             // The same work is already in progress  
11.             return false;  
12.         }  
13.     }  
14.     // No task associated with the ImageView, or an existing task was cancelled  
15.     return true;  
16. }




1. private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {  
2.    if (imageView != null) {  
3. drawable = imageView.getDrawable();  
4.        if (drawable instanceof AsyncDrawable) {  
5. asyncDrawable = (AsyncDrawable) drawable;  
6.            return asyncDrawable.getBitmapWorkerTask();  
7.        }  
8.     }  
9.     return null;  
10. }


最后,我们在Task的

onPostExecute函数中,把加载的图片更新到视图中去,在更新前我们需要检查下Task是否被取消,并且当前的Task是否是那个与ImageView关联的Task,一致则我们把图片更新到ImageView上去,代码如下:



1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
2.     ...  
3.   
4.     @Override  
5.     protected void onPostExecute(Bitmap bitmap) {  
6.         if (isCancelled()) {  
7. bitmap = null;  
8.         }  
9.   
10.         if (imageViewReference != null && bitmap != null) {  
11. imageView = imageViewReference.get();  
12. bitmapWorkerTask =  
13. getBitmapWorkerTask(imageView);  
14. this == bitmapWorkerTask && imageView != null) {  
15.                 imageView.setImageBitmap(bitmap);  
16.             }  
17.         }  
18.     }  
19. }


最后,实际的使用也相当简单,只需要在你的ListView适配器的getView函数中调用上面的loadBitmap函数就OK了~


下一节我们来说说缓存,加入缓存让这个机制更加强大。。