http://blog.joycode.com/ghj/archives/2011/05/06

在有界面的Android应用中,后台异步执行一些事情是常见的场景,这时候我们从底层开始写起的话,就需要了解比较深层的东西,比如这篇文章“Android 的消息队列模型”提到的Looper、Handler、Message、MessageQueue。

Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。

AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。

AsyncTask定义了三种泛型类型 Params,Progress和Result。

  • Params 启动任务执行的输入参数,比如HTTP请求的URL。
  • Progress 后台任务执行的百分比。
  • Result 后台执行任务最终返回的结果,比如String。

使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

  • doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
  • onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回

有必要的话你还得重写以下这三个方法,但不是必须的:

  • onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
  • onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
  • onCancelled() 用户调用取消时,要做的操作

使用AsyncTask类,以下是几条必须遵守的准则:

  • Task的实例必须在UI thread中创建;
  • execute方法必须在UI thread中调用;
  • 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法;
  • 该task只能被执行一次,否则多次调用时将会出现异常;

一个超简单的理解 AsyncTask 的例子:

这个例子后台sleep一定时间后更新前段进度条的进度,当完成后把进度条隐藏掉。

layout 文件

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="fill_parent"android:layout_height="fill_parent"><ProgressBar android:id="@+id/progress_bar"android:layout_width="200dip" android:layout_height="200dip"android:layout_gravity="center" android:max="100" android:progress="0"></ProgressBar></LinearLayout>


代码文件

package ghj1976.AsyncTask;import android.app.Activity;import android.os.AsyncTask;import android.os.Bundle;import android.view.View;import android.widget.ProgressBar;public class AsyncTaskActivity extends Activity {public ProgressBar pBar;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);pBar = (ProgressBar) findViewById(R.id.progress_bar);// AsyncTask.execute()要在主线程里调用new AsyncLoader().execute((Void) null);}// 设置三种类型参数分别为 Params = Void, Progress = Integer,Result = Voidclass AsyncLoader extends AsyncTask<Void, Integer, Void> {@Overrideprotected Void doInBackground(Void... params) {publishProgress(0);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}publishProgress(50);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}publishProgress(100);return null;}@Overrideprotected void onPostExecute(Void v) {pBar.setVisibility(View.INVISIBLE);}@Overrideprotected void onProgressUpdate(Integer... values) {pBar.setProgress(values[0]);}}}


说明:

JAVA中类型后面跟三个点,表示的是可变数组参数

http://k.techq.com/a/java/20110111/23352.html

一些其他的例子可以看下面文章:

http://lichen.blog.51cto.com/697816/486868

参考资料:

Android AsyncTask
http://lichen.blog.51cto.com/697816/486868

Android AsyncTask 的使用
http://changxianli6121.blog.163.com/blog/static/56392130201042712956597/

从Foursquare看手机程序设计(3)
http://zhanwc.iteye.com/blog/834777

Android List列表网络资源异步调用
http://cai75951577.iteye.com/blog/793736

关于Android线程模型与AsyncTask
http://www.eoeandroid.com/thread-7607-1-1.html

在学习"Android异步加载图像小结"这篇文章时, 发现有些地方没写清楚,我就根据我的理解,把这篇文章的代码重写整理了一遍,下面就是我的整理。

下面测试使用的layout文件:

简单来说就是 LinearLayout 布局,其下放了5个ImageView。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="fill_parent"android:layout_height="fill_parent"><TextView android:text="图片区域开始" android:id="@+id/textView2"android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView><ImageView android:id="@+id/imageView1"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView2"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView3"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView4"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><ImageView android:id="@+id/imageView5"android:layout_height="wrap_content" android:src="@drawable/icon"android:layout_width="wrap_content"></ImageView><TextView android:text="图片区域结束" android:id="@+id/textView1"android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView></LinearLayout>


我们将演示的逻辑是异步从服务器上下载5张不同图片,依次放入这5个ImageView。上下2个TextView 是为了方便我们看是否阻塞了UI的显示。

