Android 消息分发机制解读

2023-12-13 05:25:21

前言

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

思路整理

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

  1. Message
  2. MessageQueue 的核心逻辑;
  3. Looper的核心逻辑;
  4. 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一般不为空,为空一般是系统内部创建的消息,比如执行Viewinvalidate()就是发送了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 内部的消息队列的分发消息函数,把消息插入到队列中,完成用户对消息队列的操作。

总结

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

文章来源:https://blog.csdn.net/WPR13005655989/article/details/134841437
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。