现在的位置: 首页 > 移动开发 > 正文

Android高效显示图片详解(二)

2019年05月20日 移动开发 ⁄ 共 5169字 ⁄ 字号 评论关闭

http://blog.csdn.net/zhiying201039/article/details/8665598

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

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

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

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

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

代码:

[html] view plaincopy

  1. class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {  
  2.     private final WeakReference<ImageView> imageViewReference;  
  3.     private int 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.             final ImageView imageView = imageViewReference.get();  
  25.             if (imageView != null) {  
  26.                 imageView.setImageBitmap(bitmap);  
  27.             }  
  28.         }  
  29.     }  
  30. }  

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

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

[html] view plaincopy

  1. public void loadBitmap(int resId, ImageView imageView) {  
  2.     BitmapWorkerTask 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的代码如下:

[html] view plaincopy

  1. static class AsyncDrawable extends BitmapDrawable {  
  2.     private final WeakReference<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形成了绑定关系。

[html] view plaincopy

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

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

[html] view plaincopy

  1. public static boolean cancelPotentialWork(int data, ImageView imageView) {  
  2.     final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);  
  3.   
  4.     if (bitmapWorkerTask != null) {  
  5.         final int 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. }  

[html] view plaincopy

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

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

[html] view plaincopy

  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.             final ImageView imageView = imageViewReference.get();  
  12.             final BitmapWorkerTask bitmapWorkerTask =  
  13.                     getBitmapWorkerTask(imageView);  
  14.             if (this == bitmapWorkerTask && imageView != null) {  
  15.                 imageView.setImageBitmap(bitmap);  
  16.             }  
  17.         }  
  18.     }  
  19. }  

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


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


感谢收看! 多多好评,在此谢过!

抱歉!评论已关闭.