Android的消息机制
本篇是个人在阅读安卓开发艺术探索一书后的知识归纳以及分享自己的一些理解所写的博客,在于以后可以方便的进行复习相关知识,毕竟这些基础知识是多多去理解的。
一、Android的消息机制概述
Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQue和Looper的工作过程。同样线程默认是没有Looper的,创建Handler是需要先创建Looper。但主线程(UI线程),就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是可以在主线程直接使用Handler的原因。下面简单介绍了一下消息机制的几个类,具体内容通过下一节源码分析。:
MessageQueue:消息队列,它的内部存储了一组消息,对外提供插入和读取消息的工作。它的内部存储结构是通过单链表来实现的。
Looper:消息循环,Looper会以无限循环的形式去消息队列中查找是否有新消息,如果有的话就处理消息,否则就一直等待。
ThreadLocal:ThreadLocal是线程内部的数据存储类,作用是在每个线程中存储数据,我们知道Handler的创建需要当前线程的Looper,通过ThreadLocal就可以轻松获取每个线程的Looper。
Handler:Handler主要包含信息的发送和接受过程,发送消息通过post的一系列方法以及send的一系列方法来实现,post最终也是通过send实现,在Looper中获取消息后处理消息,这就是在Looper的线程中去处理任务了。
Handler的主要作用是将一个任务切换到某个指定线程中去执行。Android中规定只能在主线程中访问UI,在子线程中访问UI就会抛出异常。ViewRootImpl对UI操作做了验证,通过ViewRootImpl的checkThread方法来玩完成。但有时候我们需要在子线程中执行耗时操作例如通过网络获取数据,获取完后需要改变UI信息,这时就可以通过Handler将UI操作切换到主线程中去执行。
延申一下,为什么不能在子线程中访问UI呢?
因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能导致UI控件处于不可预期的状态。
那为什么系统不对UI控件加上锁机制呢?原因有两个:
1.加上锁机制会让UI访问逻辑变得复杂;
2.锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
下面简单介绍一下Handler的工作原理:
首先Handler的创建需要当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper,那么就会报错。
Handler创建完毕后就可以与内部的Looper以及MessageQueue协同工作了,然后通过Handler的post方法将一个Runnable投递到消息队列中,也可以通过send方法发送一个消息,post其实也是通过send方法完成的,在send方法中调用MessageQueue的enqueueMessage方法将这个消息添加到消息队列中,Looper会调用loop方法开启一个无限循环,其中调用MessageQueue的next方法从消息队列中取出一条消息并将该消息从消息队列中移除,同样MessageQueue.next()方法也是无限循环,如果没有消息会阻塞在那里导致loop也阻塞在那里,当有消息就取出调用Handler的dispatchMessage方法来处理消息或Runable。下一节通过源码了解具体内容,其次下面是Handler的工作过程图示:
二、Android的消息机制分析
2.1ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。一般使用场景,当某些数据是以线程为作用域并且不同数据有不同数据副本的时候,可以考虑使用ThreadLocal。例如Handler需要获取当前线程的Looper,很显然Looper的作用域就是线程且不同线程有不同的Looper,这就可以通过ThreadLocal来存取。另一个使用场景是复杂逻辑下的对象传递,就不细讲了。
ThreadLocal使用:
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();
mThreadLocal.set(true);
Log.d(TAG,"Thread:main"+mThreadLocal.get());
首先通过泛型初始化一个ThreadLocal对象,通过set进行存储数据,通过get获取数据,在不同线程通过同一个ThreadLocal对象进行存储不同的数据,取出的也是各线程存储的数据,可以自己去写写。ThreadLocal为什么会这样,这是因为在get方法中不同线程操作的不同的ThreadLocal.Values对象的数组,很显然每一个线程有不同的Values对象其操作的数组也不同,这就是为什么ThreadLocal可以在不同线程中存储不同数据且取出时互不干扰。
下面看看ThreadLocal的内部实现,ThreadLocal是一个泛型类,它的定义为public class ThreadLocal<T>,看看它的set和get方法:
ThreadLocal的set方法:
publci void set(T value) {
Thread currentThread = Thread.currentThread();
//获取当前线程的ThreadLocal.Values对象
Values values = values(currentThread);
//values对象为空就初始化
if (values == null) {
values = initializeValue(currentThread);
}
//调用put方法传入ThreadLocal对象和值,根据ThreadLocal的reference获取index
//值存入数组table[index + 1]中
values.put(this, value);
}
在Thread类中有一个成员专门用于存储线程的ThreadLocal的数据:TheradLocal.values localValues,所以获取当前线程的ThreadLocal数据就很简单了,直接通过当前线程获取Values对象,如果为空就初始化,之后存储数据,在Values中有一个数组:private Object[] table,调用Values的put方法,传入自身ThreadLocal对象和值,值就存储在这个table数组中,具体存储过程不分析,从源码可以看出一个存储规则,那就是ThreadLocal的值在table数组中的存储位置总是为ThreadLocal对象(通过put传进来的对象)的reference字段所标识的对象的下一个位置,如ThreadLocal的reference对象在数组的索引为index,值的索引就是index+1,最终值就会存在table数组中:table[index + 1] = value;
ThreadLocal的put方法:
public T get() {
Thread currentThread = Thread.currentThread();
//获取当前线程的ThreadLocal.Values对象
Values values = values(currentThread);
//values对象为空就初始化
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValue(currentThread);
}
return (T) values.getAfterMiss(this);
}
get方法也很清晰,同样是获取当前线程的Values对象,如果对象为空初始化,返回初始值,默认是null;不为空则同样是找出ThreadLocal的reference对象在table数组中的位置index,index+1就是存储数据的位置,返回这个值就可以。
从ThreadLocal的set和get方法可以看出,他们操作的对象都是当前线程Thread的ThreadLocal.Values类的localValues对象的table数组,因此在不同线程访问同一个ThreadLocal的set和get方法,所存或取数据都是对应于各自线程内部的Values对象,这也就是为什么ThreadLocal可以在多个线程中互不干扰的存储和修改数据。
2.2消息队列的工作原理
MessageQueue就是消息队列,内部存储了一组消息。MessageQueue主要提供两个操作:插入和读取,读取包含删除的操作,插入和读取对应的方法为enqueueMessage和next,其中enqueueMessage作用是向消息队列中插入一条消息,next作用是从消息队列中读取一条消息并将其从消息队列中移除。MessageQueue实际上是通过一个单链表来维护消息队列的,因为单链表在插入和删除上比较有优势。下面看看enqueueMessage和next方法:
enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
//msg就是要新插入的消息
msg.markInUse();
msg.when = when;
//消息队列也就是单链表的头结点
Message p = mMessages;
boolean needWake;
//链表为空时,单链表就是新插入的消息
if (p == null || when == 0 || when < p.when) {
msg.next = p;
//将新插入的消息赋值给单链表
mMessage = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//通过循环遍历单链表获取尾结点,新插入的消息就插入到尾结点后
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//遍历至尾结点退出循环
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//从上面遍历可以看出,prev为尾结点,p为null
msg.next = p;
//将新入的消息插入到链表尾部
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
}
从enqueueMessage源码来看,它主要实现的就是单链表的插入操作,如果单链表为空,新消息就是头结点,单链表不为空,遍历整个链表至尾部,将消息插入到尾部。
next方法:
Message next() {
...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMills = 0;
//开启无限循环,如果没有消息会阻塞在那里
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//头结点
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next();
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now,
Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
//链表头结点变为原头结点的next,因为要取出消息并移除
mMessages = msg.next;
}
//断开原头结点的next
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
//返回原头结点,这就取出了消息并从消息列表中移除了该消息
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
...
}
...
}
}
从next方法源码中可以看出,next是一个无限循环的方法,如果消息队列也就是链表中没有消息,next方法会一直阻塞在那里(loop也会阻塞)。当有新消息到来时,next会返回这条消息并将其从链表中移除。
2.3Looper的工作原理
Looper就是消息循环,具体来说就是调用loop方法后,开启无限循环,不停的从MessageQueue中查看是否有新消息,如果有就立刻处理,否则一直阻塞在那里。首先看一下它的构造方法,在构造方法中它会创建一个MessageQueue即消息队列,然后将当前线程的对象保存起来,如下:
private Looper(boolean quitAllowed) {
//创建消息队列及当前线程的对象
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
线程默认是没有Looper的,但我们知道Handler的创建需要当前线程的Looper,否则会报错,那么如何创建一个Looper呢,通过Looper.prepare() 即可为当前线程创建一个Looper,接着通过 Looper.loop() 来开启消息循环,如下:
new Thread(new Runnalbe{
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}).start();
}
Looper还提供了 prepareMainLooper 方法,这个方法主要是给主线程也就是ActiityThread创建Looper使用的,本质也是通过prepare方法实现的,ActivityThread被创建时就会初始化Looper,所以主线程可以直接使用Handler。因为主线程Looper比较特殊,所以Looper提供了一个 getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。
Looper也是可以退出的,通过 quit 和 quitSafely 方法可以退出一个Lopper,二者的区别是,quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有的消息处理完毕后才安全退出。Looper退出后通过Handler发送的消息会失败,这时Handler的send方法返回false。在子线程中,如果手动创建了Looper,那么在所有事情完成以后应该调用quit方法来终止消息循环,否则这个线程就会一直处于等待的状态(上面讲了没有消息MessageQueue.next()阻塞,Looper.loop()也阻塞,下面loop源码也会讲),而如果退出Looper以后,这个线程就会立刻终止(线程中的任务完成线程就会结束),因此建议不需要的时候终止Looper。
Looper调用loop方法后消息循环才会真正起作用,实现如下:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Lopper; Lopper.prepare() wasn't called on this
thread.");
}
//获取Looper内部的消息队列
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentify();
final long ident = Binder.clearCallingIdentity();
//开启无限循环,不断的调用MessageQueue.next()方法取出消息,进行处理
for (;;) {
//通过next方法获取消息
Message msg = queue.next();
//如果MessageQueue.next为空时跳出循环
if (msg == null) {
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//msg.target就是发送这条消息的Handler对象,调用Handler对象的dispatchMessage方法处理消息
msg.target.dispatchMessage(msg):
if (logging != null) {
logging.pringln("<<<<< Finished to " + msg.target + " " +
msg.callack);
}
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" +msg.what);
}
msg.recycleUnchecked();
}
}
loop过程如上,loop方法是一个死循环,唯一跳出循环的条件是MessageQueue的next方法返回null,当Looper的quit/quitSafely方法调用时就会调用MessageQueue的quit或quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null(quit是直接退出,quitSafely会先将消息队列已有的消息处理完再退出)。loop方法中调用MessageQueue的next方法来获取新消息,之前讲过next方法同样是一个死循环阻塞操作,如果没有消息,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue.next()返回了新消息,Looper就会处理这条消息,通过msg.target.dispatchMessage(msg),这里的msg.target就是发送这条消息的Handler对象,这样Handler发送的消息就交给了它的dispatchMessage方法来处理了。但这里是在创建Handler时所使用的Looper中执行的,这就成功将代码逻辑切换到指定的线程去执行了。
2.4Handler的工作原理
Handler的工作主要包括消息的发送和接受过程。消息的发送可以通过post的一系列方法传递Runnalbe,以及send的一系列方法来实现,post的一些列方法最终也是通过send一些列方法来实现的(Runnable为Message的callback)。发送一条消息的典型如下:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//获取消息队列
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called
with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.targe = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//发送消息调用消息队列的enqueueMessage方法,向队列中插入一条消息
return queue.enqueueMessage(msg, uptimeMillis);
}
从上述源码中可以看到,Handler发送消息的过程仅仅是调用消息队列的enqueueMessage方法向队列中插入一条消息,在loop方法中通过MessageQueue.next方法获取这条消息,Looper收到消息后开始处理,交给Handler处理,即Handler的dispatchMessage方法会被调用,看看这个方法:
public void dispatchMessage(Message msg) {
//callback就是通过post传递的Runnable,如果有callback直接调用handleCallback方法处理
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果创建Handler时指定了Callback对象(Callback实现了handleMessage方法),
//就调用它的handleMessage方法
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//如果没指定Callback对象,一般就是继承Handler重写handleMessage方法时,就调用重写的
handleMessage(msg);
}
}
从dispatchMessage方法源码来看,Handler处理消息过程,首先,检查Message的callback是否为null,不为null就通过handleCallback方法来处理消息。Message的callback是一个Runnable对象,实际上就是Handler的post方法传递Runnable参数。handleCallback逻辑如下:
private static void handleCallback(Message message) {
//直接调用Runnable对象的run方法
message.callback.run();
}
如果callback为null,接下来检查mCallback是否为null,不为null就调用mCallback的handleMessage方法来处理消息,一般创建Handler时可以指定Callback对象作为参数。Callback是个接口,如下:
//Callback接口
public interface Callback {
public boolean handleMessage(Message msg);
}
//在创建Handler时如果指定了Callback对象就是这种情况
Handler handler = new Handler(new Callback(){
//重写
public boolean handleMessage(Message msg) {
...
}
});
那么Callback的意义在哪,那是因为可以用来创建一个Handler实例但不需要派生Handler的子类来实现handleMessage方法。在日常开发中,最常见的方式就是派生一个Handler的子类并重写其handleMessage方法来处理具体的消息,而Callback给我们提供了一种使用Handler的方式,当我们不想派生子类时,就可以通过Callback的实现,创建Callback对象实现handleMessage方法。
最后,调用Handler的handleMessage方法来处理消息。整个过程可以如下面流程图示:
下面看一个Handler的一个默认构造方法public Handler(),这个构造方法会调用下面的构造方法。很明显,如果当前线程没有Lopper的话就会抛出"Can’t create handler inside thread that has not called Lopper.prepare()"这个异常。这也解释了没有Looper创建Handler抛出异常的原因:
//调用默认构造方法时就会调用下面的构造方法
public Handler(Callback callback, boolean async) {
...
mLopper = Lopper.myLopper();
if (mLooper == null) {
//没有Looper抛出异常
throw new RuntimeException(
"Can't create handler inside thread that has not called Lopper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
三、主线程的消息循环
这个内容较简单简述一下,主要就是在主线程中的入口main方法中通过Looper.prepareMainLooper()方法创建Looper和MessageQueue,并通过Looper.loop()来开启主线程的消息循环。消息循环开始以后,ActivityThread需要一个Handler,这个Handler就是ActivityThread.H,它内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。
ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS通过IPC完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread然后向H发消息,H收到消息切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。