Android中高效的显示图片之二——在非UI线程中处理图片
在“加载大图”文章中提到的BitmapFactory.decode*方法,如果源数据是在磁盘、网络或其它任何不是在内存中的位置,那么它都不应该在UI线程中执行。因为它的加载时间不可预测且依赖于一系列因素(磁盘读写速度、图片大小、CPU频率等)。如果在主线程中执行这个操作,一旦它阻塞了主线程,就会导致系统ANR。本节介绍使用AsyncTask在后台处理图片和演示怎么处理并发问题。
一、使用一个AsyncTask
AsyncTask类提供一个简易的方法在后台线程中执行一些任务并把结果发布到UI线程。使用它只需要创建一个它的子类并重写它的几个方法即可。下面是一个使用AsyncTask和decodeSampleBitmapFromResource加载大图到ImageView的例子:
- class BitmapWorkerTask extends AsyncTask {
- private final WeakReference imageViewReference;
- private int data = 0;
- public BitmapWorkerTask(ImageView imageView) {
- //使用弱引用保证ImageView可以被正常回收
- imageViewReference = new WeakReference(imageView);
- }
- // 在后台加载图片
- @Override
- protected Bitmap doInBackground(Integer... params) {
- data = params[0];
- return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
- }
- // 一旦完成,如果ImageView还存在,将图片设置给它
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (imageViewReference != null && bitmap != null) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- }
- }
- }
- }
对ImageView使用弱引用可以确保AsyncTask不会阻止ImageView和它的任何引用被垃圾回收器回收。因为不能保证当任务完成后ImageView还存在,所以在onPostExecute()方法中需要对引用进行检查。
想要异步加载图片,只需要创建一个任务并执行它:
- public void loadBitmap(int resId, ImageView imageView) {
- BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- task.execute(resId);
- }
二、处理并发
上面讲述的使用AsyncTask的方法加载图片,对于有些控件例如ListView和GridView来说会产生新的问题。为了优化内存使用,当用户拖动时这些控件重复使用它的子视图。如果每个子视图触发一个AsyncTask,无法确保当它在后台执行完毕后,与它相关联的子视图没有被其它子视图重复使用。另外,也无法保证这些任务开始的顺序与它们完成的顺序一致。下面的方法可以解决这个问题:
创建一个专用的Drawable的子类用来保存任务的引用,这里使用了一个BitmapDrawable,因此当任务完成时占位图片可以在ImageView中显示。
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference bitmapWorkerTaskReference;
- public AsyncDrawable(Resources res, Bitmap bitmap,
- BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference =
- new WeakReference(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()方法取消之前的任务。有一定概率,新任务的数据与已存在的一致,此时没有必要再做其他工作,下面是方法的实现:
- 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;
- }
上面代码使用的一个helper方法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 {
- ...
- @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方法即可。
Android中高效的显示图片之二——在非UI线程中处理图片的更多相关文章
- Android中高效的显示图片之三——缓存图片
加载一张图片到UI相对比较简单,如果一次要加载一组图片,就会变得麻烦很多.像ListView,GridView,ViewPager等控件,需要显示的图片和将要显示的图片数量可能会很大. 为了减少内存使 ...
- Android 高级UI设计笔记17:Android在非UI线程中显示Toast
1. 子线程的Toast怎么显示不出来? 因为Toast在创建的时候会依赖于一个Handler,并且一个Handler是需要有一个Looper才能够创建,而普通的线程是不会自动去创建一个Looper对 ...
- Android开之在非UI线程中更新UI
当在非UI线程中更新UI(程序界面)时会出现例如以下图所看到的的异常: 那怎样才干在非UI线程中更细UI呢? 方法有非常多种.在这里主要介绍三种: 第一种:调用主线程mHandler的post(Run ...
- Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面
Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍 ...
- 一个解决在非UI线程中访问UI 异常的小方法
写 WPF 的童鞋可能都会碰到 在非UI线程中访问 UI 异常的问题.这是为了防止数据不一致做的安全限制. 子线程中更新UI还要交给主线程更新,引用满天飞,实在是麻烦. 接下来,我们推出一个可以称之为 ...
- WPF非UI线程中调用App.Current.MainWindow.Dispatcher提示其他线程拥有此对象,无权使用。
大家都知道在WPF中对非UI线程中要处理对UI有关的对象进行操作,一般需要使用委托的方式,代码基本就是下面的写法 App.Current.MainWindow.Dispatcher.Invoke(ne ...
- Android训练课程(Android Training) - 高效的显示图片
高效的显示图片(Displaying BitmapsEfficiently) 了解如何使用通用的技术来处理和读取位图对象,让您的用户界面(UI)组件是可响应的,并避免超过你的应用程序内存限制的方式.如 ...
- Android在非UI线程中更新UI的方法
1.使用Thread+Handler实现非UI线程更新UI界面 在UI Thread中创建Handler.用sendMessage(message)或者obtainMessage(result, ob ...
- 为什么在非UI线程中操作UI的改变失不安全的
因为你如果允许在非UI线程更新操作UI的东西,那我再另一个非UI线程也可以更新这个Ui的东西 这样就会有冲突,比如你的线程刚好跑到修改UI这里,我的另一个UI也有可能跑到这里,所以这样导致线程不安全. ...
随机推荐
- Tkinter 控件详细介绍
Tkinter 控件详细介绍 1.Button 按钮.类似标签,但提供额外的功能,例如鼠标掠过.按下.释放以及键盘操作/事件 2.Canvas 画布.提供绘图功能(直线.椭圆.多边形.矩形) ;可以包 ...
- wxwidget自定义消息处理步骤
from http://www.cppblog.com/kenlistian/archive/2009/02/06/73096.html 略有修改 wxwidget自定义消息处理步骤 自定义消息处理( ...
- 九度OJ 1335:闯迷宫 (BFS)
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1782 解决:483 题目描述: sun所在学校每年都要举行电脑节,今年电脑节有一个新的趣味比赛项目叫做闯迷宫. sun的室友在帮电脑节设计 ...
- x-www-form-urlencoded名字的由来
1 提交的是表单数据 所以用form. 2 提交的形式是以参数放在url后面的形式提交的 例如,以x1=y1&x2=y2&x3=y3的形式放在url后面的形式提交,所以是urlenco ...
- iOS开发之获取系统相册ALAssetLibrary
注:当你选择看这篇博客时想必你的应用还支持iOS8一下系统,如果你的应用要求最低版本大于iOS8,建议使用PhotoKit框架,效率更高 ALAssetsLibrary包含,ALAssetsLibra ...
- 【python】-- IO多路复用(select、poll、epoll)介绍及实现
IO多路复用(select.poll.epoll)介绍及select.epoll的实现 IO多路复用中包括 select.pool.epoll,这些都属于同步,还不属于异步 一.IO多路复用介绍 1. ...
- 5.Django数据库配置
Django默认支持sqlite.mysql.oracle.postgresql数据库,像db2和sqlserver需要安装第三方的支持 配置Django数据库:\hello_django\hello ...
- python基础14 ---函数模块5(模块和包)
模块与包 一.模块 1.模块是怎么诞生的. 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护.为了编写可维护的代码,我们把很多函数分组,分别放到 不同的文 ...
- Android中子线程真的不能更新UI吗?
Android的UI访问是没有加锁的,这样在多个线程访问UI是不安全的.所以Android中规定只能在UI线程中访问UI. 但是有没有极端的情况?使得我们在子线程中访问UI也可以使程序跑起来呢?接下来 ...
- 高性能javascript学习总结(3)--数据访问
在 JavaScript 中,数据存储位置可以对代码整体性能产生重要影响.有四种数据访问类型:直接量,变量,数组项,对象成员. 直接量仅仅代表自己,而不存储于特定位置. JavaScr ...