Handler既然是一种消息机制,那它肯定会有个存放消息的数据结构,没错它就叫Message。
Message的链表结构
在分析Handler之前,基于Message是链表结构的,先来写一个链表。
public class MyMessage{
public String what;
//用一个全局变量保存下一个MyMessage的引用。
public MyMessage next
}
简单的链表通过一个变量next来保存下一个数据的引用,那么只要拥有头结点,就可以获取所有的链表数据了。
简单地概括一下Handler的消息机制运作。
首先是在某个线程上,我叫它线程A。
啊这个线程A他去吃韩国烤肉
- 线程A去吃韩国烤肉,韩国烤肉都有个炉子,要有这个炉子并且打开它才能开始吃,这个炉子就是Looper,所以线程A一开始要创建一个Looper。而且对于一个人只有一个烤炉,你进店说要两个烤炉可是要被赶出去的。
所以对于一个线程来说,有且仅有一个Looper。 - 韩国烤肉好多肉吃的,有雪花牛肉,猪颈肉,牛欢喜,猪大肠等等,这些肉就是Message了。
- 韩国烤肉有个好处就是有服务员小姐姐帮你烤,线程A手指着这个雪花牛肉说,我要烤这个,线程A的hand就是handler了,handler.enqueueMessage这个操作就相当于线程A手指这个雪花牛肉告诉服务员小姐姐说我要烤这个,实际上是服务员小姐姐把肉放烤炉里面烤的,她就是MessageQueue。
- 烤炉Looper不停地烤啊烤,然后小姐姐MessageQueue看到肉快熟了,就拿出来(MessageQueue.next())。拿出来后线程A的手(hand)就可以去拿肉message来吃了,也就是hander的处理,所以handler还得处理消息,自己点的牛欢喜,喊着泪也要自己吃完。
- 肉在小姐姐没拿出来之前都不能吃,因为生的不能吃(执行时间未到),所以MessageQueue.next()有阻塞操作。
- 烤炉上的这么多肉就是消息池,链表结构的一些message,他们衔接在一起,按时间顺序,早烤早熟。
好了简单扯完后继续深入分析。
Looper
以在应用程序的入口ActivityThread.Main()开始。
在main函数执行之前,系统已经为应用程序创建了一个进程。一个进程开始肯定会有一个线程,这个就是程序的主线程。
public static void main(String[] args) {
...
//为主线程创建Looper
Looper.prepareMainLooper();
}
接着看prepareMainLooper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
//不允许重复创建
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper并存入sThreadLocal,一个线程只有一个Looper
sThreadLocal.set(new Looper(quitAllowed));
}
在看下Looper的构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
构造函数里面实例化了一个MessageQueue对象。(就是一个烤炉给你安排了一个服务员小姐姐)。并且保存了当前线程。对于在ActivityThread来说就是应用进程的主线程。
MessageQueue
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();//mPtr记录native消息队列的信息,NativeMessageQueue的指针
}
native初始化并返回了NativeMessageQueue的指针给java层保存起来。
继续看一下MessageQueue有什么重要的变量和方法。
public final class MessageQueue {
Message mMessages;
Message next() {
...
}
boolean enqueueMessage(Message msg, long when) {
...
}
}
mMessages其实贯穿了next和enqueueMessage。
这是当然的,放消息和取消息肯定是离不开消息对象
服务员小姐姐烤肉,放和拿的都是肉。
那么先来看看这肉。
Message
public final class Message implements Parcelable {
public int what;
int flags;
long when;
Bundle data;
Handler target;
Runnable callback;
Message next; //有需要的时候通过next衔接成一个链表。
public static Message obtain() {
synchronized (sPoolSync) {
//先从缓存池里面拿,判断缓存池的头节点也同样是message类型的sPool是否为null
if (sPool != null) {
Message m = sPool; //sPool不为null,用m存起来,return用
sPool = m.next;//把缓存池的头节点sPool指向原来头节点的下一个 因为原来准备被使用了,下一个obtain操作智能使用原来的next的message
m.next = null;//断开原来头节点对下一个next的指向,方便释放内存
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();//头节点为null,代表缓存池没有,需要new一个message
}
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) { //缓存池最大数量为MAX_POOL_SIZE 50
next = sPool; //这里是把执行recyle操作的message的next指向当前缓存池的头结点
sPool = this; // 然后把执行recyle操作的message变成缓存池的头结点,典型的链表操作
sPoolSize++;
}
}
}
Message的obtain和recycleUnchecked两个方法实现了一个缓存池,是个以链表结构实现的队列,先进先出。每一次都从缓存池的头节点取message容器,用完了就放回缓存池的头节点并衔接上之前的缓存池的头部。
再回到MessageQueue
MessageQueue 的发消息
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.");
}
msg.target就是handler,一般而言handler不能为空。
msg.isInUse()是判断是否在使用中,inUse() true的话说明已经在被使用了,不能重复处理。
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;
}
判断MessageQueue是否退出了,对于在主线程而言,这个mQuitting 是false的不能退出,如果退出了,就把msg.recycle掉然后return不继续执行。
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 {
//异步消息,消息屏障等
- 首先通过makeInuse把传进来的msg 标志成inUse避免重复发。
- 把msg的时间设置成传进来的when时间戳。
- 新增一局部变量指向mMessages实例。
- needWake 是否需要唤醒阻塞的布尔值。
- 判断p,即mMessages为null的话就是之前没有任何消息,传进来的msg就是最新的消息。
- 时间戳为0,或者传进来的when也就是msg的when是否比mMessages的when时间戳要小,即可以认为msg就是要处理的最新消息。
- msg.next = p; 把p接在最新要处理的消息的next处,衔接起来,这里相当于把msg放到链表头,next指向旧的mMessages。
- mMessages = msg,看得出mMessages其实就是消息池的链表头,这里把mMessages更新成msg。
- needWake = mBlocked 这里我先告诉你应该是true。
- else 部分是处理异步消息或者消息屏障的。先忽略。
接着就是执行nativeWake
if (needWake) {
nativeWake(mPtr);
}
这里是唤醒在某个在阻塞等待的地方。因为有消息msg了,然通知别人来拿。
好比服务员小姐姐看到肉烤熟了。
MessageQueue 取消息
Message next() 方法
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
还没初始化就return null。
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);//阻塞操作,用于提取消息队列中的消息
永不停止的循环里,有nativePollOnce个阻塞住,前面的nativeWake就是在这里唤醒的,通过ptr来一对一关联。
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();//获得当前的时间戳
Message prevMsg = null; //局部变量preMsg,初始值为null
Message msg = mMessages;// 消息池的链表头也就是最新要处理的消息的对象的引用赋给msg。
此时msg就是最新要处理的消息。
//消息屏障
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());
}
上面是消息屏障,消息也是个message不过没有target。正常的消息不会走这里因为target都不为null。
消息屏障这里是要从链表头部开始遍历,直到找到msg.isAsynchronous()为true也就是异步消息,这个异步消息后面就可以通过preMsg.next来获取。msg = msg.next,也就是说msg就是要处理的最新异步消息。
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不为空就是代表msg 是最新的异步消息
prevMsg.next = msg.next; // prevMsg->msg->msg.next 这里相当于把msg remove了,变成了prevMsg->msg.next()
} else {// 这里是普通的同步消息处理
mMessages = msg.next;//要返回的是msg,这里把消息池链表头更新为msg.next,就是链表头指向下一条消息。
}
msg.next = null; //断开msg跟消息池得连接方便释放内存
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); //标志成已使用
return msg;
}
}
-
首先判断时间,now < msg.when ,就是说你这个msg还没到时间执行
-
if (prevMsg != null) {//prevMsg不为空就是代表msg 是最新的异步消息
(1). prevMsg.next = msg.next; // prevMsg->msg->msg.next 这里相当于把msg remove了,变成了prevMsg->msg.next() -
if (prevMsg != null) 为false // 这里是普通的同步消息处理
(1). mMessages = msg.next;//要返回的是msg,这里把消息池链表头更新为msg.next,就是链表头指向下一条消息。 -
msg.next = null;//断开msg跟消息池的连接。
-
msg.markInUse(); //标志成已使用 最后返回。
以上一个普通的msg在MessageQueue里面的收发就完成了。就是说小姐姐把肉放进烤炉烤熟了然后放一边让你拿了。
next()下半段的的pendingIdleHandler处理
在取消息的里还有当if (msg != null) 为false的逻辑,这里代表这没有消息处理了。对于在ActivityThread的Looper的MessageQueue而言,就是主线程闲置了,此时MessageQueue会处理一些任务。
就好比,肉都放烤炉上了,小姐姐没事干了,她去拿点调味料或者配料给你(免费加芝士,我吃!)
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);
这里就是把判断有没有Idle任务,这个我们开发的时候是可以自己定义idle任务和添加的。如果有没有就下次了(下次一定),并把mBlocked = true,状态设置为不阻塞,好让在enqueueMessage能执行唤醒操作。
把这些任务放在mPendingIdleHandlers数组。
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);
}
}
}.
遍历数组并执行每一个Idler的queueIdle方法。Idler任务的queueIdle有个返回值,如果这个返回值是false,就mIdleHandlers.remove(idler);就代表只执行一次。
我们自定义Idler任务的话可以在queueIdle做想在线程闲置的时候做的操作,如果需要不停地在一闲置就执行的话,把queueIdle的返回值return true即可。
好比跟小姐姐说,你一有空就帮我拿点芝士吧...
Handler
终于来到线程A的手了。
吃韩国烤肉你除手点点要烤什么;=> Handler.enqueueMessage
还要自己含泪吃完; ==> Handler.handleMessage(Message msg)
一般是这样的。
Handler.enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
handler封装了好几个发送消息的方法,包括post(Runnable r),sendEmptyMessage(int what),sendMessage(Message msg)等等最终都是调用了enqueueMessage方法。
而enqueueMessage也很简单直接使用了MessageQueue的enqueueMessage,前面已经分析过了。
小伙子想法很多,最后还是手点点让MessageQueue小姐姐来烤了。
msg.target = this;
把发送消息的handler,放在msg的tartget变量上了。
这块肉你哪只手点的,想怎么吃?!
Handler.handleMessage
public void handleMessage(Message msg) {
}
这里是完全由子类实现,你想怎么吃都可以。(建议雪花牛肉卷起芝士然后占点沙茶酱加酱油,手动拇指)。
不过可能有人不明白为什么最后在这里处理消息,其实这只是一般情况,大多数是在这里处理的。
下面再看看Looper。
再回到Looper
loop()
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
最后会执行msg.target.dispatchMessage(msg),也就是从MessageQueue获得最新要处理的message的target执行dispatchMessage(msg)。
前面handler发消息的时候msg.target = this就把handler放在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 不为空的话就执行handleCallback(msg);里面是
private static void handleCallback(Message message) {
message.callback.run();
}
执行了message.callback.run();
也就是说如果message本身有callback的话就执行它的callback方法,就好比这块肉是块全肥的,天然的callback就是让它一边去吧。然后小姐姐烤完了,就让它一边去了。
- if (mCallback != null) 就执行mCallback.handleMessage(msg)并且return不执行下面了。
看看mCallback这个全局变量在哪里初始化
public Handler(Callback callback, boolean async) {
mCallback = callback;
}
这里看得出我们可以用一个Callback来处理这些msg。
雪花牛肉给女朋友吃,我吃雪花
- handleMessage(msg); 如果前面两个都不是的话就执行handleMessage了。这也是一般情况的用法。
关于唤醒
是否能nativeWake的布尔值,needWake = mBlocked;在发送普通消息的时候,是直接把mBlocked这个值赋给needWake的。可这就奇怪了。
阅读源码的时候发现 mBlocked = true;只有在pendingIdleHandlerCount <= 0 也就是说闲置的时候才会为true。那么逻辑上好像说不通。
- 虽说可能在刚MessageQueue创建的时候就进入闲置状态的话,这个值设置为true后,有新消息过来因为needWake = mBlocked == true,可以执行唤醒。
- 但是发送完第一条后,在next获取到msg后就执行mBlocked = false;所以即使后面连续发好几条过来,也是不会唤醒的。
- 按我翻阅网上的blog,nativePollOnce如果没有nativeWake的话是会一直阻塞的。
- 有个叫removeSyncBarrier的方法有nativeWake方法,其实去掉消息屏障。消息屏幕一般是在屏幕刷新用的。刷新完了就把消息屏障去掉,然后唤醒,执行之前被屏障的消息。
final boolean needWake;
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {// 跳出循环的时候prev 不是消息屏障,p是消息屏障
prev = p;
p = p.next;
}
if (p == null) { // 如果发现没有消息屏障,抛出异常
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
if (prev != null) {// prev 不为空,说明消息屏障并不在链表头
prev.next = p.next; // 这个操作相当于把消息屏障 p remove掉,
needWake = false; // 这种情况不需要唤醒
} else {
mMessages = p.next; // prev 为空,证明链表头开始的就是屏障,p.next就是屏障连接者的最新的消息
needWake = mMessages == null || mMessages.target != null; // mMessages为null可以执行idle任务,或者target不为空就说明不是消息屏障也需要唤醒
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
可以看的出来,removeSyncBarrier是有唤醒操作的,说明在屏幕刷新完后会唤醒阻塞。
流程总结
韩国烤肉挺好吃的。
发消息:Looper.loop() => handler.enqueueMessage(msg) =>MessageQueue.enqueueMessage(msg)
收消息:MessageQueue.next => Looper.loop() msg =>Message.target. dispatchMessage
=> 1. handleCallback(msg);
=> 2. mCallback.handleMessage(msg)
=> 3. handler.handleMessage(msg)
网友评论