写在前面
接着昨天写的继续吧。还是先围绕我们的Handler来写。https://blog.csdn.net/weixin_46130326/article/details/107677615
正文
昨天写到了Looper的初始化,初始化之后需要调用Looper.loop()方法,使得主线程进入一个死循环,然后通过不断的从MessageQueue中获取元素,进行操作。
里面有这么几个问题需要仔细想想
1. 既然是个死循环,为什么线程不会阻塞,占用大量的CPU资源?
2. 我们使用sendMessage传递Message给主线程,是怎么传的?
先呆着这两个问题来看吧,暂时没想到别的。
首先跟着昨天的进度,来到Looper.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;
先初始化本线程的looper,从ThreadLocal中获取当前线程的Looper对象,对了,ThreadLocal是静态的,所以在多线程使用时,也不会受到干扰。之后初始化了一堆看不太懂的东西,比如这个
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
拿到一个thresholdOverride,看起来是记录了looper所在的进程和线程
选择性的跳过一些log记录和调试用的代码(以后研究)
进入到for循环中,先取出queue中的message,为空则直接结束
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
之后进行如下操作
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
创建了一个logging,看起来是日志相关的东西,还有个Observer是个接口,里面主要有三个函数
Object messageDispatchStarting();
void messageDispatched(Object token, Message msg);
void dispatchingThrewException(Object token, Message msg, Exception exception);
看下注释
-
Called right before a message is dispatched. * * <p> The token type is not specified to allow the implementation to specify its own type. * * @return a token used for collecting telemetry when dispatching a single message. * The token token must be passed back exactly once to either * {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException} * and must not be reused again.
当Message分发之前被执行。未指定令牌类型以允许实现指定自己的类型。
-
Called when a message was processed by a Handler
当消息被程序处理时调用
-
Called when an exception was thrown while processing a message.
当处理一个message时抛异常时调用
看起来是一个作为回调,记录当前运行状态的东西
貌似也不是主要做的。接着往下看是一堆log记录的东西和一些时间节点
下面的内容是主要的流程
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
首先使用调用msg.target.dispatchMessage(msg),什么鬼,用自己的target执行自己?看了下Message里面的target是一个Handler,那这个handler是哪个handler?继续往下看,dispatchMessage()就是Handler中的内容了,如下
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这个callback先不管,没见过,调用后面的handleMessage就是我们在初始化Handler时复写的方法。这看起来已经是传进来做处理了
中间那段去哪了,target怎么设置的?handler是如何对应的?这个时候应该从我们的sendMessage来看
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
继续
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
再来
public boolean sendMessageAtTime(@NonNull 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);
}
当这个queue不为空的时候,调用enqueueMessage方法,在这里入队,往下看
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
ok,找到了,在这里我们设置了msg.target为这个handler,然后又调用queue.enqueueMessage,看下
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;
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;
}
主要看for()中的代码,是吧目标msg放到message的相应的时间位置(每个Message中都有一个when时间,相当于往排序链表中插入一个元素),message构成一个链表。
prev->msg->p
中间还有是否被阻塞的状态判断。
可以看出来,sendMessage和sendMessgeDelayed都调用了sendMessageAtTime(),然后把message和时间点传入,插入到队列中。
除了sendMessage之外,我们经常还会使用到post(Runnable r)这种类型的方式作为发送消息的方式。
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull 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);
}
这样看起来是不是和sendMessage很像呢?只是传入队列的变成了一个Runnable对象
所以重点就是这个getPostMessage(r)
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
往里看
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();
}
注释:
* * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases.
翻译:
从全局池返回一个新的消息实例。让我们在许多情况下避免分配新对象。
这段代码大家可以再结合Message这个类详细去看下
这样就是获取到一个Message,然后把这个Message的callback设置为Runnable对象,设置为对象以后也总得运行吧,那运行是在哪里呢?由于之后还是会一样还有入队操作,所以估计会在执行的的时候运行吧,带着这个猜测,回去看下dispatchMessage()
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
注意了,这段在之前出现过,当时我还不太明白这里的callback是做什么的,所以调用了post之后,其实是把Runnable对象存在了Message.callback中,然后再dispatchMessage时判断callback是否为空,再执行。
private static void handleCallback(Message message) {
message.callback.run();
}
这下真相大白了,运行确实是在dispatchMessage中进行的。
感觉分析的差不多了,再回到一开始提出来的问题
1. 既然是个死循环,为什么线程不会阻塞,占用大量的CPU资源?
2. 我们使用sendMessage传递Message给主线程,是怎么传的?
对于问题1,我们的主线程就跑在这个Looper里面,就像是那种原始的监听方式,没有回调,只能靠一个循环一直去监听一个对象的值是否有变化,再执行相应的操作。应该是包括TouchEvent在里面的,这个还不确定,以后应该会写个东西详细看下。至于占用大量的CPU,可以看到我们的MessageQueue中也有一些阻塞状态的判断,初步判断通过状态来减少CPU使用吧。在获取Message的时候是这样用的
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
好像也没做什么操作,进入queue.next()中查看,代码很长,里面也有个for循环,估计是用来获取message的主要部分,开头有个方法
nativePollOnce(ptr, nextPollTimeoutMillis);
非常引人注意。其实这个不是我自己发现的,我上网查了查资料
https://www.cnblogs.com/jiy-for-you/archive/2019/10/20/11707356.html
讲的比较详细,引用里面大神的一段话
简短答案:
nativePollOnce
方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 则您的主线程没有实际工作要做, 而是等待下一个事件处理.无需担心.说明:
因为主线程负责绘制 UI 和处理各种事件, 所以主线程拥有一个处理所有这些事件的循环. 该循环由
Looper
管理, 其工作非常简单: 它处理MessageQueue
中的所有Message
.
例如, 响应于输入事件, 将消息添加到队列, 帧渲染回调, 甚至您的Handler.post
调用. 有时主线程无事可做(即队列中没有消息), 例如在完成渲染单帧之后(线程刚绘制了一帧, 并准备好下一帧, 等待适当的时间).MessageQueue
类中的两个 Java 方法对我们很有趣:Message next()
和boolean enqueueMessage(Message, long)
. 顾名思义,Message next()
从队列中获取并返回下一个消息. 如果队列为空(无返回值), 则该方法将调用native void nativePollOnce(long, int)
, 该方法将一直阻塞直到添加新消息为止. 此时,您可能会问nativePollOnce
如何知道何时醒来. 这是一个很好的问题. 当将Message
添加到队列时, 框架调用enqueueMessage
方法, 该方法不仅将消息插入队列, 而且还会调用native static void nativeWake(long)
.nativePollOnce
和nativeWake
的核心魔术发生在 native 代码中. nativeMessageQueue
利用名为epoll
的 Linux 系统调用, 该系统调用可以监视文件描述符中的 IO 事件.nativePollOnce
在某个文件描述符上调用epoll_wait
, 而nativeWake
写入一个 IO 操作到描述符,epoll_wait
等待. 然后, 内核从等待状态中取出epoll
等待线程, 并且该线程继续处理新消息. 如果您熟悉 Java 的Object.wait()
和Object.notify()
方法,可以想象一下nativePollOnce
大致等同于Object.wait()
,nativeWake
等同于Object.notify()
,但它们的实现完全不同:nativePollOnce
使用epoll
, 而Object.wait
使用futex
Linux 调用. 值得注意的是,nativePollOnce
和Object.wait
都不会浪费 CPU 周期, 因为当线程进入任一方法时, 出于线程调度的目的, 该线程将被禁用(引用Object类的javadoc). 但是, 某些事件探查器可能会错误地将等待epoll
等待(甚至是 Object.wait)的线程识别为正在运行并消耗 CPU 时间, 这是不正确的. 如果这些方法实际上浪费了 CPU 周期, 则所有空闲的应用程序都将使用 100% 的 CPU, 从而加热并降低设备速度.结论:
nativePollOnce
. 它只是表明所有消息的处理已完成, 线程正在等待下一个消息.
cpp的代码从上完密码学就没碰过了,这里就不出来丢人了。这样我们就知道为什么主线程不会阻塞。
对于问题2相信不用多说大家都明白了,其实就是在子线程里面获取到主线程的handler,然后通过handler的sendMessage方法把Message对象推到我们的Message中,这个时候其实是调用了我们的enqueueMessage(),此时设置Message的target为相应的Handler,之后经过Looper获取message,然后使用dispatchMessage方法进行处理。大概就是这样的。
总结
Handler的消息处理和调度分析的差不多了,对于多线程的处理相信我和各位看官都有了更进一步的掌握。对于其他的多线程使用方式,之后也会继续写写,但是要整理下,毕竟我还是个菜鸟,哈哈,今天不早了,还没洗澡,就先到这里。
感觉写blog还有点上瘾,淦