Android 消息分发机制解读
前言
想必大家都知道Android系统有自己的一套消息分发机制,,从App启动那一刻起,App就创建了主线程的消息分发实例:Looper.sMainLooper
,并开始无限循环,也就是App的心脏,一直跳动,负责协调分配来自各方的事件,让App不断响应用户操作,如果主线程出现了异常,也就是心脏跳动异常停止,那么App的生命随之终止,也就是常见的‘进程已停止运行’。那么,你有没有想过,既然他在一直无限循环,为什么没有卡死呢?为什么能看到“应用无响应”?怎么保证界面刷新不受其他事件影响,怎么做到有条不理的处理每一条消息等等这些问题呢,作为一名Android开发者,我想我们有必要对其结构进行简单了解。
思路整理
基于消息分发机制,我们可以从以下几个方面由深到浅去解惑:
Message
;MessageQueue
的核心逻辑;Looper
的核心逻辑;Handler
机制;
在阅读前,你可能需要对数据结构单链表有一定的了解。
源码基于 Android API 33
Message
消息对象,部分源码:
public final class Message implements Parcelable {
//
/**
* The targeted delivery time of this message. The time-base is
* {@link SystemClock#uptimeMillis}.
* @hide Only for use within the tests.
*/
@UnsupportedAppUsage
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
//
@UnsupportedAppUsage
/*package*/ Handler target;
//是否为异步消息
/**
* Returns true if the message is asynchronous, meaning that it is not
* subject to {@link Looper} synchronization barriers.
*
* @return True if the message is asynchronous.
*
* @see #setAsynchronous(boolean)
*/
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
}
关于此类,需要知道的是,我们外部创建的target
一般不为空,为空一般是系统内部创建的消息,比如执行View
的invalidate()
就是发送了target为空的异步消息,具体看 消息队列中的分发逻辑。
MessageQueue 消息队列
顾名思义,是一个存放消息Message
的队列,主要负责管理消息的插入和取出。每个消息都有对应的创建时间,插入队列中的消息会按时间排序,当调用next时会从队列中取出一条符合条件的Message
,如果没有,则next函数会进入休眠状态,直到被唤醒。为了方便理解,下面对该类的核心方法,核心变量分开分析。
大致结构如下:
//源码位于 android.os.MessageQueue
@UnsupportedAppUsage
@SuppressWarnings("unused")
private long mPtr; // used by native code
@UnsupportedAppUsage
Message mMessages;
boolean enqueueMessage(Message msg, long when){}
Message next() {}
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier(){}
@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int token) {}
mPtr
看起来就是一个指针,源码中注释为: used by native code,也就是说是在Native层用的代码,我们只需知道他是一道闸,堵塞next方法用的。
mMessages
队列的头部消息。
postSyncBarrier ,removeSyncBarrier
这两个方法是发送和移除同步屏障消息
,我们可以把它想象成为一道栅栏,能拦截所有同步消息,只允许异步信息通过。当向队列中插入该消息后,消息分发会主动跳过所有同步消息,即使队列中没有异步消息,直至屏障被移除。
不难发现,调用postSyncBarrier
时,会返回一个int类型,也就是屏障的标识,自然移除屏障的时候需要传入这个标识。
至于为什么加入这个机制,大致因为,加入队列中有很多消息,此时用户点击了一下界面,如果没有这个机制,那么响应用户点击的操作需要等待其他事件被分发完后才轮到,容易让用户觉得不流畅,所以系统为了UI相关的消息要优先被分发,在队列中插入同步屏障信号,之后响应点击的消息会被优先处理,处理完成后,再把信号移除,继续执行其他分发。
这个机制如果处理不好,屏障没有正常移除,就会出现进程假死的问题,这也就是官方为何把此方法标记成@UnsupportedAppUsage,不给开发者调用。
*那么这样就可以避免不会出问题吗?*不可能,这只是减少了问题的出现概率,还是有机会出现的,屏障信号是系统在更新UI时发送的,如果我们操作不当,频繁在子线程操作UI,可能某一瞬间,发送了超过两个屏障信号,但是只记录到最后一个token,更新完成后,自然只移除了最后添加的屏障,结果就是之前插入的一直挂在队列中,堵塞主线程所有同步消息,也就引发了同步屏障泄露
的问题,App就直接出现了明明有些控件明明还在刷新,但是怎么点都没反应。
这也是为什么官方只允许我们只能在主线程执行更新UI操作原因之一。
enqueueMessage
分发消息,把用户发的Message
插入到队列中。关键源码:
boolean enqueueMessage(Message msg, long when){
//target为空为屏障消息,屏障信息只能由系统代码发送,用户端发送的消息target不可能是空
//这里就解析了为什么用户不能发送没有target的消息
if (msg.target == null) {//
throw new IllegalArgumentException("Message must have a target.");
}
//......省略
synchronized (this) {
//......省略
//1.把当前的对头赋值给p
Message p = mMessages;
boolean needWake;//是否需要唤醒next函数
if (p == null || when == 0 || when < p.when) {
//2.A 如果对头为空(队列没有任何消息)或者when为0(只有刚开机才会存在)或者msg的时间比队头的时间早
//把待分发消息插到对头
// New head, wake up the event queue if blocked.
msg.next = p; //把当前消息的next指向p 尽管p为空
mMessages = msg; //把当前消息放到对头
needWake = mBlocked;//?
} else {
//2.B 队列中已有消息,这时需要把当前消息按时间顺序插到队列中
// 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.
//3、是否需要唤醒(正在堵塞、队头是屏障信号、当前消息是异步消息 )
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //4、开始循环队列,和队列中消息when做比较,找出当前消息的when值在队列中最适合的位置
prev = p; //此时p的角色为当前遍历的消息,先赋值给上一条消息,保证每次循环能拿到上一条消息和下一条消息,方便插入
p = p.next; //取下一条消息,赋值到p
if (p == null || when < p.when) { //5、如果下一条消息是空(说明已到达队尾),或者当前消息的时刻比下一条消息的时刻小,说明此时的位置最适合,结束循环
break;
}
//队头符合步骤3处条件,说明有同步屏障信号,并且当前p是异步消息,根据步骤5,能走到这里说消息分发时机还没到,所以不需要唤醒next
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//把当前消息入队,插入到上一条消息和p之间
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) { //唤醒next函数,取 消息 分发
nativeWake(mPtr);
}
}
return true;
}
next 取消息
从队列中取出一条可用消息(when时机合适),如果有就返回,没有则堵塞,直到mPtr
被唤醒。
@UnsupportedAppUsage
Message next() {
//......省略
//1、下一次唤醒时间
int nextPollTimeoutMillis = 0;
for (;;) {//2、开始死循环取消息
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//3、堵塞,时长为nextPollTimeoutMillis,或者主动调用唤醒函数`nativeWake`
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) {
//4、关键点,target为空,说明当前队头消息为屏障信号,需要优先处理离信号最近的异步消息
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
//一直在队列中寻找离信号最近的异步消息,直到没找到或者到达队尾
prevMsg = msg;
msg = msg.next;//如果一直找到队尾,msg.next是空,结束循环
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {//5、此时,如果有屏障信号的话,步骤4肯定走了,msg不为空肯定是异步消息,否则msg必为空
//检查该消息是否以达到分发时机
if (now < msg.when) {//当前时间还没达到消息的时机,计算还差多久,赋值给nextPollTimeoutMillis,进入下一次堵塞,直到达到时间nextPollTimeoutMillis,再取该消息
// 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) {//说明此消息在队列里面(同步屏障会优先执行里面的异步消息),要把消息出队,把该消息的上一条消息的next直接指向该消息的下一条消息
prevMsg.next = msg.next;
} else {//说明此消息是队头,直接把当前消息的next放到队头
mMessages = msg.next;
}
//删除出队消息的next指向
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//标注消息正在使用
msg.markInUse();
//把该消息返回
return msg;
}
} else {//6、没有符合条件消息,继续下一次循环,取符合条件的消息
//如果屏障信号没有被移除,又没有异步消息进入队列,那么next函数将陷入死循环,循环线路 3--》4--》6
//导致APP所有同步消息无法被处理,表现为软件部分界面卡死(如文本正常刷新,点击事件无法响应)并且不会引发ANR
// 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;
}
}
Looper
一个无限循环的类,他内部持有消息队列MessageQueue
的引用,当Looper.loop()
后,将会一直调用MessageQueue.next
函数获取消息,next在没有消息的时候又会堵塞,让出CPU资源,这就是为什么死循环却没有占满CPU的原因。关键源码:
public final class Looper {
public static void prepare() {
prepare(true);
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
* 循环一次的逻辑
* Poll and deliver single message, return true if the outer loop should continue.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
//从队列中取一条消息
Message msg = me.mQueue.next(); // might block
if (msg == null) {//由于next没消息会堵塞,所以没消息的时候这里不会执行,除非强制退出
// No message indicates that the message queue is quitting.
return false;
}
try {
//开始分发取到的消息
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略,,,,,,,,,,,,,,,,,,,,,,,
// 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();
return true;
}
/**
* 开始无限循环
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
@SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
if (me.mInLoop) {
Slog.w(TAG, "Loop again would have the queued messages be executed"
+ " before this one completed.");
}
me.mInLoop = true;
// 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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
me.mSlowDeliveryDetected = false;
for (;;) {//无限循环核心
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
}
Handler
通过Handler,我们可以把消息发送到对应的消息队列中,是用户代码操作消息队列的入口。关键源码:
public class Handler {
/**
* Use the provided {@link Looper} instead of the default one.
*
* @param looper The looper, must not be null.
*/
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
/**
* Use the provided {@link Looper} instead of the default one and take a callback
* interface in which to handle messages.
*
* @param looper The looper, must not be null.
* @param callback The callback interface in which to handle messages, or null.
*/
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
}
上面只列举了几个构造函数,其他一些post消息函数就不一一说明了,主要是Handler创建需要关联一个Looper,发消息的时候又调用了Looper 内部的消息队列的分发消息函数,把消息插入到队列中,完成用户对消息队列的操作。
总结
至此,相信你对消息分发机制也有大概的理解。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!