在非UI线程处理Bitmap

http://my.oschina.net/ryanhoo/blog/88344

译者:Ryan
Hoo

来源:https://developer.android.com/develop/index.html

译者按: 在Google最新的文档中,提供了一系列含金量相当高的教程。因为种种原因而鲜为人知,真是可惜!Ryan将会细心整理,将之翻译成中文,希望对开发者有所帮助。

        本系列是Google关于展示大Bitmap(位图)的官方演示,可以有效的解决内存限制,更加有效的加载并显示图片,同时避免让人头疼的OOM(Out Of Memory)。

-------------------------------------------------------------------------------------

译文:

       在高效地加载Bitmap中,我们讨论了BitmapFactory.decode*系列方法,如果源数据来自硬盘或者网络(或者除内存之外的来源),是不应该在主UI线程执行的。这是因为读取这样的数据所需的加载时间是不确定的,它依赖于多种因素(从硬盘或网络的读取速度、图片的大小、CPU的功率等等)。如果这些任务里面任何一个阻塞了UI线程,系统会将你的应用标记为未响应,并且用户可以选择关闭应用(更多信息,请参阅Designing
for Responsiveness
)。

        这节课将教会你使用AsyncTask在后台线程处理Bitmap并向你展示如何处理并发问题。

使用AsyncTask(异步任务)

        AsyncTask类提供了一种简单的方法,可以在后来线程处理一些事情,并将结果返回到UI线程。要使用它,需要创建一个继承于它的子类,并且覆写它提供的方法。这里有一个使用AsyncTask和decodeSampledBitmapFromResource()加载大图片到ImageView中的例子: 

01 class BitmapWorkerTask extends AsyncTask<Integer,
Void, Bitmap> {
02     private final WeakReference<ImageView>
imageViewReference;
03     private int data
0;
04  
05     public BitmapWorkerTask(ImageView
imageView) {
06         //
Use a WeakReference to ensure the ImageView can be garbage collected
07         imageViewReference
new WeakReference<ImageView>(imageView);
08     }
09  
10     //
Decode image in background.
11     @Override
12     protected Bitmap
doInBackground(Integer... params) {
13         data
= params[
0];
14         return decodeSampledBitmapFromResource(getResources(),
data, 
100100));
15     }
16  
17     //
Once complete, see if ImageView is still around and set bitmap.
18     @Override
19     protected void onPostExecute(Bitmap
bitmap) {
20         if (imageViewReference
!= 
null &&
bitmap != 
null)
{
21             final ImageView
imageView = imageViewReference.get();
22             if (imageView
!= 
null)
{
23                 imageView.setImageBitmap(bitmap);
24             }
25         }
26     }
27 }

        ImageView的WeakReference(弱引用)可以确保AsyncTask不会阻止ImageView和它的任何引用被垃圾回收器回收。不能保证在异步任务完成后ImageView依然存在,因此你必须在onPostExecute()方法中检查引用。ImageView可能已经不存在了,比如说,用户在任务完成前退出了当前Activity或者应用配置发生了变化(横屏)。

        为了异步加载Bitmap,我们创建一个简单的异步任务并且执行它:

1 public void loadBitmap(int resId,
ImageView imageView) {
2     BitmapWorkerTask
task = 
new BitmapWorkerTask(imageView);
3     task.execute(resId);
4 }

处理并发

        常见的View(视图)组件如ListView和GridView在于AsyncTask配合使用的时候引出了另外一个问题,这个我们在上一节中提到过。为了提升内存效率,当用户滚动这些组件的时候进行子视图的回收(主要是回收不可见的视图)。如果每个子视图都触发了一个AsyncTask,无法保证在任务完成的时候,关联视图还没有被回收而被用来显示另一个子视图。此外,也无法保证异步任务结束的循序与它们开始的顺序一致。

        Multithreading
for Performance
这篇文章深入讨论了如何处理并发问题,并且给出了如何在任务结束的时候检测ImageView存储最近使用的AsyncTask引用的解决方案。使用相似的方法,可以遵循类似的模式来扩展前面的AsyncTask。

        创建一个专用的Drawable之类,用来存储worker task的引用。在这种情况下,任务结束的时候BitmapDrawable可以取代图像占位符显示在ImageView中。

01 static class AsyncDrawable extends BitmapDrawable
{
02     private final WeakReference<BitmapWorkerTask>
bitmapWorkerTaskReference;
03  
04     public AsyncDrawable(Resources
res, Bitmap bitmap,
05             BitmapWorkerTask
bitmapWorkerTask) {
06         super(res,
bitmap);
07         bitmapWorkerTaskReference
=
08             new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
09     }
10  
11     public BitmapWorkerTask
getBitmapWorkerTask() {
12         return bitmapWorkerTaskReference.get();
13     }
14 }

        在执行BitmapWorkerTask前,你需要创建一个AsyncDrawable并将之绑定到目标ImageView: 

1 public void loadBitmap(int resId,
ImageView imageView) {
2     if (cancelPotentialWork(resId,
imageView)) {
3         final BitmapWorkerTask
task = 
new BitmapWorkerTask(imageView);
4         final AsyncDrawable
asyncDrawable =
5                 new AsyncDrawable(getResources(),
mPlaceHolderBitmap, task);
6         imageView.setImageDrawable(asyncDrawable);
7         task.execute(resId);
8     }
9 }

        在上面的代码示例中引用的cancelPotentialWork方法可以检测一个执行中的任务是否与ImageView有关联。如果有关联,它将通过调用canceel()方法试图取消之前的任务。在少数情况下,新的任务中的数据与现有的任务相匹配,因此不需要做什么。下面是calcelPotentialWork的具体实现: 

