Android 消息分发机制解读

前言

想必大家都知道Android系统有自己的一套消息分发机制,,从App启动那一刻起,App就创建了主线程的消息分发实例:Looper.sMainLooper,并开始无限循环,也就是App的心脏,一直跳动,负责协调分配来自各方的事件,让App不断响应用户操作,如果主线程出现了异常,也就是心脏跳动异常停止,那么App的生命随之终止,也就是常见的'进程已停止运行'。那么,你有没有想过,既然他在一直无限循环,为什么没有卡死呢?为什么能看到"应用无响应"?怎么保证界面刷新不受其他事件影响,怎么做到有条不理的处理每一条消息等等这些问题呢,作为一名Android开发者,我想我们有必要对其结构进行简单了解。

思路整理

基于消息分发机制,我们可以从以下几个方面由深到浅去解惑:

  1. Message
  2. MessageQueue 的核心逻辑;
  3. Looper的核心逻辑;
  4. Handler机制;
    在阅读前,你可能需要对数据结构单链表有一定的了解。

源码基于 Android API 33

Message

消息对象,部分源码:

java 复制代码
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一般不为空,为空一般是系统内部创建的消息,比如执行Viewinvalidate()就是发送了target为空的异步消息,具体看 消息队列中的分发逻辑。

MessageQueue 消息队列

顾名思义,是一个存放消息Message的队列,主要负责管理消息的插入和取出。每个消息都有对应的创建时间,插入队列中的消息会按时间排序,当调用next时会从队列中取出一条符合条件的Message,如果没有,则next函数会进入休眠状态,直到被唤醒。为了方便理解,下面对该类的核心方法,核心变量分开分析。

大致结构如下:

java 复制代码
//源码位于 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插入到队列中。关键源码:

java 复制代码
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被唤醒。

java 复制代码
    @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的原因。关键源码:

java 复制代码
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 内部的消息队列的分发消息函数,把消息插入到队列中,完成用户对消息队列的操作。

总结

至此,相信你对消息分发机制也有大概的理解。

相关推荐
踏雪羽翼19 分钟前
android 解决混淆导致AGPBI: {“kind“:“error“,“text“:“Type a.a is defined multiple times
android·java·开发语言·混淆·混淆打包出现a.a
csj5034 分钟前
安卓基础之《(21)—高级控件(3)翻页类视图》
android
2501_9159184139 分钟前
中小团队发布,跨平台 iOS 上架,证书、描述文件创建管理,测试分发一体化方案
android·ios·小程序·https·uni-app·iphone·webview
betazhou1 小时前
MySQL相关性能查询语句
android·数据库·mysql
一起养小猫1 小时前
Flutter for OpenHarmony 进阶:Timer组件与倒计时系统深度解析
android·网络·笔记·flutter·json·harmonyos
符哥20081 小时前
Fastjson2.X 使用详解
android·java
月明泉清2 小时前
Android中对于点击事件的深度梳理(三)
android
电饭叔2 小时前
DataFrame和 Series 索引
android·python
lexiangqicheng2 小时前
【全网最全】React Native 安卓原生工程结构与构建机制深度解析
android·react native·react.js
数据蜂巢2 小时前
MySQL 8.0 生产环境备份脚本 (Percona XtraBackup 8.0+)
android·mysql·adb