[Android]简略的Android消息机制源码分析
相关源码
framework/base/core/java/andorid/os/Handler.java
framework/base/core/java/andorid/os/Looper.java
framework/base/core/java/andorid/os/Message.java
framework/base/core/java/andorid/os/MessageQueue.java
libcore/luni/src/main/java/java/lang/ThreadLocal.java
1. 为什么需要消息机制?
首先我们知道Android中有两个特别重要的机制,一个是Binder,另一个是消息机制(Handler + Looper + MessageQueue)。毕竟Android是以消息驱动的方式来进行交互的。
- 为什么需要在android中添加这样的消息机制呢?
有时候我们需要在子线程中进行好事的I/O操作,可能是读取文件或访问网络,当耗时操作完成以后可能要在UI上做一些改变,但由于Android的开发规范,不可以在子线程上更新UI,否则就会触发程序异常,这个时候就可以通过Handler来将更新UI的操作切换到主线程中执行。因此,本质上来说Handler并不是专门用于更新UI的,他只是常常被开发者用来更新UI。
- 为什么不可以在子线程中访问UI呢?
这是因为Android的UI空间不是线程安全的,如果在多线程中并发访问可能导致UI空间处于不可预期的状态。当然也不可以在UI空间的访问加锁,原因有两个,首先加锁会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
鉴于这两个缺点,最简单搞笑的方法就是采用单线程模型来处理UI操作,对于开发者来说也不是很麻烦,只是需要通过Handler来切换一下UI访问的执行线程即可。
2.消息机制概述
Message: 消息分为硬件产生的消息和软件产生的消息
MessageQueue:消息队列的主要功能向消息池投递消息-enqueueMessage和取走消息池的消息-next;
Handler:消息辅助类,主要功能向消息池发送各种消息事件-sendMessage和处理相应消息事件-handleMessage;
Looper:不断循环执行-loop,按分发机制将消息分发给目标处理者。
首先需要在主线程中建立一个Handler对象,并重写Handler.Message(),并调用sendMessage(),将之存到MessageQueue中,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回handleMessage()中,这是一个无限循环。Looper若没有新消息需要处理则会进入等待状态,直到有消息需要处理为止。
简单来说
- Looper 中有MessageQueue
- MessageQueue中有一组待处理的Message
- Message有一个用于处理消息的Handler
- Handler中有Looper和MessageQueue
3.消息机制分析
Android的消息机制主要包括Hanler、Looper、MessageQueue和ThreadLocal
3.1 ThreadLocal工作原理
ThreadLocal是一个线程内部的数据存储类,可以理解为一个HashMap。
当某些数据是以先成为作用域并且不同线程具有不用的数据副本的时候,就可以采用ThreadLocal。
对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用于就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表提供Handler查找指定的Looper。最后还是和ThreadLocal一个道理。
ThreadLocal常用的就两个方法,get和set。set就没什么好说的,我们主要需要了解get。通过get,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value,因而ThreadLocal可以在不同的线程中维护一套数据的副本而且不会彼此干扰。
接着我们来看set和get的源码,通过这两个就可以大致明白它的工作原理。
首先看set的源码
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
Values values(Thread current) {
return current.localValues;
}
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
通过这段代码可以知道,通过使用Thread的方法localValues来获取当前ThreadLocal的数据,
localValues的值通过put就可以存在它内部的一个table数组中。
通过阅读put的源码,我们可以知道ThreadLocal的值在table中的存储位置总是为了ThreadLocal的reference字段所标识的对象的下一个位置,即table[index+1] = value。
再来看get。
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
这个的逻辑就相当清楚了,前两步和set一样,接着判断value是否为null,如果是null,则返回由initializeValues设定的初始值,否则就返回table[index+1]的值。
3.2 MessageQueue的工作原理
MessageQueue主要有两个操作,插入和读取,相对应的是enqueueMessage和next。
MessageQueue内部是采用的单链表的数据结构来维护MessageQueue的,毕竟单链表在插入和删除上面比较有优势。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
enqueueMessage的主要操作就是单链表的插入操作了
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
next则是读取,并且这是一个无限循环的方法,如果消息队列中没有消息,那么next则会一直阻塞。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。
3.3 Looper的工作原理
(1)创建消息循环
prepare()用于创建Looper消息循环对象。Looper对象通过一个成员变量ThreadLocal进行保存。
(2)获取消息循环对象
myLooper()用于获取当前消息循环对象。Looper对象从成员变量ThreadLocal中获取。
(3)开始消息循环
loop()开始消息循环。循环过程如下:
每次从消息队列MessageQueue中取出一个Message
使用Message对应的Handler处理Message
已处理的Message加到本地消息池,循环复用
循环以上步骤,若没有消息表明消息队列停止,退出循环
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static Looper myLooper() {
return sThreadLocal.get();
}
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
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();
}
}
3.4 Handler的工作原理
(1)发送消息
Handler支持2种消息类型,即Runnable和Message。因此发送消息提供了post(Runnable r)和sendMessage(Message msg)两个方法。从下面源码可以看出Runnable赋值给了Message的callback,最终也是封装成Message对象对象。学姐个人认为外部调用不统一使用Message,应该是兼容Java的线程任务,学姐认为这种思想也可以借鉴到平常开发过程中。发送的消息都会入队到MessageQueue队列中。
(2)处理消息
Looper循环过程的时候,是通过dispatchMessage(Message msg)对消息进行处理。处理过程:先看是否是Runnable对象,如果是则调用handleCallback(msg)进行处理,最终调到Runnable.run()方法执行线程;如果不是Runnable对象,再看外部是否传入了Callback处理机制,若有则使用外部Callback进行处理;若既不是Runnable对象也没有外部Callback,则调用handleMessage(msg),这个也是我们开发过程中最常覆写的方法了。
(3)移除消息
removeCallbacksAndMessages(),移除消息其实也是从MessageQueue中将Message对象移除掉。
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public final Message obtainMessage()
{
return Message.obtain(this);
}
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
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.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
代码里面有个Callback,跟踪一下会发现这是一个接口,它是用于创建一个Handler的实例但并不需要派生Handler的子类,在日常开发中,最常见的方式就是派生一个Handler的子类并重写其handleMessage来处理具体消息,而Callback给我们提供了另一种使用Handler的方式。总结一下Handler的消息处理流程如下图
最后用一张图来说明Message、Handler、Looper之间的关系。
4.为什么Handler采用的是管道?
一些教科书上面写着Binder是IPC,而Handler则是线程间通信,管道是属于进程通信,Handler不可能使用它。实际上呢,Handler的确是采用的管道,不自己去深入探究,这样子学习是不完整的。
首先我们来了解一下管道,其本质是也是文件,但又和普通的文件会有所不同:管道缓冲区大小一般为1页,即4K字节。管道分为读端和写端,读端负责从管道拿数据,当数据为空时则阻塞;写端向管道写数据,当管道缓存区满时则阻塞。
在Looper.loop方法,会不断循环处理Message,其中消息的获取是通过
Message msg = queue.next(); //用于获取消息队列中的下一条消息
该方法中会调用nativePollOnce()方法,这便是一个native方法,再通过JNI调用进入Native层,便采用了管道,比如epoll_create/epoll_wait/epoll_ctl,这里通过一副图来让大家看清楚Java层与native的联系。
那么问题来了,既然是同一个进程间的线程通信,为何需要管道呢?
线程之间内存共享,通过Handler通信,消息池的内容并不需要从一个线程拷贝到另一个线程,
因为两线程可使用的内存时同一个区域,都有权直接访问,当然也存在线程私有区域ThreadLocal(这里不涉及)。即然不需要拷贝内存,那管道是何作用呢?
Handler机制中管道作用就是当一个线程A准备好Message,并放入消息池,这时需要通知另一个线程B去处理这个消息。线程A向管道的写端写入数据1(对于老的Android版本是写入字符W
),管道有数据便会唤醒线程B去处理消息。管道主要工作是用于通知另一个线程的,这便是最核心的作用。
至于为什么handler为何采用管道而非Binder?
首先要明确handler不采用Binder,并非binder完成不了这个功能,对于两个具有独立地址空间的进程通信都可以,当然也能用于共享内存空间的两个线程间通信,而是太浪费CPU和内存资源了。Binder采用C/S架构,往往用于不同进程间的通信。
从内存角度:通信过程中还涉及一次内存拷贝,handler机制中的Message根本不需要拷贝,本身就是在同一个内存。Handler需要的仅仅是告诉另一个线程数据有了。
从CPU角度,为了Binder通信底层驱动还需要为何一个binder线程池,每次通信涉及binder线程的创建和内存分配等比较浪费CPU资源。
最后handler消息机制能否用于进程间通信?答案是不能,Handler只能用于共享内存地址空间的两个线程间通信,即同进程的两个线程间通信。