Handler消息机制
一、概述
在安卓开发里面,当子线程在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成,也不是调用Thread.wait()或是Thread.sleep()。安卓采取的方法是,主线程为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。
一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。Android里面,线程分两种:
主线程:也叫UI线程,或称ActivityThread,或者MainThread,用于运行四大组件和处理他们用户的交互。 ActivityThread类管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。
ActivityThread既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。
子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。更新UI的工作必须交给主线程,在安卓里子线程是不允许更新UI的。
二、几个基本概念:
1、消息机制:就是不同线程之间的通信机制或规则。
2、安卓的消息机制:简单的讲就是就是 Handler的运行机制。
3、Handler 运行机制的作用:有效避免ANR的发生,一旦发生ANR,程序就Crash了。
4、触发ANR的条件: a、在activity中超过5秒的时间未能响应下一个事件;b、BroadcastReceive超过10s未响应。以上a和b两个条件任一个出现都会引发ANR。而造成a、b两点的原因有很多,比如网络请求, 大文件的操作, 复杂的耗时计算等。
如何避免ANR:A、主线程不能执行耗时操作;B、子线程不直接更新UI界面(UI界面更新方式可参阅下问《Android开发中更新UI的几种常用方式》http://blog.csdn.net/haoyuegongzi/article/details/78406342)。
5、Handler的四角生死恋:
1)、Message:用于存放消息的对象,消息发送的载体。
2)、MessageQueue:消息队列(单表链的方式实现),用来存放通过Handler发布的消息,管理Message,遵循先进先出的原则。
3)、Handler:负责处理消息,将Message添加到消息队列以及对消息队列中的Message进行处理。
4)、Looper:一个死循环,扮演MessageQueue和Handler之间纽带的角色,循环取出MessageQueue里面的Message,并交付给相应的Handler进行处理。
简而言之,Handler消息机制主要就是就是Handler、Looper、MessageQueue、Message*一台戏。
三、Handler的工作机制概要:
1、Handler通过其对象调用sendxxx方法插入一条信息到MessageQueue;
2、Looper不断轮询调用MeaasgaQueue的next方法;
3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()。大致过程见下图。
四、Handler的工作机制详解:
1、Looper对象的创建
在应用App启动的时候,会在执行程序的入口ActivityThread.class类中主函数public static void main(String[] args)里面会创建一个Looper对象:Looper.prepareMainLooper(),然后Looper.loop();完成Looper对象的创建(下面红色部分代码)。实际上.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这里要注意一点就是:在子线程中通过关键字new创建的Handler对象时,Looper对象是没有被创建的,直接使用的话,会报异常。如果要在子线程中创建Handler对象,那么就需要在创建前添加代码:Looper.prepare();来解决问题。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
}
}).start();
原因:
在我们new一个Handler的时候,调用了Handler的构造方法,其构造方法主要如下:
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到在if语句判断mLooper是否为空前,调用了mLooper = Looper.myLooper();来获取mLooper对象,如果Looper对象为空,则会抛出一个运行时异常。
那什么时候Looper对象才可能为空呢?我们来看看判断mLooper是否为空的if语句的前一段代码myLooper的源码:
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
这里通过sThreadLocal的get方法获取了一个不为空的mLooper对象。提到get方法,自然就会想到set方法,并给sThreadLocal设置Looper对象,通过前面在子线程中创建Handler对象可知,Looper.prepare()方法是给sThreadLocal设置Looper对象的根本所在。Prepare()如下:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
可以看到,首先判断sThreadLocal中Looper对象是否为空,如果为空new一个新的Looper并设置进去。这样也就完全解释了为什么在子线程中我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。
Looper的作用:通过一个while(true)de 死循环,不断轮询MessageQueue,有新的消息就交给Handler处理。而MessageQueue对象是在其构造方法中创建的:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
2、Handler发送消息
使用Handler发送消息的流程想必不多说了。直接上代码:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.arg1 = 1;
bundle.putString("data", "data");
message.setData(bundle);
handler.sendMessage(message);
}
}).start();
Handler调用sendMessage方法后把Message发送到哪里去了呢?又如何在Handler的handleMessage()方法中重新得到这条Message呢?
Handler的send系列方法:
sendEmptyMessage(int what);
sendEmptyMessageDelayed(int what, long delayMillis);
sendEmptyMessageAtTime(int what, long uptimeMillis);
sendMessage(Message msg);
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
sendMessageAtFrontOfQueue(Message msg);
除了sendMessageAtFrontOfQueue()方法之外,其它发送消息的方法最终都会辗转调用到sendMessageAtTime()方法中。源码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}else {
RuntimeException e = new RuntimeException(" sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
sendMessageAtTime()方法接收两个参数, msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。
这里MessageQueue,前面提到过就是消息队列,通过表单链的形式用于保存和管理Meassage对象,并提供入队和出队的方法,遵循先进先出的原则。它的对象是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
enqueueMessage()方法字面翻译过来就是入(en: entrance)队的方法了:
final boolean enqueueMessage(Message msg, long when) {
if (msg.when != 0) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null && !mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit");
}
synchronized (this) {
if (mQuiting) {
String exception = " sending message to a Handler on a dead thread";
RuntimeException e = new RuntimeException(msg.target + exception);
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else if (msg.target == null) {
mQuiting = true;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
this.notify();
}
}
return true;
}
从源码可以知道,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间就是我们前面介绍的uptimeMillis参数。操作上就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。如果你是通sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。
明白另外入队操作,再看看出队操作。提到出队操作,就要考虑到我们前面说到的四角生死恋中的Looper对象的loop()方法,源码如下:
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next();
if (msg != null) {
if (msg.target == null) {
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
从源码可以看到,整个while 循环都是一个死循环,不断地调用的MessageQueue的next()方法,也就是前面讲到的消息队列的出队方法。每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中(第7行)。这里的msg.target,其实就是Handler。不信,我们来看看Handler中dispatchMessage()方法的源码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果msg.callback 不为空,则调用mCallback的handleCallback()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样就可以解释为什么在Handler中复写它的handleMessage()方法就可以获取到之前发送的消息!
为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler通常是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。