Android 消息机制
参考了一些其他人的资料,加上自己的理解,总结了一下,给自己做个备忘。
Android的消息机制有进程间通信的消息机制和进程内部线程间通信的消息机制,在这里只分析Hanlder相关的线程间通信的消息机制;可以分成三部分:消息循环、消息发送、消息处理,下面分开来讨论。
一、消息循环
在这里将消息循环分为三个阶段:消息循环的建立、消息循环的运行以及消息循环的结束。
1、消息循环的建立
在Android中,消息循环可以分为两类:主线程(即UI线程)的消息循环和子线程的消息循环。
注:进程创建的第一个线程一般就称为该进程的主线程,在需要创建Activity的进程中,UI线程通常是第一个启动的线程,所以UI线程一般也是该进程的主线程。
主线程的消息循环在当前应用的进程创建时建立。一般情况下,由launcher启动的应用通常会运行在新的进程中,大致流程如下:
应用的默认Activity在创建时,ActivityManagerService.java中
private final void startProcessLocked (ProcessRecord app, String hostingType, String hostingNameStr)
{
…
int pid = Process.start("android.app.ActivityThread",
mSimpleProcessManagement ? app.processName : null, uid, uid,
gids, debugFlags, null);
…
}
此处创建了一个新的进程并运行ActivityThread类的main方法:
public static final void main(String[] args)
{
…
Looper.prepareMainLooper();//创建主线程的消息循环和消息队列,该方法只允许主线程调用。
…
Looper.loop();//进入主线程消息循环
…
}
public static final void prepareMainLooper() {
prepare();//创建并保存looper对象(即消息循环)
setMainLooper(myLooper());//再将looper对象保存在static成员变量mMainLooper中,以便将来通过getMainLooper()方法可以获取到该对象
…
}
此处通过getMainLooper()方法获取到的是当前进程中主线程(UI线程)的消息循环;通常情况下,不同的应用运行在不同的进程中,所以应用之间不能通过直接向对方消息队列插入消息的方式来通信。
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
}
在looper类的构造方法中:
public class Looper {
private Looper() {
mQueue = new MessageQueue();//创建消息队列
mRun = true;
mThread = Thread.currentThread();
}
}
MessageQueue的构造方法:
MessageQueue() {
nativeInit();//通过JNI创建了一个navtiveMessageQueue,并在C++层创建了一个Looper对象。
}
子线程的消息循环在线程运行时创建。通常情况下,我们通过实例化Thread类并重载run()方法来得到一个新的线程,在这种情况下,run()方法执行完成之后线程就结束了,并没有消息循环的概念。要建立一个带消息循环的线程就需要利用Android提供的HandlerThread类。
HandlerThread myThread;
myThread = new HandlerThread("myThread");
myThread.start();
这样就启动了一个带有消息循环的子线程。大致流程如下:
public class HandlerThread extends Thread {
public void run() {
…
Looper.prepare();//创建looper,该过程同主线程,但因为线程局部变量的原因,看到的是属于当前线程自己的looper
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
…
Looper.loop();//进入循环
…
}
}
子线程可以通过Looper.myLooper()获取到自己的消息循环;
主线程和子线程消息循环大致是相同的,总结一下消息循环的创建过程:
1) 在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;
2) 在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;
3) 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当 Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。
前面的代码分析,只涉及到第1)点。
2、消息循环的运行
从前面可以看到,主线程ActivityThread的main方法和子线程的run方法,最终都调用loop()方法进入消息循环。
public static final void loop() {
Looper me = myLooper();//从线程局部变量中获取当前线程looper
MessageQueue queue = me.mQueue;//获取消息队列
…
while (true) {
Message msg = queue.next(); //获取队列的下一条消息
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
msg.target.dispatchMessage(msg);//消息分发
msg.recycle();//释放资源
}
}
}
其中,queue.next()除了从队列中获取消息之外,还关系到循环的等待和唤醒。
public class MessageQueue {
final Message next() {
…
nativePollOnce(mPtr, nextPollTimeoutMillis);
...
final long now = SystemClock.uptimeMillis();
final Message msg = mMessages;
if (msg != null) {
final long when = msg.when;
if (now >= when) {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
return msg;
}
}
if (pendingIdleHandlerCount < 0) {//idleHandler队列
pendingIdleHandlerCount = mIdleHandlers.size();
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
…
keep = idler.queueIdle();
…
}
}
nextPollTimeoutMillis = 0;//执行完idleHandler任务之后,需要重置等待时间,以防在执行idleHandler时有新消息产生。
}
nativePollOnce()是一个native方法作用是控制当前消息循环的等待和唤醒,nextPollTimeoutMillis参数表示等待的时间,值为-1表示当前队列没有消息,进入等待状态;mPtr参数指向nativeMessageQueue。
当队列中没有消息的时候,循环就等待在nativePollOnce()这里,有新消息产生时,循环被唤醒,并从nativePollOnce()处继续运行。
另外,需要注意的是,在查询到消息队列中没有消息且进入等待之前,还要查看是否需要执行idleHandler的任务,idleHandler是一个对立的队列。如果有应用实现了IdleHandler的Interface,则需要在睡眠之前先执行完idleHandler任务。
可以通过如下方法添加idleHandler任务:
private void addIdleHandlerDemo() {
MessageQueue queue = Looper.myQueue();
queue.addIdleHandler(new MessageQueue.IdleHandler() {
public boolean queueIdle() {
…//需要执行的任务;
return false;
}
});
}
可以看出idleHandler任务的响应不会很及时,因为它必须等到消息队列为空的时候才会被调度到。
3、消息循环的结束
当从消息队列中取得的消息满足如下条件时,消息循环就会结束:
if (msg.target == null)
{
// No target is a magic identifier for the quit message.
return;
}
调用HandlerThread对象的quit()方法,或者直接调用Looper对象的quit()方法都可以向消息队列中插入一个target==null的消息,从而终止消息循环。
二、消息发送
主线程和子线程消息的发送没有区别,可采用如下几种方式:
1、handleMessage方式
private final Handler mIntentHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what)
{
…
}
}
};
Message msg = myHandler.obtainMessage(1);
myHandler.sendMessageDelayed(msg, 2000);
通过这种方式发送消息的方法有好几个:
sendMessage(Message msg);
sendEmptyMessage(int what);
sendEmptyMessageDelayed(int what, long delayMillis);
sendEmptyMessageAtTime(int what, long uptimeMillis);
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
最终都是调用了同一个方法:
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(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
获取消息队列,并向队列中插入消息,其中时间戳uptimeMillis是消息中定义的时间戳加上当前系统时间得到的一个值。
还有一个方法sendMessageAtFrontOfQueue (Message msg),它没有调用sendMessageAtTime,而是直接调用enqueueMessage方法将消息插入到了队列头部,且时间戳为0,而这个消息也会在loop的下一次迭代中被执行到。该方法极易引起时序问题,慎用!!!
final boolean enqueueMessage(Message msg, long when) {
…
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked; // new head, might need to wake up
} else {
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
needWake = false; // still waiting on head, no need to wake up
}
…
if (needWake) {
nativeWake(mPtr);//唤醒处于等待状态的消息循环
}
}
在上面的方法中可以看出,队列中的消息是按照时间由小到大排列的,如果当前消息被插入到队列头部,则表示之前消息循环可能处于等待状态,需要将之唤醒。
2、Runnable方式
final Runnable mTestRunnable= new Runnable() {
public void run() {
…//to do
}
};
Handler mHandler = new Handler();
mHandler.postDelayed(mTestRunnable, 10000);
通过这种方式发送消息的方法有好几个:
post(Runnable r);
postAtTime(Runnable r, long uptimeMillis);
postAtTime(Runnable r, Object token, long uptimeMillis)
postDelayed(Runnable r, long delayMillis);
这种方式发送消息,本质上跟1中的方法是相同的,最终也是调用了sendMessageAtTime方法向队列中插入消息,不同的地方在于事先需要调用如下方法将Runnable封装成一个消息。
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
这里同样有一个例外的方法:
public final boolean postAtFrontOfQueue(Runnable r)
{
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
慎用!!!
总结:
从前面两种消息发送的方式可以看出,他们都是通过Handler将消息插入到了一个消息队列中,可以得出一个结论:Handler必须依附于一个具有消息循环/消息队列的线程。
Handler mHandler = new Handler();这种方式创建的handler依附于当前线程,如果当前线程没有消息队列,则会报出异常:"Can't create handler inside thread that has not called Looper.prepare()"
可以采用如下的方式让handler依附于一个子线程:
HandlerThread myThread;
myThread = new HandlerThread("myThread");
myThread.start();
Handler myHandler = new Handler(myThread.getLooper());
这样,handler就可以依附于线程myThread。
三、消息处理
消息的处理比较简单,在前面提到的消息循环中:
public static void loop() {
…
msg.target.dispatchMessage(msg);
…
}
Loop从消息队列获取到消息之后,通过dispatchMessage分发消息,而target是一个Handler类型的参数,在Handler类的sendMessageAtTime方法中:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
…
msg.target = this;
…
}
可以看出,target指向的是发送消息的Handler对象,所以消息分发的目标对象即是当时发送这个消息的Handler对象。
大致分发流程如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果消息的callback参数不为空,则执行callback指向的方法即用第二部分消息发送的第二种方式Runnable的run()方法;否则就执行第一种方式中的handleMessage()方法。