在上文中Android API 31 Handler机制源码解读(一)主要看了Handler的创建相关的内容,今天是时候一起看看Hanlder能干的两件事情了,按照顺序,先看发送(send)。
上文的demo中,发送所使用的方法为sendEmptyMessage(0),实际上,Hanlder的各种send方法以及post方法,归根结底都是调用到了sendMessageAtTime这个方法,中间的中转过程,其实也很简单,跟着源码点几下就能到,就是这个调那个,那个再调那个,,,最终反正是调sendMessageAtTime,哈哈哈,咱看下sendMessageAtTime庐山真面目,
好吧,其实sendMessageAtTime干的事情也不多,真正的幕后大佬,是enqueueMessage。这里有个mQueue的MessageQueue对象,这个我们在上文中已经知道,是在Handler构建的时候,给Handler指定的Looper对象中的queue,看看enqueueMessage
这个方法第一句就干了一件大事情,重点说一下,把this(也就是当前的Handler对象的引用)赋给了msg.target,在上文的分析中咱已经知道有这个结论:
1. Handler与线程,是多对一的关系,即一个Handler只能属于一个线程,但是一个线程可以有很多Handler。
2. 线程,Looper,MessageQueue,这三者是一一对应的,互相之间均不存在一对多的关系。
我们知道,Message的处理是在Handler的handleMessage方法里进行的,那么就有这么一个问题了,假如说一个线程中有2个handler对象,2个handler分别都有自己的handleMessage方法,handler1发送了Message A,B,C,handler2发送了D,E,那怎么保证A,B,C就进入handler1的handleMessage进行处理,而不是进入handler2的呢?
咱们坐飞机的时候托运行李箱,然后下飞机的时候呢,去出口那个大转盘那里再取行李,每个人拿自己的,都能拿到不会错(这里不考虑坏家伙恶意乱拿别人行李的情况哈),为什么呢,因为在托运手续的时候工作人员用胶带把我们的名字写在了行李箱上(即使2位乘客的行李箱一模一样也不会错),在转盘那里,按照名字来拿就可以了。
那这里其实Handler也是这么干的,把自己的名字写在了Message里面即:msg.target = this,后面我们会了解到,就是根据这个target来进行分发的(简直跟坐飞机托运行李箱的情景一模一样,哈哈哈)。
msg.workSourceUid只在system server中用到,msg.setAsynchronous设置msg是否为异步,这2个事情,咱暂且不管他,我们暂时只关注Message怎么交出去(发送)的,下面调到了MessageQueue的enqueueMessage方法,这个方法简而言之,就是把Message放到了MessagQueue里,但是MessageQueue是很多人公用的(机场取行李的转盘是大家公用的),所以涉及到同步等细节问题需要处理,我们来看看,这个方法位于android/os/MessageQueue.java Line 549.
boolean enqueueMessage(Message msg, long when) {
//行李交到上飞机处发现有个漏网之鱼,居然没写名字,当然是不予办理
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {//大家公用的队列,所以要同步,不然乱套了
//明明已经上过飞机了,居然又来了一次,肯定是坏蛋干的,不能随便给上
//这种情况发生在一个Message已经被enqueue一次,又重复被enqueue,
//Message对象不要重复使用,用Message之前重新obtain一个即可
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
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();//已经enqueue的,做个标记,不能重复enqueue
msg.when = when;//设定的Message期望被handle的时间点
//把当前queue的第一个节点(头),保存到p
//这里用p来命名,很显然是用了老铁们都熟知的领域统一命名法,目的是期望得到大家的共鸣,
//所以一起鸣起来,写这个代码的老铁绝非外行,哈哈,
//学习过数据结构的老铁们都知道,操作链表的时候会有移动指针,那个指针就被命名为p
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//空表,直接添加即可,wake逻辑咱们这里暂且忽略他,
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//非空,需要进行插入操作
//同步屏障的内容咱后续分析,从developer api过来,needWake是false
// 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 (;;) {//移动p指针,一直移到合适的位置
prev = p;
p = p.next;
//因为这个Message是有期望被handle的时间的,所以当然根据时间来决定顺序
//找到合适的位置了
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//插入,链表常规操作,数据结构全是这个操作套路,
//p,pre,next,树的话还有chlid,parent啥的,满天飞,
//就是烦,其实一点不复杂,哈哈
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// wake先忽略他
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
好了,Message已经被放到Queue里去了。等到时机成熟,就可以执行了。
时间仓促,水平有限,欢迎老铁同行们批评指正,讨论交流,敬请关注后续。