当然 AndroidManifest.xml 文件中要配置好网络访问权限。

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

Handler+Runnable模式

我们先看一个并不是异步线程加载的例子,使用 Handler+Runnable模式。

这里为何不是新开线程的原因请参看这篇文章:Android Runnable 运行在那个线程这里的代码其实是在UI 主线程中下载图片的,而不是新开线程。

我们运行下面代码时,会发现他其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。

package ghj1976.AndroidTest;import java.io.IOException;import java.net.URL;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.os.Handler;import android.os.SystemClock;import android.util.Log;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage(http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage("http://cache.soso.com/30d/img/web/logo.gif, R.id.imageView3);loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage("http://www.cnblogs.com/images/logo_small.gif",R.id.imageView5);}private Handler handler = new Handler();private void loadImage(final String url, final int id) {handler.post(new Runnable() {public void run() {Drawable drawable = null;try {drawable = Drawable.createFromStream(new URL(url).openStream(), "image.gif");} catch (IOException e) {Log.d("test", e.getMessage());}if (drawable == null) {Log.d("test", "null drawable");} else {Log.d("test", "not null drawable");}                                // 为了测试缓存而模拟的网络延时                                 SystemClock.sleep(2000); ((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);}});}}


Handler+Thread+Message模式

这种模式使用了线程,所以可以看到异步加载的效果。

核心代码:

package ghj1976.AndroidTest;import java.io.IOException;import java.net.URL;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.SystemClock;import android.util.Log;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage2("http://www.cnblogs.com/images/logo_small.gif",R.id.imageView5);}final Handler handler2 = new Handler() {@Overridepublic void handleMessage(Message msg) {((ImageView) MainActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable) msg.obj);}};// 采用handler+Thread模式实现多线程异步加载private void loadImage2(final String url, final int id) {Thread thread = new Thread() {@Overridepublic void run() {Drawable drawable = null;try {drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");} catch (IOException e) {Log.d("test", e.getMessage());}// 模拟网络延时SystemClock.sleep(2000);Message message = handler2.obtainMessage();message.arg1 = id;message.obj = drawable;handler2.sendMessage(message);}};thread.start();thread = null;}}


这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去。

Handler+ExecutorService(线程池)+MessageQueue模式

能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。

这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService下面的演示例子是创建一个可重用固定线程数的线程池。

核心代码

