Android消息机制理解
研究可以Android的消息机制,觉得还是自己亲自来记录下来才印象深刻
简单认识
Android的消息机制最多的用处就是在子线程中去更新UI,四个主要成员:Looper、Handler、Message和MessageQueue
下面自己一一写出自己的见解:
Loop
先抛开loop的作用,我们看看loop的初始化。每个线程里只能有一个loop,而且每个loop只能初始化一次!
下面我来证实这个:
可以看到我在主线程开启looper初始化时就会抛异常,看看looper源码!
Image_02.png说的很清楚,如果looper已经初始化后,再初始化的时候就会报错Only one Looper may be created per thread,
因为主线程已经初始化过looper,我们看看ActivityThread里的源码: Image_03.png
初始化调用了Looper的prepareMainLooper(),跟进去看:
Image_03.png这里有疑问系统是怎么区分主线程的loop和子线程的loop呢,这就要去看Loop源码里的loop存放了,
Image_04.pngThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。
从loop源码可知在创建loop时调用了:
调用了ThreadLocal,看看ThreadLocal的set方法
Image_06.png Image_07.png在上面的set方法中,首先会通过getMapt方法来获取当前线程中的ThreadLocal数据,如果获取呢?其实获取的方式也是很简单的,在Thread类的内容有一个成员专门用于存储线程的ThreadLocal的数据,因此获取当前线程的ThreadLocal数据就变得异常简单了。如果ThreadLocalMap的值为null,那么就需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
再来Loopr如何get()
Image_08.png跟随进去看
Image_09.png这样我们就可以在不同的线程里获取不同的loop了,这样loop也初始化了,也可以获取了;
Handler
Handler业务逻辑的三种方式,它们分别是重写handleMessage(Message msg)方法、实现Handler.Callback接口和实现Runnable接口。三种方式最终都是在Looper所在的线程中执行的,是我们执行异步操作的地方,比如更新主线程的UI
重写handleMessage(Message msg)方法,也是我们经常用的,
我们跟随
handler.sendEmptyMessage(0);
进入到 handler的源码,找到核心代码:
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);
}
当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。
Message
Message是消息载体,它的作用就是存储执行某一个任务所需要的数据和实现接口,该接口是Runnable、Handle.Callback或者Handler.handleMessage(Message msg)
Message的获取
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
该方法就是获取Message的关键,其他obtain()的重载方法最后调用的都是该方法,只是有传入参数进行初始化赋值或者进行浅拷贝而已。方法内部首先定义了一个同步块synchronized防止多线程操作时出现两个以上的线程同时申请同一个Message对象。在同步快内部进行的是Message的获取操作,如果消息池不为null(sPool != null),就从链表(消息池)中获取一个Message对象,并且将sPool指向下一个元素,同时链表的长度减一。如果消息池中没有任何可用的Message对象,就直接实例化一个新的Message对象。
MessageQueue
MessageQueue既消息队列,Handler将Message发送到消息队列中,消息队列会按照一定的规则取出要执行的Message。
添加消息
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(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 根据when的比较来判断要添加的Message是否应该放在队列头部。
// 当第一次添加消息的时候,测试队列为空,所以该Message也应该
// 位于队列的头部。
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;
// 不断遍历消息队列,根据when的比较找到适合插入Message的位置。
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;
}
将Message加入到消息队列中的操作也很简单,就是遍历消息队列中的所有消息,根据when的比较找到适合添加Message的位置。
四者关系
当然从源头loop说起:
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 (;;) {
//可能会阻塞,因为next()方法可能会无限循环
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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();
}
}
跟随到MessageQueue中的next()的方法中去:
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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
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(TAG, "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;
}
}
当loop得到消息后就会分发消息
public static void loop() {
...
msg.target.dispatchMessage(msg);
...
因为Message发送时已经设置了tag,那就是发送者handler.然后在调用消息分发,利用原来的handler发送消息.
Image_10.png Image_11.png我们自己实现了这个方法,可以操作更新UI