注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。
原文链接: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
在上节课(博客链接: http://www.cnblogs.com/jdneo/p/3514060.html )中所讨论的 BitmapFactory.decode* 方法,在数据源是从闪存盘或者网络(任何非手机存储的数据来源)读取的,那么就不能再主UI线程中执行。因为加载这个数据所花费的时间是不可估计的,并且它依赖于虚度因素(从网络读取的速度,图片尺寸,CPU的处理能力,等等)。如果其中一个任务阻塞的UI线程,那么系统将会把你的应用标记为未响应,之后用户就可以选择强制关闭它(可以阅读: Designing for Responsiveness )。
这节课会教你如何在一个后台线程,使用
AsyncTask
处理图像,并告诉你如何处理并发问题。
一). 使用一个AsyncTask
AsyncTask
类提供一个简单地方法在后台线程执行一些任务,并将结果反馈给UI线程。要使用它,可以创建一个子类,并覆写一些函数。下面是一个使用
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 是否仍然存在就没有保证了,所以你必须在 onPostExecute() 中检查一下引用是否存在。这个 ImageView 也许已经不存在了,就比如说,用户转到了其他的activity,或者一个配置的变更在任务完成之前发生了。
要开始异步地加载这个位图,简单地创建一个任务的实例并执行它:
public
void
loadBitmap(
int
resId, ImageView imageView) {
BitmapWorkerTask task
=
new
BitmapWorkerTask(imageView);
task.execute(resId);
}
二). 处理并发
当一些普通的View组件,如: ListView 和 GridView 等和 AsyncTask 配合使用时,会引入另一个之前章节讲过的问题。为了让存储使用更高效,这些组件会在用户滚动窗口时回收自己的子View。如果没一个子View都激活一个 AsyncTask ,那么当执行完毕后,相关联的view是否会因为另一个子view也引用同样的对象而不被回收,这一方面是没有保证的。另外,异步任务结束的顺序是否和开始的顺序保持一致,这一点也未必。
在这篇博客: Multithreading for Performance 中进一步讨论了处理并发的问题,并提供了一种解决方案,这个方案能让 ImageView 存储一个最新的 AsyncTask 引用,同时在任务执行完毕后可以对其进行检查。还是像之前章节那样类似的方法,对 AsyncTask 进行一些扩展。
创建一个专用的
Drawable
子类,用来存储“WorkerTask”的引用。在这个例子中,一个
BitmapDrawable
被使用到,这样的话一个“占位符式的”图片就能在任务完成之前被显示:
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 和其它需要回收子view的组件来说,就变的更加合适了。只需要调用 loadBitmap()就可以对你的 ImageView 设置图片。例如,在一个 GridView 的实现中,是在其对应适配器的 getView() 方法中执行。