package ghj1976.AndroidTest;import java.io.IOException;import java.net.URL;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.SystemClock;import android.util.Log;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage3("http://cache.soso.com/30d/img/web/logo.gif",R.id.imageView3);loadImage3("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage3("http://www.cnblogs.com/images/logo_small.gif",R.id.imageView5);}private Handler handler = new Handler();private ExecutorService executorService = Executors.newFixedThreadPool(5);// 引入线程池来管理多线程private void loadImage3(final String url, final int id) {executorService.submit(new Runnable() {public void run() {try {final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");// 模拟网络延时SystemClock.sleep(2000);handler.post(new Runnable() {public void run() {((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);}});} catch (Exception e) {throw new RuntimeException(e);}}});}}


这里我们象第一步一样使用了 handler.post(newRunnable() { 更新前段显示当然是在UI主线程,我们还有 executorService.submit(newRunnable() { 来确保下载是在线程池的线程中。

Handler+ExecutorService(线程池)+MessageQueue+缓存模式

下面比起前一个做了几个改造:

  • 把整个代码封装在一个类中
  • 为了避免出现同时多次下载同一幅图的问题,使用了本地缓存

封装的类:

package ghj1976.AndroidTest;import java.lang.ref.SoftReference;import java.net.URL;import java.util.HashMap;import java.util.Map;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import android.graphics.drawable.Drawable;import android.os.Handler;import android.os.SystemClock;public class AsyncImageLoader3 {// 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务private final Handler handler = new Handler();/** * * @param imageUrl *            图像url地址 * @param callback *            回调接口 * @return 返回内存中缓存的图像,第一次加载返回null */public Drawable loadDrawable(final String imageUrl,final ImageCallback callback) {// 如果缓存过就从缓存中取出数据if (imageCache.containsKey(imageUrl)) {SoftReference<Drawable> softReference = imageCache.get(imageUrl);if (softReference.get() != null) {return softReference.get();}}// 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中executorService.submit(new Runnable() {public void run() {try {final Drawable drawable = loadImageFromUrl(imageUrl); imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));handler.post(new Runnable() {public void run() {callback.imageLoaded(drawable);}});} catch (Exception e) {throw new RuntimeException(e);}}});return null;}// 从网络上取数据方法protected Drawable loadImageFromUrl(String imageUrl) {try {// 测试时,模拟网络延时,实际时这行代码不能有SystemClock.sleep(2000);return Drawable.createFromStream(new URL(imageUrl).openStream(),"image.png");} catch (Exception e) {throw new RuntimeException(e);}}// 对外界开放的回调接口public interface ImageCallback {// 注意 此方法是用来设置目标对象的图像资源public void imageLoaded(Drawable imageDrawable);}}


说明:

final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。参看:Java关键字final、static使用总结

这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的,更详细的可以参看:内存优化的两个类:SoftReference 和 WeakReference

前段调用:

package ghj1976.AndroidTest;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.widget.ImageView;public class MainActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif",R.id.imageView2);loadImage4("http://cache.soso.com/30d/img/web/logo.gif",R.id.imageView3);loadImage4("http://csdnimg.cn/www/images/csdnindex_logo.gif",R.id.imageView4);loadImage4("http://www.cnblogs.com/images/logo_small.gif",R.id.imageView5);}private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();// 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程private void loadImage4(final String url, final int id) {// 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行Drawable cacheImage = asyncImageLoader3.loadDrawable(url,new AsyncImageLoader3.ImageCallback() {// 请参见实现:如果第一次加载url时下面方法会执行public void imageLoaded(Drawable imageDrawable) {((ImageView) findViewById(id)).setImageDrawable(imageDrawable);}});if (cacheImage != null) {((ImageView) findViewById(id)).setImageDrawable(cacheImage);}}}


参考资料:

Android异步加载图像小结

http://blog.csdn.net/sgl870927/archive/2011/03/29/6285535.aspx

Runnable 并不一定是新开一个线程,比如下面的调用方法就是运行在UI主线程中的:

Handler mHandler=new Handler();mHandler.post(new Runnable(){@Overridepublic void run() {// TODO Auto-generated method stub}});


官方对这个方法的解释如下,注意其中的:“The runnable will be run on the user interface thread. ”

boolean android.view.View .post(Runnable action)

Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread.

Parameters:

action The Runnable that will be executed.

Returns:

Returns true if the Runnable was successfully placed in to the message queue. Returns false on failure, usually because the looper processing the message queue is exiting.

我们可以通过调用handler的post方法,把Runnable对象(一般是Runnable的子类)传过去;handler会在looper中调用这个Runnable的Run方法执行。

Runnable是一个接口,不是一个线程,一般线程会实现Runnable。

有关 Looper、Handler,Thread 关系可以看这篇博客:

Android 的消息队列模型

http://www.cnblogs.com/ghj1976/archive/2011/05/06/2038469.html

这里我们看代码 mHandler.post(newRunnable(){ 好像是new 了一个 interface, 其实是new的一个实现Runnable的匿名内部类(Inner Anonymous Class),这是很简练的写法。

上面的代码可以看成是: new anonymousClass() implement interface{ [改写interface method]}

Runnable是一个接口,不是一个线程,一般线程会实现Runnable。 所以如果我们使用匿名内部类是运行在UI主线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。

具体来说,这个函数的工作原理如下:

View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

如下图,前面看到的代码,我们这里Message的callback为一个Runnable的匿名内部类

这种情况下,由于不是在新的线程中使用,所以千万别做复杂的计算逻辑。

参考资料:

Android中的Handler, Looper, MessageQueue和Thread

http://www.cnblogs.com/xirihanlin/archive/2011/04/11/2012746.html

Android系列之Message机制的灵活应用

http://tech.ddvip.com/2010-07/1280393505158258_3.html

Android 的消息队列模型

Android是参考Windows的消息循环机制来实现Android自身的消息循环的。
Android通过Looper、Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。
Android系统中,Looper负责管理线程的消息队列和消息循环。我们可以通过Loop.myLooper()得到当前线程的Looper对象,通过Loop.getMainLooper()可以获得当前进程的主线程的Looper对象。
一个线程可以存在(当然也可以不存在)一个消息队列和一个消息循环(Looper)。
Activity是一个UI线程,运行于主线程中,Android系统在启动的时候会为Activity创建一个消息队列和消息循环(Looper)。
Handler的作用是把消息加入特定的(Looper)消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定则利用当前线程的Looper创建。
Activity、Looper、Handler,Thread的关系如下图所示:

一个Activity中可以创建多个工作线程或者其他的组件,如果这些线程或者组件把他们的消息放入Activity的主线程消息队列,那么该消息就会在主线程中处理了。

因为主线程一般负责界面的更新操作,并且Android系统中的widget不是线程安全的,所以这种方式可以很好的实现Android界面更新。在Android系统中这种方式有着广泛的运用。

那么一个线程怎样把消息放入主线程的消息队列呢?答案是通过Handle对象,只要Handler对象以主线程的Looper创建,那么调用Handler的sendMessage等接口,将会把消息放入队列都将是放入主线程的消息队列。并且将会在Handler主线程中调用该handler的handleMessage接口来处理消息。

更多Android消息队列的信息请参看:http://my.unix-center.net/~Simon_fu/?p=652

下面这个图从另外一个角度描述了他们的关系:

参考资料:

Android异步加载图像小结
http://blog.csdn.net/sgl870927/archive/2011/03/29/6285535.aspx

深入理解Android消息处理系统——Looper、Handler、Thread

http://my.unix-center.net/~Simon_fu/?p=652

Android线程模型(Painless Threading)
http://android.group.iteye.com/group/blog/382683

android线程控制UI更新(Handler 、post()、postDelayed()、postAtTime)
http://lepeng.net/blogger/?p=21

Android – Multithreading in a UI environment
http://www.aviyehuda.com/2010/12/android-multithreading-in-a-ui-environment/

Android中的Handler, Looper, MessageQueue和Thread
http://www.cnblogs.com/xirihanlin/archive/2011/04/11/2012746.html

Android Runnable
http://blog.csdn.net/michaelpp/archive/2010/06/30/5704682.aspx

更多相关文章

  1. Android中Handler源码解析(一)
  2. Android深入浅出之Binder机制
  3. [原]采用MQTT协议实现Android消息推送
  4. Android面试系列文章2018之Android部分IntentService机制篇
  5. android 加载图片轻松避免OOM(out of memory)
  6. Android(安卓)Configuration属性解析
  7. 彻底理解android binder通信架构
  8. Android(安卓)之 Looper Handler Message 之间的关系
  9. AsynTask 异步任务

随机推荐

  1. 文件过多时ls命令为什么会卡住?
  2. php数据类型的转换与检测技术, 变量声明与
  3. 理解格式化原理
  4. FM实现F4帮助系列一:常规实现
  5. mysql 中 group by 用法
  6. ALV显示红绿灯(FM ALV 和 OO ALV两种实现
  7. FM实现F4帮助系列二:联动的搜索
  8. 系统发布用户通知以及相关增强
  9. FM实现F4帮助系列三:弹出框多筛选条件的搜
  10. 航电oj2001-2003