位图管理、图片下载缓存、管理图片内存 (三) 在非UI线程中处理位图
2014-11-12 09:57
465 查看
BitmapFactory.decode*等解码方法不应在主线程中执行,假如资源数据是从硬盘或者网络地址中读取的话(或者说除内存以外的其他任意位置)。这些数据可能花费的时间是不可预知的,依赖于一系列的因素(包括硬盘或者网络的读取速度,图片尺寸,CPU处理能力等)。如果其中某个因素阻塞了UI线程,可能导致应用提示无响应状态。本节将学习如何通过AsyncTask在后台处理位图,并说明如何处理并发问题。
使用异步任务
异步任务类提供了一种简单的方法,用于在后台线程中执行某些工作,并将结果发布到UI线程中。要使用异步任务,需要创建AsyncTask的子类,并重写其中一些方法,下面是一个通过AsyncTask下载大图到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);
}
}
}
} 使用WeakReference存贮ImageView可以让AsyncTask不阻止ImageView和其他的资源被垃圾回收,不过,当异步任务结束时不保证ImageView仍然存在,所以必须在onPostExecute()中检查引用。ImageView可能已经不存在,比如说,客户从当前Activity中导航离开,或者在异步任务结束之前配置发生改变(屏幕方向发生改变)。
异步下载位图只需要创建一个该异步任务的子类,并执行即可。
处理并发
如上一节所示,常用组件如ListView和GridView与异步任务结合使用时可能引起另外一个问题,为了高效使用内存,当组件滚动时,组件会循环使用其子视图,如果每个视图都触发一个AsyncTask,则无法确定当他们结束时,与之相关联的视图是否正被另外一个视图循环使用着。而且,无法保证异步任务启动执行的顺序与其结束执行的顺序一致。
这篇博客对多线程操作的并发问题作进一步讨论,并提供一种方案用于在AsynTask中存贮一个最近使用的ImageView的引用,这个引用会在异步任务结束时被再次检查。通过相似的方法,上一节中讨论的异步任务可以被扩展至如下一种相似的模式。
创建一个专用的Drawable子类用于存储一个引用到工作task中,这样,一个BitmapDrawable会被使用,当这个异步任务结束时,其持有的图片会被展示到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中。
下面是cancelPotentialWork的实现。
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
if (bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// 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相匹配。
使用异步任务
异步任务类提供了一种简单的方法,用于在后台线程中执行某些工作,并将结果发布到UI线程中。要使用异步任务,需要创建AsyncTask的子类,并重写其中一些方法,下面是一个通过AsyncTask下载大图到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);
}
}
}
} 使用WeakReference存贮ImageView可以让AsyncTask不阻止ImageView和其他的资源被垃圾回收,不过,当异步任务结束时不保证ImageView仍然存在,所以必须在onPostExecute()中检查引用。ImageView可能已经不存在,比如说,客户从当前Activity中导航离开,或者在异步任务结束之前配置发生改变(屏幕方向发生改变)。
异步下载位图只需要创建一个该异步任务的子类,并执行即可。
处理并发
如上一节所示,常用组件如ListView和GridView与异步任务结合使用时可能引起另外一个问题,为了高效使用内存,当组件滚动时,组件会循环使用其子视图,如果每个视图都触发一个AsyncTask,则无法确定当他们结束时,与之相关联的视图是否正被另外一个视图循环使用着。而且,无法保证异步任务启动执行的顺序与其结束执行的顺序一致。
这篇博客对多线程操作的并发问题作进一步讨论,并提供一种方案用于在AsynTask中存贮一个最近使用的ImageView的引用,这个引用会在异步任务结束时被再次检查。通过相似的方法,上一节中讨论的异步任务可以被扩展至如下一种相似的模式。
创建一个专用的Drawable子类用于存储一个引用到工作task中,这样,一个BitmapDrawable会被使用,当这个异步任务结束时,其持有的图片会被展示到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相关联了,如果有,那么这个异步任务会调用cancel方法试图取消前一个绑定的异步任务。在少数情况下,新的异步任务数据会与现有的异步任务匹配,而不会有更进一步的事情发生。
下面是cancelPotentialWork的实现。
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
if (bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// 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组件中显得更加合适,也适用于其他任何循环复用子视图的组件。使用时只需要简单调用loadBitmap即可。例如在GridView的实现类中,这个过程只要在后台的适配器中执行即可。
相关文章推荐
- 位图管理、图片下载缓存、管理图片内存 (六) 在UI组件中展示位图
- 位图管理、图片下载缓存、管理图片内存(二)高效下载大图
- 位图管理、图片下载缓存、管理图片内存 (四) 缓存位图
- 位图管理、图片下载缓存、管理图片内存 (五) 管理位图内存
- 位图管理、图片下载缓存、管理图片内存(一) 高效展示图片系列概述
- Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片
- 新应用知识整理-图片的下载、内存软引用与本地缓存的实现
- Android中高效的显示图片之二——在非UI线程中处理图片
- 列表下载图片线程管理
- 安卓中关于图片从网络获取,压缩,上传,下载,缩略图,缓存的一些处理总结(四)
- iOS基础8:自定义MyData/自定义SQLite用于网络判断,版本判断,图片缓存处理,下载或者上传的GET或POST请求,加密手段,.数据解析
- Android 图片缓冲的管理-内存缓存
- 详细讲解Android图片下载框架UniversialImageLoader之内存缓存(三)
- Android中高效的显示图片之二——在非UI线程中处理图片
- 安卓中关于图片从网络获取,压缩,上传,下载,缩略图,缓存的一些处理总结(二)
- Android高性能加载大量图片系列课程2-在非UI线程中处理图片
- Android中高效的显示图片之二——在非UI线程中处理图片
- Android 图片下载本地内存的缓存方式
- Android中高效的显示图片之二——在非UI线程中处理图片
- 列表下载图片线程管理