01 public static boolean cancelPotentialWork(int data,
ImageView imageView) {
02     final BitmapWorkerTask
bitmapWorkerTask = getBitmapWorkerTask(imageView);
03  
04     if (bitmapWorkerTask
!= 
null)
{
05         final int bitmapData
= bitmapWorkerTask.data;
06         if (bitmapData
!= data) {
07             //
Cancel previous task
08             bitmapWorkerTask.cancel(true);
09         else {
10             //
The same work is already in progress
11             return false;
12         }
13     }
14     //
No task associated with the ImageView, or an existing task was cancelled
15     return true;
16 }

        一个助手方法,getBitmapWorkerTask(),在上面用来检索和指定ImageView相关的任务

01 private static BitmapWorkerTask
getBitmapWorkerTask(ImageView imageView) {
02    if (imageView
!= 
null)
{
03        final Drawable
drawable = imageView.getDrawable();
04        if (drawable instanceof AsyncDrawable)
{
05            final AsyncDrawable
asyncDrawable = (AsyncDrawable) drawable;
06            return asyncDrawable.getBitmapWorkerTask();
07        }
08     }
09     return null;
10 }

        最后一步是更新BitmapWorkerTask中的onPostExecute()方法,以便检测与ImageView关联的任务是否被取消或者与当前任务相匹配。 

01 class BitmapWorkerTask extends AsyncTask<Integer,
Void, Bitmap> {
02     ...
03  
04     @Override
05     protected void onPostExecute(Bitmap
bitmap) {
06         if (isCancelled())
{
07             bitmap
null;
08         }
09  
10         if (imageViewReference
!= 
null &&
bitmap != 
null)
{
11             final ImageView
imageView = imageViewReference.get();
12             final BitmapWorkerTask
bitmapWorkerTask =
13                     getBitmapWorkerTask(imageView);
14             if (this ==
bitmapWorkerTask && imageView != 
null)
{
15                 imageView.setImageBitmap(bitmap);
16             }
17         }
18     }
19 }

        这里的方法也适合用在ListView、GridView以及其他任何需要回收子视图的组件中。当你只需要为ImageView设置图片,调用loadBitmap就可以了。例如,在GridView中实现的方式是在Adapter的getView()方法中。 

时间: 2024-11-05 01:30:23

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

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

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

windows平台发消息到非UI线程.

下面的代码是介绍如何在windows平台发消息到非UI线程. 主要是'PeekMessage || GetMessage' 这两个API的应用. 当他们被调用的时候,如果当前线程还没有消息循环,就会创建一个.利用这个特性比自己手动的去创建一个消息循环要方便得多. 发消息主要是使用线程PostThreadMessage #include <iostream> #include <string> #include "cassert" #include "w

SWT 关闭窗口后 非UI线程如何关闭?

问题描述 刚刚开始学SWT,有个问题不知道如何解决public static void main(String[] args) {try {ClientLoginWindow window = new ClientLoginWindow();window.open();} catch (Exception e) {e.printStackTrace();}}public void open() {Display display = Display.getDefault();createConte

Android 关于操作UI线程

在非UI线程里访问 Android UI toolkit-这个在一个worker线程修改了 View .这会导致不可预期的结果,而且还难以调试.   为了修复这个问题,Android提供了几个方法从非UI线程访问Android UI toolkit .详见下面的这个列表: Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long)   可以使用 View.post(Runnable) 

高效地显示Bitmap图片 2 - 在UI线程之外处理Bitmaps

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

OkHttp3几个简单的例子和在子线程更新UI线程的方法

okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿.就是唯一可以更新UI的线程.这个只是点会在给okHttp填坑的时候用到.而且,这个内容本身在日常的开发中也经常用到,值得好好学一学. okHttp发起同步请求 第一个列子是一个同步请求的例子. private void performSyncHttpRequest() { OkHttpClient

关于winform中多线程调用AX控件中的方法会阻塞UI线程的问题

问题描述 本人新手...winform中子线程调用AX控件中的某个耗时的方法(3.4秒)会阻塞UI线程,请问有没有办法在子线程中创建AX控件,让子线程去执行AX控件中的方法呢? 解决方案 解决方案二:你可以尝试直接在新的线程里执行该方法,不行的话只能由控件编写者提供异步的方法解决方案三:winform中子线程调用AX控件中的某个耗时的方法(3.4秒)会阻塞UI线程既然你是在子线程里调用,为什么会阻塞UI线程?放出你的代码来解决方案四:控件是vc写的,拖到form上的,SCM_Card_Chb是控

Android后台线程和UI线程通讯实例_Android

本节向你展示如何在任务中发送数据给UI线程里的对象,这个特性允许你在后台线程工作,完了在UI线程展示结果. 在UI线程定义一个Handler Handler是Android系统线程管理框架里的一部分.一个Handler对象接收消息,并且运行代码来处理消息.正常情况下,你为新线程创建Handler,但你也可以为已有的线程创建一个Handler.当你连接Handler到UI线程时,处理消息的代码会在UI线程上运行. 在创建线程池的类的构造器里实例化Handler对象,保存在全局变量里.用Handle

详解Android中OkHttp3的例子和在子线程更新UI线程的方法

okHttp用于android的http请求.据说很厉害,我们来一起尝尝鲜.但是使用okHttp也会有一些小坑,后面会讲到如何掉进坑里并爬出来. 首先需要了解一点,这里说的UI线程和主线程是一回事儿.就是唯一可以更新UI的线程.这个只是点会在给okHttp填坑的时候用到.而且,这个内容本身在日常的开发中也经常用到,值得好好学一学. okHttp发起同步请求 第一个列子是一个同步请求的例子. private void performSyncHttpRequest() { OkHttpClient