非UI线程加载图片
2015-05-31 16:20
441 查看
Processing Bitmaps Off the UI Thread 非UI线程加载图片
BitmapFactory.decode*一系列方法,在之前的高效加载大图的文章中讲到过。如果图片的数据源是磁盘,或则网络(内存以外的其他地方),那么解析图片的方法不应该在UI线程中执行。这些数据加载任务所要花费的时间有许多不可控因素,(例如:磁盘读取速度,图片的大小,CPU的频率,等等)如果这些任务阻塞了UI线程,系统判定你的应用程序无响应,用户是有权关闭你的软件的,这样的用户体验非常不好。
这篇文章主要用来讲解如何使用AsyncTask在后台线程处理图片加载过程,以及并发问题的处理。
Use an AsyncTask 使用AsyncTask
AsyncTask这个类使得 在后台处理某些任务变得更加简单,处理完任务之后再将结果 返回给UI线程。使用它的方式就是自己写一个子类覆盖AsyncTask中的方法。下面是一个通过AsyncTask和decodeSampledBitmapFromResource()方法来加载图片进入ImageView的一个例子:[code]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的对象被垃圾回收机制回收。 当AsyncTask任务执行完了之后,不能保证ImageView任然存在,所以你必须 在onPostExecute()方法中检查 一下这个引用。 这个ImageView可能不会长久存在,比如当用户退出当前Activity,或则任务完成之后 配置环境发生变化。
实现异步的加载图片的任务只需要新建一个Task然后execute一下。
[code]public void loadBitmap(int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); }
Handle Concurrency 处理并发问题
一些常用的View组件,ListView和GridView与AsyncTask结合起来展示当前章节的内容的时候,会引发一系列问题。 为了高效的使用内存,这些控件会在用户滑动屏幕的时候回收子控件view对象。 如果每一个子类的view对象都触发一个AsyncTask,这就不能保证当AsyncTask任务执行完时,相关的view对象还没有被回收,从而用来展示其他的view。而且并不能保证异步任务的开始顺序和结束顺序的一致性。
这篇博客 讲了 Multithreading for Performance 来解决并发问题的讨论,而且提供了一个解决办法.
创建一个Drawable的子类去存储一个指向worker task的引用.在这种情况下,BitmapDrawable就可拿出来使用了。这样的话 当后台任务执行完之后,image的容器 PlaceHoler 就可以将image显示在ImageView中了。
[code]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上面。
[code]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是否有多个正在运行的任务。(其实就是验证一个imageView是否只有一个后台任务运行,避免一个imageView绑定多个后台任务,造成资源的浪费)
如果 是,则通过 cancel()方法来取消当前的任务。 其实在少数情况下,即使发现了新开的后台任务跟之前的有重复,但是也不会取消掉这个新开的任务。 下面是这个方法的实现。
[code]public static boolean cancelPotentialWork(int data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final int bitmapData = bitmapWorkerTask.data; // If bitmapData is not yet set or it differs from the new data // if (bitmapData == 0 || bitmapData != data) { //取消当前任务 // Cancel previous task bitmapWorkerTask.cancel(true); } else { // The same work is already in progress return false; } } //当前ImageView没有其他的任务与它关联,或则有其他的任务与他关联但是已经取消掉了 // No task associated with the ImageView, or an existing task was cancelled return true; }
还有一个帮助方法,getBitmapWorkerTask 用来获得与这个ImageView想关联的任务
[code] 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相关联。
[code]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的组件,也可以使用这个类。
在你需要为 ImageView设置 一个image的时候简单的调用一下 loadBitmap这个方法就可以实现复杂的后台加载任务了。
比如说:在GridView中实现这个过程就可以后台的Adapter中的getView()方法中修改。
这篇 示例的完整实现类,请看郭霖的博客文章。
相关文章推荐
- Android UI 模板设计
- 对于字符串拼接,string.format、stringbuilder、+=
- Arduino数字电路中的高低电平~~~
- Internal Sorting: Quicksort-2: Sorting by Exchanging
- UILable的属性及方法使用
- VMware Workstation 不可恢复错误:(vmui)
- 【STL源码剖析读书笔记】自己实现priority_queue之MyPriorityQueue
- 【STL源码剖析读书笔记】自己实现priority_queue之MyPriorityQueue
- iOS开发-UIScrollView的学习
- BusyBox 官网 Frequently Asked Questions
- question1-在插入排序中运用二元搜索代替线性查找
- Searching: Sequential Search
- Searching: Quick Sequential Search
- Searching: Sequential Search In Ordered Table
- 【Android】UI thread和非UI thread
- TUniQuery 对应的tb_table中的数据更新操作
- ugui学习资料
- scrollView-contentOffset和UIViewAnimation动画执行
- jquey easyui 常用方法
- UITableView中的(NSIndexPath *)indexPath