非UI线程处理Bitmap

在上一课中有介绍一系列的BitmapFactory.decode*) 方法,当数据源是网络或者是磁盘时(或者是任何实际源不在内存的),这些方法都不应该在main UI 线程中执行。那些情况下加载数据是不可以预知的,它依赖于许多因素(从网络或者硬盘读取数据的速度, 图片的大小, CPU的速度, etc.)。如果其中任何一个任务卡住了UI thread, 系统会出现ANR的错误。

这一节课会介绍如何使用 AsyncTask 在后台线程中处理bitmap并且演示了如何处理并发(concurrency)的问题。

使用AsyncTask(Use a AsyncTask)

AsyncTask 类提供了一个简单的方法来在后台线程执行一些操作,并且可以把后台的结果呈现到UI线程。下面是一个加载大图的示例:

class BitmapWorkerTask extends AsyncTask {

private final WeakReference imageViewReference;

private int data = 0;

public BitmapWorkerTask(ImageView imageView) {

// Use a WeakReference to ensure the ImageView can be garbage collected

imageViewReference = new WeakReference(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 所引用的资源可以被GC(garbage collected)。因为当任务结束时不能确保 ImageView 仍然存在,因此你必须在 onPostExecute() 里面去检查引用。这个ImageView 也许已经不存在了,例如,在任务结束时用户已经不在那个Activity或者是设备已经发生配置改变(旋转屏幕等)。

开始异步加载位图,只需要创建一个新的任务并执行它即可:

public void loadBitmap(int resId, ImageView imageView) {

BitmapWorkerTask task = new BitmapWorkerTask(imageView);

task.execute(resId);

}

处理并发问题(Handle Concurrency)

通常类似 ListView 与 GridView 等视图组件在使用上面演示的AsyncTask 方法时会同时带来另外一个问题。为了更有效的处理内存,那些视图的子组件会在用户滑动屏幕时被循环使用。如果每一个子视图都触发一个AsyncTask ,那么就无法确保当前视图在结束task时,分配的视图已经进入循环队列中给另外一个子视图进行重用。而且, 无法确保所有的异步任务能够按顺序执行完毕。

Multithreading for Performance 这篇博文更进一步的讨论了如何处理并发并且提供了一种解决方法,当任务结束时ImageView 保存一个最近常使用的AsyncTask引用。使用类似的方法, AsyncTask 可以扩展出一个类似的模型。创建一个专用的 Drawable 子类来保存一个可以回到当前工作任务的引用。在这种情况下,BitmapDrawable 被用来作为占位图片,它可以在任务结束时显示到ImageView中。

创建一个专用的Drawable的子类来储存返回工作任务的引用。在这种情况下,当任务完成时BitmapDrawable会被使用,placeholder image才会在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() 方法来取消之前的一个任务. 在小部分情况下, New出来的任务有可能已经存在,这样就不需要执行这个任务了。下面演示了如何实现一个 cancelPotentialWork 。

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() 方法里面做更新操作:

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 方法。例如,在GridView 中实现这个方法会是在 getView() 方法里面调用。

时间: 2024-10-21 19:13:30

非UI线程处理Bitmap的相关文章

Android自定义组件系列【12】——非UI线程绘图SurfaceView

一.SurfaceView的介绍 在前面我们已经会自定义View,使用canvas绘图,但是View的绘图机制存在一些缺陷. 1.View缺乏双缓冲机制. 2.程序必须重绘整个View上显示的图片,比较耗资源. 3.非UI线程无法更新View组件,所以会占用主线程资源,当需要在主线程中处理逻辑的时候会很慢. 在Android中为我们提供了一个SurfaceView来替代View实现绘制图形,一般在游戏绘图方面应用较广,所以如果是比较复杂的绘图建议使用SurfaceView. 二.SurfaceV

Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面

Android应用的开发过程中需要把繁重的任务(IO,网络连接等)放到其他线程中异步执行,达到不阻塞UI的效果. 下面将由浅入深介绍Android进行异步处理的实现方法和系统底层的实现原理. 本文介绍Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面: 即如何使用Thread+Handler的方式从非UI线程发送界面更新消息到UI线程. 概述:每个Android应用程序都运行在一个dalvik虚拟机进程中,进程开始的时候会启动一个主线程(MainThread),

Android UI线程和非UI线程

UI线程及Android的单线程模型原则 当应用启动,系统会创建一个主线程(main thread). 这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和Android的UI组件(components from the Android UI toolkit (components from the android.widget and android.view packages))发生交互. 所以main thread也叫UI thread也即UI线程. 系统不会为

5.UI线程和非UI线程的交互方式

这里说的交互方式应该指的是如何在非UI线程中修改UI线程中的组件.      一般来说有三种方式: 1.Activity.unOnUiThread(Runnable) 如果当前线程是UI Thread,立马执行action.run方法:否则将Runnable发送到UI Thread的event 队列中. 2. view.post(Runnable) 将action加入到UI thread 的message queue. 3.view.postDelayed(Runnable,long)同2一样,

学习通过Thread+Handler实现非UI线程更新UI组件

[Android线程机制] 出于性能考虑,Android的UI操作并不是线程安全的,这就意味着如果有多个线程并发操作UI组件,可能导致线程安全问题.为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件 当一个程序第一次启动时,Android会同时启动一条主线程(Main Thread),主线程主要负责处理与UI相关的事件,如用户的按键事件,用户接触屏幕的事件及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理.所以主线程通常又被叫做UI线程 [H

非UI线程加载图片

Processing Bitmaps Off the UI Thread 非UI线程加载图片 BitmapFactory.decode*一系列方法,在之前的高效加载大图的文章中讲到过. 如果图片的数据源是磁盘,或则网络(内存以外的其他地方),那么解析图片的方法不应该在UI线程中执行.这些数据加载任务所要花费的时间有许多不可控因素,(例如:磁盘读取速度,图片的大小,CPU的频率,等等)如果这些任务阻塞了UI线程,系统判定你的应用程序无响应,用户是有权关闭你的软件的,这样的用户体验非常不好. 这篇文

Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面

目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+Looper+MessageQueue深入详解Android异步处理四:AsyncTask的实现原理 Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面 概述:每个Android应用程序都运行在一个dalvik虚拟机进程中,进程开始的时候会启动一个主线程(MainTh

Android异步机制一:使用Thread+Handler实现非UI线程更新UI界面

概述:每个Android应用程序都运行在一个dalvik虚拟机进程中,进程开始的时候会启动一个主线程(MainThread),主线程负责处理和ui相关的事件,因此主线程通常又叫UI线程.而由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作.如果在非UI线程直接对UI进行了操作,则会报错: CalledFromWrongThreadException only the original thread that created a view hierarchy can tou

android脚步---如何看log之程序停止运行,和UI线程和非UI线程之间切换

经常运行eclipse时,烧到手机出现,“停止运行”,这时候得通过logcat查log了.一般这种情况属于FATAL EXCEPTION,所以检索FATAL 或者 EXCEPTION,然后往下看几行 例子: 11-26 16:18:17.949: E/AndroidRuntime(5363): FATAL EXCEPTION: Thread-19311-26 16:18:17.949: E/AndroidRuntime(5363): Process: com.scme.jiance, PID: