您的位置:首页 > 产品设计 > UI/UE

非UI线程加载图片

2015-05-31 16:20 441 查看

Processing Bitmaps Off the UI Thread 非UI线程加载图片

BitmapFactory.decode*一系列方法,在之前的高效加载大图的文章中讲到过。

如果图片的数据源是磁盘,或则网络(内存以外的其他地方),那么解析图片的方法不应该在UI线程中执行。这些数据加载任务所要花费的时间有许多不可控因素,(例如:磁盘读取速度,图片的大小,CPU的频率,等等)如果这些任务阻塞了UI线程,系统判定你的应用程序无响应,用户是有权关闭你的软件的,这样的用户体验非常不好。

这篇文章主要用来讲解如何使用AsyncTask在后台线程处理图片加载过程,以及并发问题的处理。

Use an AsyncTask 使用AsyncTask

AsyncTask这个类使得 在后台处理某些任务变得更加简单,处理完任务之后再将结果 返回给UI线程。使用它的方式就是自己写一个子类覆盖AsyncTask中的方法。下面是一个通过AsyncTask和decodeSampledBitmapFromResource()方法来加载图片进入ImageView的一个例子:

[code]class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private int data = 0;

public BitmapWorkerTask(ImageView imageView) {

      // Use a WeakReference to ensure the ImageView can be garbage collected
      imageViewReference = new WeakReference<ImageView>(imageView);
  }

   // Decode image in background.
  @Override
  protected Bitmap doInBackground(Integer... params) {
     data = params[0];
     return decodeSampledBitmapFromResource(getResources(), data,    100, 100));
 }

 // Once complete, see if ImageView is still around and set bitmap.
  @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的对象被垃圾回收机制回收。 当AsyncTask任务执行完了之后,不能保证ImageView任然存在,所以你必须 在onPostExecute()方法中检查 一下这个引用。 这个ImageView可能不会长久存在,比如当用户退出当前Activity,或则任务完成之后 配置环境发生变化。

实现异步的加载图片的任务只需要新建一个Task然后execute一下。

[code]public void loadBitmap(int resId, ImageView imageView) {

    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);

}


Handle Concurrency 处理并发问题

一些常用的View组件,ListView和GridView与AsyncTask结合起来展示当前章节的内容的时候,会引发一系列问题。 为了高效的使用内存,这些控件会在用户滑动屏幕的时候回收子控件view对象。 如果每一个子类的view对象都触发一个AsyncTask,这就不能保证当AsyncTask任务执行完时,相关的view对象还没有被回收,从而用来展示其他的view。

而且并不能保证异步任务的开始顺序和结束顺序的一致性。

这篇博客 讲了 Multithreading for Performance 来解决并发问题的讨论,而且提供了一个解决办法.

创建一个Drawable的子类去存储一个指向worker task的引用.在这种情况下,BitmapDrawable就可拿出来使用了。这样的话 当后台任务执行完之后,image的容器 PlaceHoler 就可以将image显示在ImageView中了。

[code]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();

    }
}


在执行BitmapWorkerTask之前,你需要创建一个AsyncDrawable 然后将它绑定到目标的ImageView上面。

[code]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);
  }
}


这个cancelPotentialWork方法 可以检查上面的示例代码中的imageView是否有多个正在运行的任务。(其实就是验证一个imageView是否只有一个后台任务运行,避免一个imageView绑定多个后台任务,造成资源的浪费)

如果 是,则通过 cancel()方法来取消当前的任务。 其实在少数情况下,即使发现了新开的后台任务跟之前的有重复,但是也不会取消掉这个新开的任务。 下面是这个方法的实现。

[code]public static boolean cancelPotentialWork(int data, ImageView imageView) {

  final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

  if (bitmapWorkerTask != null) {

      final int bitmapData = bitmapWorkerTask.data;

      // If bitmapData is not yet set or it differs from the new data
        //
      if (bitmapData == 0 || bitmapData != data) {
            //取消当前任务
          // Cancel previous task
          bitmapWorkerTask.cancel(true);

      } else {

          // The same work is already in progress
          return false;
      }
  }
    //当前ImageView没有其他的任务与它关联,或则有其他的任务与他关联但是已经取消掉了
  // No task associated with the ImageView, or an existing task was cancelled
  return true;
}


还有一个帮助方法,getBitmapWorkerTask 用来获得与这个ImageView想关联的任务

[code]  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;
 }


最后一步就是更新(上传)通过BitmapWorkerTask 中的onPostExecute()方法来检查 当前任务是否被取消 ,是否与 这个ImageView相关联。

[code]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);
             }

        }
    }
}


这个实现类现在可以 简单的用在ListView和GridView中了。 一些其他的需要回收子类View的组件,也可以使用这个类。

在你需要为 ImageView设置 一个image的时候简单的调用一下 loadBitmap这个方法就可以实现复杂的后台加载任务了。

比如说:在GridView中实现这个过程就可以后台的Adapter中的getView()方法中修改。

这篇 示例的完整实现类,请看郭霖的博客文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: