您的位置:首页 > 移动开发 > Android开发

Android高效显示图片详解(二)

2016-01-05 18:24 369 查看
上节课我们介绍了如何加载和显示大图,这节课我们就要把这个技巧与实际开发联系起来,在实际的开发过程中,最常见的场景就是用ListView,GridView等集合显示控件

来呈现图片,这节课,我们就要用这些控件来高效的显示图片。

实际的使用环境中,如果图片来源是SD卡或者网络,那那么加载图片的过程一定不要放在UI线程中,这样会严重的阻塞UI线程,出现ANR,程序就废了。因此我们首先要实现异步加载。

第一步:利用AsyncTask实现图片的异步加载

将decodeSampledBitmapFromResource方法放入Task的doInBackground中后台执行。不熟悉AsyncTask的同学可以学习AsyncTask的相关知识,这里不再过多介绍。

代码:

[html] view
plaincopy

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必要时候的垃圾回收。否则可能会出现内存泄露,同时,我们一定要在Task执行完毕后对ImageView的存在性进行判断,因为不能保证Task执行完毕后,ImageView还会存在。

下来我们按照下面的代码就可以使用这个Task了:

[html] view
plaincopy

public void loadBitmap(int resId, ImageView imageView) {

BitmapWorkerTask task = new BitmapWorkerTask(imageView);

task.execute(resId);

}

第二步:处理并发情况

ListView与GridView这种多子视图的控件会出现两个问题,

第一,一个ListView会有众多的ChildView,为了更高效的利用内存,控件会自动回收掉被用户滑动过去,不在当前有显示的ChildView,如果每一个ChildView都开启一个Task去加载图片,这样就不能保证开启Task的ChildView在Task执行完毕后没有被回收掉(很有可能用户滑动到其他地方去了)。

第二,因为每张图片的处理时间是不同的,因此同样不能保证加载完成的次序与开始的次序一致。

下来我们开始着手解决这些问题,我们要让ImageView与Task形成一种绑定的关系。

我们先来创建一个特殊的Drawable,这个Drawable有两个功能,一个是与Task形成一种绑定的关系,另外也充当了ImageView的临时占位图像,该Drawable的代码如下:

[html] view
plaincopy

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();

}

}

在该Drawable中通过弱引用能与对应的Task形成一种一一对应的捆绑关系。

我们可以这样使用它,在执行Task之前,先创建一个对应的Drawable,并把它当成将要呈现实际图片的ImageView占位图片,同时也与ImageView形成了绑定关系。

[html] view
plaincopy

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);

}

}

当然,我们需要判断下ImageView之前是否已经绑定了,如果之前绑定过但与本次的图片不同,那我们就要按最新的需要从新绑定下,如果之前与现在的一致,则保持原状,不再从新绑定,代码中的cancelPotentialWork就是做这个工作的,其代码如下:

[html] view
plaincopy

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;

}

[html] view
plaincopy

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;

}

最后,我们在Task的onPostExecute函数中,把加载的图片更新到视图中去,在更新前我们需要检查下Task是否被取消,并且当前的Task是否是那个与ImageView关联的Task,一致则我们把图片更新到ImageView上去,代码如下:

[html] view
plaincopy

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适配器的getView函数中调用上面的loadBitmap函数就OK了~

下一节我们来说说缓存,加入缓存让这个机制更加强大。。

感谢收看! 多多好评,在此谢过!

Android高效显示图片详解(三)地址:/article/3678976.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: