http://my.oschina.net/ryanhoo/blog/88344
译者:Ryan
Hoo
来源:https://developer.android.com/develop/index.html
译者按: 在Google最新的文档中,提供了一系列含金量相当高的教程。因为种种原因而鲜为人知,真是可惜!Ryan将会细心整理,将之翻译成中文,希望对开发者有所帮助。
本系列是Google关于展示大Bitmap(位图)的官方演示,可以有效的解决内存限制,更加有效的加载并显示图片,同时避免让人头疼的OOM(Out Of Memory)。
-------------------------------------------------------------------------------------
译文:
在高效地加载Bitmap中,我们讨论了BitmapFactory.decode*系列方法,如果源数据来自硬盘或者网络(或者除内存之外的来源),是不应该在主UI线程执行的。这是因为读取这样的数据所需的加载时间是不确定的,它依赖于多种因素(从硬盘或网络的读取速度、图片的大小、CPU的功率等等)。如果这些任务里面任何一个阻塞了UI线程,系统会将你的应用标记为未响应,并且用户可以选择关闭应用(更多信息,请参阅Designing
for Responsiveness)。
这节课将教会你使用AsyncTask在后台线程处理Bitmap并向你展示如何处理并发问题。
使用AsyncTask(异步任务)
AsyncTask类提供了一种简单的方法,可以在后来线程处理一些事情,并将结果返回到UI线程。要使用它,需要创建一个继承于它的子类,并且覆写它提供的方法。这里有一个使用AsyncTask和decodeSampledBitmapFromResource()加载大图片到ImageView中的例子:
01
|
class BitmapWorkerTask extends AsyncTask<Integer,
Void, Bitmap> {
|
02
|
private final WeakReference<ImageView>
imageViewReference;
|
05
|
public BitmapWorkerTask(ImageView
imageView) {
|
06
|
//
Use a WeakReference to ensure the ImageView can be garbage collected
|
07
|
imageViewReference
= new WeakReference<ImageView>(imageView);
|
10
|
//
Decode image in background.
|
12
|
protected Bitmap
doInBackground(Integer... params) {
|
14
|
return decodeSampledBitmapFromResource(getResources(),
data, 100 , 100 ));
|
17
|
//
Once complete, see if ImageView is still around and set bitmap.
|
19
|
protected void onPostExecute(Bitmap
bitmap) {
|
20
|
if (imageViewReference
!= null &&
bitmap != null )
{
|
21
|
final ImageView
imageView = imageViewReference.get();
|
22
|
if (imageView
!= null )
{
|
23
|
imageView.setImageBitmap(bitmap);
|
ImageView的WeakReference(弱引用)可以确保AsyncTask不会阻止ImageView和它的任何引用被垃圾回收器回收。不能保证在异步任务完成后ImageView依然存在,因此你必须在onPostExecute()方法中检查引用。ImageView可能已经不存在了,比如说,用户在任务完成前退出了当前Activity或者应用配置发生了变化(横屏)。
为了异步加载Bitmap,我们创建一个简单的异步任务并且执行它:
1
|
public void loadBitmap( int resId,
ImageView imageView) {
|
2
|
BitmapWorkerTask
task = new BitmapWorkerTask(imageView);
|
处理并发
常见的View(视图)组件如ListView和GridView在于AsyncTask配合使用的时候引出了另外一个问题,这个我们在上一节中提到过。为了提升内存效率,当用户滚动这些组件的时候进行子视图的回收(主要是回收不可见的视图)。如果每个子视图都触发了一个AsyncTask,无法保证在任务完成的时候,关联视图还没有被回收而被用来显示另一个子视图。此外,也无法保证异步任务结束的循序与它们开始的顺序一致。
Multithreading
for Performance这篇文章深入讨论了如何处理并发问题,并且给出了如何在任务结束的时候检测ImageView存储最近使用的AsyncTask引用的解决方案。使用相似的方法,可以遵循类似的模式来扩展前面的AsyncTask。
创建一个专用的Drawable之类,用来存储worker task的引用。在这种情况下,任务结束的时候BitmapDrawable可以取代图像占位符显示在ImageView中。
01
|
static class AsyncDrawable extends BitmapDrawable
{
|
02
|
private final WeakReference<BitmapWorkerTask>
bitmapWorkerTaskReference;
|
04
|
public AsyncDrawable(Resources
res, Bitmap bitmap,
|
05
|
BitmapWorkerTask
bitmapWorkerTask) {
|
07
|
bitmapWorkerTaskReference
=
|
08
|
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
|
11
|
public BitmapWorkerTask
getBitmapWorkerTask() {
|
12
|
return bitmapWorkerTaskReference.get();
|
在执行BitmapWorkerTask前,你需要创建一个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);
|
在上面的代码示例中引用的cancelPotentialWork方法可以检测一个执行中的任务是否与ImageView有关联。如果有关联,它将通过调用canceel()方法试图取消之前的任务。在少数情况下,新的任务中的数据与现有的任务相匹配,因此不需要做什么。下面是calcelPotentialWork的具体实现:
01
|
public static boolean cancelPotentialWork( int data,
ImageView imageView) {
|
02
|
final BitmapWorkerTask
bitmapWorkerTask = getBitmapWorkerTask(imageView);
|
04
|
if (bitmapWorkerTask
!= null )
{
|
05
|
final int bitmapData
= bitmapWorkerTask.data;
|
06
|
if (bitmapData
!= data) {
|
07
|
//
Cancel previous task
|
08
|
bitmapWorkerTask.cancel( true );
|
10
|
//
The same work is already in progress
|
14
|
//
No task associated with the ImageView, or an existing task was cancelled
|
一个助手方法,getBitmapWorkerTask(),在上面用来检索和指定ImageView相关的任务
01
|
private static BitmapWorkerTask
getBitmapWorkerTask(ImageView imageView) {
|
02
|
if (imageView
!= null )
{
|
03
|
final Drawable
drawable = imageView.getDrawable();
|
04
|
if (drawable instanceof AsyncDrawable)
{
|
05
|
final AsyncDrawable
asyncDrawable = (AsyncDrawable) drawable;
|
06
|
return asyncDrawable.getBitmapWorkerTask();
|
最后一步是更新BitmapWorkerTask中的onPostExecute()方法,以便检测与ImageView关联的任务是否被取消或者与当前任务相匹配。
01
|
class BitmapWorkerTask extends AsyncTask<Integer,
Void, Bitmap> {
|
05
|
protected void onPostExecute(Bitmap
bitmap) {
|
10
|
if (imageViewReference
!= null &&
bitmap != null )
{
|
11
|
final ImageView
imageView = imageViewReference.get();
|
12
|
final BitmapWorkerTask
bitmapWorkerTask =
|
13
|
getBitmapWorkerTask(imageView);
|
14
|
if ( this ==
bitmapWorkerTask && imageView != null )
{
|
15
|
imageView.setImageBitmap(bitmap);
|
这里的方法也适合用在ListView、GridView以及其他任何需要回收子视图的组件中。当你只需要为ImageView设置图片,调用loadBitmap就可以了。例如,在GridView中实现的方式是在Adapter的getView()方法中。
时间: 2024-11-05 01:30:23