非UI线程加载图片

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

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

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

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

Use an AsyncTask 使用AsyncTask

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

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一下。

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中了。

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上面。

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()方法来取消当前的任务。 其实在少数情况下,即使发现了新开的后台任务跟之前的有重复,但是也不会取消掉这个新开的任务。 下面是这个方法的实现。

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想关联的任务

  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相关联。

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()方法中修改。

这篇 示例的完整实现类,请看郭霖的博客文章

时间: 2024-10-08 04:51:38

非UI线程加载图片的相关文章

异步线程加载图片工具类

/** * 异步线程加载图片工具类 * 使用说明: * BitmapManager bmpManager; * bmpManager = new BitmapManager(BitmapFactory.decodeResource(context.getResources(), R.drawable.loading)); * bmpManager.loadBitmap(imageURL, imageView); */ public class BitmapManager { private st

阿冰教你一步一步做Android新闻客户端(二)两种异步线程加载图片的方法

哈哈哈抱着没人看的心态随便写,直接上代码,各位看官看注释 一种Thread  一种AsyncTask 先不说用框架 public class ImageLoader { private ImageView mImageView; private String mUrl; //Thread明显很low 还需要handler来传递消息,好累T T android.os.Handler mHandler = new android.os.Handler(){ @Override public void

高效地加载图片(五) 将图片展示在UI中

这篇文章将前几篇使用的方法进行了整合,让我们能够在后台线程中加载以及缓存图片并在ViewPager和GridView中展示出来,并在这些过程中处理并发以及参数的设置. 将图片加载到ViewPager中 使用滑动视图来对图片详情进行导航是一种不错的方式.我们可以使用ViewPager和PagerAdapter来实现.但是,使用FragmentStatePagerAdapter可能会更好,它能够自动地保存ViewPager中Fragment的状态并控制它的创建和销毁,能够有效地利用内存. 注意:如果

Android:ViewPager扩展详解——带有导航的ViewPagerIndicator(附带图片缓存,异步加载图片)

大家都用过viewpager了, github上有对viewpager进行扩展,导航风格更加丰富,这个开源项目是ViewPagerIndicator,很好用,但是例子比较简单,实际用起来要进行很多扩展,比如在fragment里进行图片缓存和图片异步加载. 下面是ViewPagerIndicator源码运行后的效果,大家也都看过了,我多此一举截几张图: 下载源码请点击这里 ===========================================华丽的分割线==============

Listview异步加载图片之优化篇

在APP应用中,listview的异步加载图片方式能够带来很好的用户体验,同时也是考量程序性能的一个重要指标.关于listview的异步加载,网上其实很多示例了,中心思想都差不多,不过很多版本或是有bug,或是有性能问题有待优化.有鉴于此,本人在网上找了个相对理想的版本并在此基础上进行改造,下面就让在下阐述其原理以探索个中奥秘,与诸君共赏… 贴张效果图先: 2013-2-1 17:25 上传 下载附件 (214.08 KB) 异步加载图片基本思想: 1.      先从内存缓存中获取图片显示(内

Android异步加载学习笔记之四:利用缓存优化网络加载图片及ListView加载优化

如果不做任何处理,直接用网络加载图片在网速快的情况下可能没什么不好的感觉,但是如果使用移动流量或是网络不好的时候,问题就来了,要么用户会抱怨流量使用太多,要么抱怨图片加载太慢,如论从哪个角度出发,都不是好的体验!要提高用户体验,我们就要使用缓存.Android中数据缓存的方式有很多,相关介绍的文章也比较多,比如http://blog.csdn.net/dahuaishu2010_/article/details/17093139和http://www.jb51.net/article/38162

安卓异步加载图片(缩略图显示)的实现

/** * 根据指定的图像路径和大小来获取缩略图 * 此方法有两点好处: * 1. 使用较小的内存空间,第一次获取的bitmap实际上为null,只是为了读取宽度和高度, * 第二次读取的bitmap是根据比例压缩过的图像,第三次读取的bitmap是所要的缩略图. * 2. 缩略图对于原图像来讲没有拉伸,这里使用了2.2版本的新工具ThumbnailUtils,使 * 用这个工具生成的图像不会被拉伸. * @param imagePath 图像的路径 * @param width 指定输出图像的

Android中高效的显示图片之二——在非UI线程中处理图片

在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘.网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行.因为它的加载时间不可预测且依赖于一系列因素(磁盘读写速度.图片大小.CPU频率等).如果在主线程中执行这个操作,一旦它阻塞了主线程,就会导致系统ANR.本节介绍使用AsyncTask在后台处理图片和演示怎么处理并发问题. 一.使用一个AsyncTask AsyncTask类提供一个简易的方法在后台线程中执行一些任务并把结果发布到UI线程.

使用Android新式LruCache缓存图片,基于线程池异步加载图片

import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import a