2.4 发送消息
下面我们挑几个发送方法来看下
sendMessage: 发送一个Message,when为当前的时间
MessageQueue根据when进行匹配插入位置
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;
…
return enqueueMessage(queue, msg, uptimeMillis);
}
post:从消息复用池中获取Message,设置Message的Callback
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage®, 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
postAtFrontOfQueue(): 将消息插入到队列头部
通过调用sendMessageAtFrontOfQueue 加入一个when为0的message到队列,即插入到队列的头部,需要注意的是 MessageQueue#enqueueMessage的插入到链表中时是根据when比较的(when < p.when),如果之前已经有多个when等于0的消息在队列中,这个新的会加入到前面when也为0的后面。
public final boolean postAtFrontOfQueue(Runnable r)
{
return sendMessageAtFrontOfQueue(getPostMessage®);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
…
//第三个参数为0,即Message的when为0,插入到队列的头部,注意到MessageQueue#enqueueMessage的插入到链表中时是根据when比较的(when < p.when),如果之前已经有多个when等于0的消息在队列中,这个新的会加入到前面when也为0的后面。
return enqueueMessage(queue, msg, 0);
}
2.5 派发消息 dispatchMessage
优先级如下:
Message的回调方法callback.run() >
Handler的回调方法mCallback.handleMessage(msg) > Handler的默认方法handleMessage(msg)
public void dispatchMessage(@NonNull Message msg) {
//Message的回调方法,优先级最高
if (msg.callback != null) {
handleCallback(msg);
} else {
//Handler的mCallBack优先级次之
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler的handleMessage方法优先级最低(大部分都是在该方法中实现Message的处理)
handleMessage(msg);
}
}
全局变量
//一些重要的变量
public int arg1;
public int arg2;
public Object obj;
public long when;
Bundle data;
Handler target; //Message中有个Handler的引用
Runnable callback;
//Message有next指针,可以组成单向链表
Message next;
public static final Object sPoolSync = new Object();
//复用池中的第一个Message
private static Message sPool;
//复用池的大小,默认最大50个(如果短时间内有超过复用池最大数量的Message会怎样,重新new)
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
构造方法
查看下是否有可以复用的message,如果有,复用池的中可复用的Message个数减一,返回该Message;如果没有重新new一个。注意复用池默认最大数量为50。
public static Message obtain() {
synchronized (sPoolSync) {
//查看下是否有可以复用的message
if (sPool != null) {
//取出第一个Message
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
//复用池的中可复用的Message个数减一
sPoolSize–;
return m;
}
}
//如果复用池中没有Message了重新new
return new Message();
}
recycleUnchecked
//标记一个Message时异步消息,正常的情况都是同步的Message,当遇到同步屏障的时候,优先执行第一个异步消息。关于同步屏障,我们在MessageQueue中在结合next等方法再介绍。
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
//可以复用的message为50个,如果超过了就不会再复用
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
//toString和dumpDebug可以Dump出message信息,遇到一些问题时可以帮助分析
android.os.Message#toString(long)
android.os.Message#dumpDebug
MessageQueue是一个单链表优先队列
Message不能直接添加到MessageQueue中,要通过Handler以及相对应的Looper进行添加。
变量
//MessageQueue链表中的第一个Message
Message mMessages;
next:从消息队列中取出下一条要执行的消息
如果是同步屏障消息,找到第一个队列中中第一个异步消息
如果第一个Message的执行时间比当前时间见还要晚,记录还要多久开始执行;否则就找到下一条要执行的Message。
后面的Looper的loop方法会从过queue.next调用该方法,获取需要执行的下一个Message,其中会调用到阻塞的native方法nativePollOnce,该方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做,不会因此会出现ANR,ANR和这个没有半毛钱关系。
关键代码如下:
Message next() {
//native层MessageQueue的指针
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
…
for (;😉 {
//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒
//nativePollOnce用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做,不会因此会出现ANR,ANR和这个没有半毛钱关系。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//创建一个新的Message指向 当前消息队列的头
Message msg = mMessages;
//如果是同步屏障消息,找到第一个队列中中第一个异步消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//如果第一个Message的执行时间比当前时间见还要晚,记录还要多久开始执行
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//否则从链表中取出当前的Message ,并且把链表中next指向指向下一个Message
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
//取出当前的Message的值,next置为空
msg.next = null;
msg.markInUse();
return msg;
}
}
…
//android.os.MessageQueue#quit时mQuitting为true
//如果需要退出,立即执行并返回一个null的Message,android.os.Looper.loop收到一个null的message后退出Looper循环
if (mQuitting) {
dispose();
return null;
}
…
if (pendingIdleHandlerCount <= 0) {
// 注意这里,如果没有消息需要执行,mBlocked标记为true,在enqueueMessage会根据该标记判断是否调用nativeWake唤醒
mBlocked = true;
continue;
}
…
}
…
}
enqueueMessage:向消息队列中插入一条Message
如果消息链表为空,或者插入的Message比消息链表第一个消息要执行的更早,直接插入到头部
否则在链表中找到合适位置插入,通常情况下不需要唤醒事件队列,以下两个情况除外:
-
消息链表中只有刚插入的这一个Message,并且mBlocked为true即,正在阻塞状态,收到一个消息后也进入唤醒
-
链表的头是一个同步屏障,并且该条消息是第一条异步消息
唤醒谁?MessageQueue.next中被阻塞的nativePollOnce
具体实现如下,
关于如何找到合适的位置?这涉及到链表的插入算法:引入一个prev变量,该变量指向p也message(如果是for循环的内部第一次执行),然后把p进行向next移动,和需要插入的Message进行比较when
关键代码如下:
boolean enqueueMessage(Message msg, long when) {
…
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果消息链表为空,或者插入的Message比消息链表第一个消息要执行的更早,直接插入到头部
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//否则在链表中找到合适位置插入
//通常情况下不需要唤醒事件队列,除非链表的头是一个同步屏障,并且该条消息是第一条异步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//具体实现如下,这个画张图来说明
//链表引入一个prev变量,该变量指向p也message(如果是for循环的内部第一次执行),然后把p进行向next移动,和需要插入的Message进行比较when
Message prev;
for (;😉 {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
//如果插入的是异步消息,并且消息链表第一条消息是同步屏障消息。
//或者消息链表中只有刚插入的这一个Message,并且mBlocked为true即,正在阻塞状态,收到一个消息后也进入唤醒
唤醒谁?MessageQueue.next中被阻塞的nativePollOnce
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
简单着看下native的epoll (这块还没有深入分析,后面篇章补上吧)
nativePollOnce 和 nativeWake 利用 epoll 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用
epoll_wait
, 而 nativeWake 写入一个 IO 操作到描述符
epoll属于IO复用模式调用,调用
epoll_wait
等待. 然后 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息
removeMessages: 移除消息链表中对应的消息
需要注意的是,在该函数的实现中分为了头部meg的移除,和非头部的msg的移除。
移除消息链表中头部的和需要移除相同的msg
eg:msg1.what=0;msg2.what=0;msg3.what=0; msg4.what=1; 需要移除what为0的msg,即移除前三个
移除消息链表中非头部的对应的消息,eg:msg1.what=1;msg2.what=0;msg3.what=0; 需要移除what为0的消息,即移除后续的消息,处处体现链表的查询和移除算法
关键代码如下:
void removeMessages(Handler h, int what, Object object) {
…
synchronized (this) {
Message p = mMessages;
//移除消息链表中头部的和需要移除相同的msg eg:msg1.what=0;msg2.what=0;msg3.what=0; msg4.what=1; 需要移除what为0的msg,即移除前三个
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
//移除消息链表中非头部的对应的消息,eg:msg1.what=1;msg2.what=0;msg3.what=0; 需要移除what为0的消息,即移除后续的消息,处处体现链表的查询和移除算法
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
postSyncBarrier:发送同步屏障消息
同步屏障也是一个message,只不过这个Message的target为null,. 通过ViewRootImpl#scheduleTraversals()发送同步屏障消息
同步屏障消息的插入位置并不是都是消息链表的头部,而是根据when等信息而定:如果when不为0,消息链表也不空,在消息链表中找到同步屏障要插入入的位置;如果prev为空,该条同步消息插入到队列的头部。
关键代码如下:
/**
-
android.view.ViewRootImpl#scheduleTraversals()发送同步屏障消息
-
@param when
-
@return
*/
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don’t need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
//同步屏障也是一个message,只不过这个Message的target为null
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
//如果when不为0,消息链表也不空,在消息链表中找到同步屏障要插入入的位置
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
//如果prev为空,该条同步消息插入到队列的头部
msg.next = p;
mMessages = msg;
}
return token;
}
}
dump: MessageQueue信息
有时候我们需要dump出当前looper的Message信息来分析一些问题,比不,是否Queue中有很多消息,如果太多就影响队列中后面的Message的执行,可能造成逻辑处理比较慢,甚至可能导致ANR等情况,MessageQueue的默认复用池是50个,如果太多排队的Message也会影响性能。通过dump Message信息可以帮助分析。mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
void dump(Printer pw, String prefix, Handler h) {
synchronized (this) {
long now = SystemClock.uptimeMillis();
int n = 0;
for (Message msg = mMessages; msg != null; msg = msg.next) {
if (h == null || h == msg.target) {
pw.println(prefix + "Message " + n + ": " + msg.toString(now));
}
n++;
}
pw.println(prefix + "(Total messages: " + n + “, polling=” + isPollingLocked()
- “, quitting=” + mQuitting + “)”);
}
}
Looper主要涉及到构造、prepare和loop几个重要的方法,在保证一个线程有且只有一个Looper的设计上,采用了ThreadLocal以及代码逻辑的控制。
变量
//一些重要的变量
static final ThreadLocal sThreadLocal = new ThreadLocal();
final MessageQueue mQueue;
final Thread mThread;
构造方法
在构造Looper的时候 创建和Looper一一对应的MessageQueue
private Looper(boolean quitAllowed) {
//在构造Looper的时候 new一一对应的MessageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
prepare
我们这里可以看到消息机制是 如何保证一个线程只有一个Looper。
//quitAllowed参数是否允许quit,UI线程的Looper不允许退出,其他的允许退出
private static void prepare(boolean quitAllowed) {
//保证一个线程只能有一个Looper,这里的sThreadLocal
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(quitAllowed));
}
loop
我们在MessageQueue的next方法已经分析过nativePollOnce这个方法可能会阻塞,直到拿到message。
如果next返回一个null的Message退出Looper循环,否则进行msg的派发。
取出的msg执行完之后,会加入到回收池中等待复用。recycleUnchecked我们在Message中也已经分析过了。不清楚的可以再回看。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
最后
我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。
字节高级Android经典面试题和答案
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
ed参数是否允许quit,UI线程的Looper不允许退出,其他的允许退出
private static void prepare(boolean quitAllowed) {
//保证一个线程只能有一个Looper,这里的sThreadLocal
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(quitAllowed));
}
loop
我们在MessageQueue的next方法已经分析过nativePollOnce这个方法可能会阻塞,直到拿到message。
如果next返回一个null的Message退出Looper循环,否则进行msg的派发。
取出的msg执行完之后,会加入到回收池中等待复用。recycleUnchecked我们在Message中也已经分析过了。不清楚的可以再回看。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
最后
我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。
字节高级Android经典面试题和答案
[外链图片转存中…(img-7ZgKOvkC-1714646719459)]
[外链图片转存中…(img-iBtRv48v-1714646719461)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!