转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-) html
http://my.oschina.net/ryanhoo/blog/88344
java
译者:Ryan Hoo android
来源:https://developer.android.com/develop/index.html
网络
译者按: 在Google最新的文档中,提供了一系列含金量至关高的教程。由于种种缘由而不为人知,真是惋惜!Ryan将会细心整理,将之翻译成中文,但愿对开发者有所帮助。 并发
本系列是Google关于展现大Bitmap(位图)的官方演示,能够有效的解决内存限制,更加有效的加载并显示图片,同时避免让人头疼的OOM(Out Of Memory)。
异步
------------------------------------------------------------------------------------- async
译文: ide
在高效地加载Bitmap中,咱们讨论了BitmapFactory.decode*系列方法,若是源数据来自硬盘或者网络(或者除内存以外的来源),是不该该在主UI线程执行的。这是由于读取这样的数据所需的加载时间是不肯定的,它依赖于多种因素(从硬盘或网络的读取速度、图片的大小、CPU的功率等等)。若是这些任务里面任何一个阻塞了UI线程,系统会将你的应用标记为未响应,而且用户能够选择关闭应用(更多信息,请参阅Designing for Responsiveness)。 this
这节课将教会你使用AsyncTask在后台线程处理Bitmap并向你展现如何处理并发问题。 spa
使用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或者应用配置发生了变化(横屏)。
为了异步加载Bitmap,咱们建立一个简单的异步任务而且执行它:
public void loadBitmap(int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); }
处理并发
常见的View(视图)组件如ListView和GridView在于AsyncTask配合使用的时候引出了另一个问题,这个咱们在上一节中提到过。为了提高内存效率,当用户滚动这些组件的时候进行子视图的回收(主要是回收不可见的视图)。若是每一个子视图都触发了一个AsyncTask,没法保证在任务完成的时候,关联视图尚未被回收而被用来显示另外一个子视图。此外,也没法保证异步任务结束的循序与它们开始的顺序一致。
Multithreading for Performance这篇文章深刻讨论了如何处理并发问题,而且给出了如何在任务结束的时候检测ImageView存储最近使用的AsyncTask引用的解决方案。使用类似的方法,能够遵循相似的模式来扩展前面的AsyncTask。
建立一个专用的Drawable之类,用来存储worker 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有关联。若是有关联,它将经过调用canceel()方法试图取消以前的任务。在少数状况下,新的任务中的数据与现有的任务相匹配,所以不须要作什么。下面是calcelPotentialWork的具体实现:
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以及其余任何须要回收子视图的组件中。当你只须要为ImageView设置图片,调用loadBitmap就能够了。例如,在GridView中实现的方式是在Adapter的getView()方法中。