Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4027236
  • 博文数量: 366
  • 博客积分: 9916
  • 博客等级: 中将
  • 技术积分: 7195
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-29 23:27
个人简介

简单!

文章分类

全部博文(366)

文章存档

2013年(51)

2012年(269)

2011年(46)

分类: Java

2012-09-26 10:04:34

       众所周知Android应用开发中不能在UI线程中做耗时的操作,否则就会弹出烦人的ANR窗口。应用开发中如果需要加载来自网络、磁盘或其他非内存中图片资源时,因加载时间会受到其他因素(如磁盘、网络、图片大小、CPU等等)的影响,很容易 产生耗时操作。所以在进行类似操作时要避免在UI线程中进行。今天就和大家分享一下如何通过AsyncTask异步加载图片和怎么处理多线程并发问题。


如何使用 AsyncTask加载图片?

       通过AysncTask可以很容易的在启动后台线程加载资源,然后将结果返回到UI线程中。使用它时,需要创建它的子类并实现相应的方法,如下是一 个通过AysncTask和decodeSampledBitmapFromResource()方法加载一张大图片到ImageView中的例子:

  1. class BitmapWorkerTask extends AsyncTask {
  2.      private final WeakReference imageViewReference;
  3.      private int data = 0;
  4.                                                                         
  5.      public BitmapWorkerTask(ImageView imageView) {
  6.          // Use a WeakReference to ensure the ImageView can be garbage collected
  7.          imageViewReference = new WeakReference(imageView);
  8.      }
  9.                                                                                                                                                                                        
  10.      // Decode image in background.
  11.      @Override
  12.      protected Bitmap doInBackground(Integer... params) {
  13.          data = params[0];
  14.          return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
  15.      }                                                                                                                                                                             
  16.      // Once complete, see if ImageView is still around and set bitmap.
  17.      @Override
  18.      protected void onPostExecute(Bitmap bitmap) {
  19.          if (imageViewReference != null && bitmap != null) {
  20.              final ImageView imageView = imageViewReference.get();
  21.              if (imageView != null) {
  22.                  imageView.setImageBitmap(bitmap);
  23.              }
  24.          }
  25.      }
  26.  }
       使用WeakReference 保存ImageView的原因,是为了在内存资源紧张时确保AsyncTask 不会阻止对其进行资源回收,因此当task结束时不能保证Imageview还存在,所以你应该在onPostExecute中对它进行验证(本例中在 Task结束前如果用户关闭Activity,或系统设置改变时,ImageView可能会被回收)。通过以下方式我们就可以异步加载图片:
  1. public void loadBitmap(int resId, ImageView imageView) {
  2.      BitmapWorkerTask task = new BitmapWorkerTask(imageView);
  3.      task.execute(resId);
  4. }

如何处理并发操作?

      常用的View组件中,像ListView、GridView等为了高效实用内存,用户在进行View滚动操作时系统会对不再使用子View进行资源回收,,采用上面的方式进行图片加载时会引入另外一个问题。如果在每 个子View中开启AsyncTask,不能保证在任务完成时,相关的View是否已经被回收。此外,也不能保证他们加载完成的顺序。我们可以通过将AsyncTask的引用保存ImageView关联Drawable中,任务完成时检查引用是否存在.创建一个专用的Drawable子类,存储工作任务线程的引用。这样在任务完成时即可将图片设置在ImageView中。

  1. static class AsyncDrawable extends BitmapDrawable {
  2.      private final WeakReference bitmapWorkerTaskReference;
  3.      public AsyncDrawable(Resources res, Bitmap bitmap,
  4.              BitmapWorkerTask bitmapWorkerTask) {
  5.          super(res, bitmap);
  6.          bitmapWorkerTaskReference =
  7.              new WeakReference(bitmapWorkerTask);
  8.      }
  9.                                                                   
  10.      public BitmapWorkerTask getBitmapWorkerTask() {
  11.          return bitmapWorkerTaskReference.get();
  12.      }
  13.  }
        在执行BitmapTask前,你可以创建AsyncDrawable并将其绑定到ImageView中

  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.  }

       上面代码中通过cancelPotentialWork判断是否已经存在正在运行的任务绑定在ImageView中,若有,通过执行任务cancel方法取消它,当然这种情况不常发生,下面是cancelPotentialWork的实现:

  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.  }
下面是一个辅助方法,通过ImageView查找与其关联的异步任务;

  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.  }

      下一步需要在BitmapWorkerTask中的onPostExecute中执行更新操作,首先检查任务是否取消,如后更行与其关联的ImageView:

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

       通过以上方法,可以在ListView、GridView或者其他具有子view回收处理的组件中使用,通过调用loadBitmap可以很简单的添加图片到ImageView中,如:在GirdView的 Adapter中的getView方法中调用。


阅读(6973) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~