• Android源码解析:Handler机制


    Android源码解析:Handler机制

    在这里插入图片描述

    导言

    关于如何在Android中进行多线程通信,使用Handler机制是一个好方法。Android提供的Handler机制为我们提供了方便的方法进行线程间通信,具体来说,Handler将会绑定一个线程和Looper,Looper将MessageQueue中的Message取出给Handler进行处理。我们可以看一下这幅图先来有一个大概的印象:

    在这里插入图片描述

    本篇文章将从源码的角度对Handler机制进行进一步分析。

    生产者-消费者模式

    首先我们需要明确一点,那就是Handler机制的设计是基于生产者-消费者模式的。这和Android UI是单线程的也有关,实际上目前的绝大多数(甚至可以说是所有)带GUI的系统都是采取单线程模型的方式来实现UI的。多线程UI听起来很美好但是实际上会出现各种各样的问题,为了克服多进程修改的不确定需要层层加锁,时不时就会出现死锁的情况。

    在Handler机制中,Handler可以看做是生产者(同时也是消费者);Looper从消息队列中取消息,可以看做消费者;而MessageQueue是消息队列。

    源码解析

    类的组成

    handler类的组成

    我们首先从Handler类的源码开始看起。首先看Handler中的成员变量:

    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    @UnsupportedAppUsage
    final Callback mCallback;
    final boolean mAsynchronous;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到Handler中有四个主要的成员变量:

    • mLooper:与当前Handler关联的Looper
    • mQueue:当前的消息队列
    • mCallback:Handler的回调方法,该接口只有一个handleMessage方法
    • mAsynchronous:异步标志位,这和同步屏障有关

    MessageQueue类的组成

    MessageQueue中比较值得聊的成员变量应该是mPendingIdleHandlers,这是一个IdleHandler类型的数组。具体关于IdleHandler,它是一个空闲处理程序。IdleHandler的主要方法是onIdle,当消息队列为空并且没有要处理的消息时,系统会调用此方法。

    Message类的组成

    Message类的成员变量太多了,我们选几个重要的:

    /*package*/ Bundle data;
    
    @UnsupportedAppUsage
    /*package*/ Handler target;
    
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
    
    // sometimes we store linked lists of these things
    @UnsupportedAppUsage
    /*package*/ Message next;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第一个data用来传递数据;第二个target变量是用来指定目标的Handler,即这个Message需要传递到哪个Handler中;

    第三个callback是用来执行runnable的;第四个next指的是下一个Message。其他倒没什么,看到这个next应该就理解了,Message是用链表的结构串联起来的。

    工作原理

    接下来我们从Handler使用的角度切入,解析一下Handler机制的工作原理。首先我们使用Handler机制时往往需要先创建一个Handler对象,我们来查看Handler的构造函数。

    Handler的构造函数

    Handler构造有很多重载,不过最后跳转到的就两个具体的构造方法,我们首先来看简单的那个构造:

    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个构造很简单,就是把四个传入的参数分别赋值给四个成员变量。

    接下来还有第二个构造函数:

    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    总的来说和第一个构造差别不大,只是它会进行额外的一段检查,这段检测主要和内存泄漏有关。

    这里需要说明一下Handler的无参构造方法已经被废弃了,但还是有很多书上还是会调用它的无参构造方法,这里还是不建议使用这个手段。

    获得Message

    获得了Handler之后我们接下来要做的就是获得Message了。一开始我是直接使用Message的构造方法来直接创建Message的,不过Message实际上有一个obtain方法来从内部获得Message:

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    首先这个方法显然是一个同步方法,接着如果有对象池sPool的话就会获取这个池中的对象,然后将剩余可用数量减一,最后返回这个对象。如果没有可用的对象池的话就直接调用构造方法创建一个新的Message。可以看出来这个对象池是一个链表结构。

    发送Message

    获得了Message之后我们会通过Handler将Message发送出去,调用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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到通过层层跳转后会转到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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到Handler所谓的发送Message最后还是要调用MessageQueue的enqueueMessage来实现:

    boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            .......
            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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这就是将Message加入消息队列中的核心逻辑,我们来逐句分析。

    首先它会将传入的Message的when属性给取出来,这个when属性存储的是Message延迟的时间点。接着会根据三个条件判断新加入的Message是否是新的头部(因为Message是按照链表组织起来的),这三个条件分别是:

    • 当前的Message队列是否为空
    • 新传入的Message的延迟时间是否为0
    • 当前传入的Message的延迟时间是否比当前Message队列的第一个Message延迟时间短

    首先有必要解释一下上面出现的Messages就是MessageQueue中原来有的Message队列。总的来说就是判断新入队的Message的优先级是否比MessageQueue中原有的Messages高,如果以上三个条件满足其一就会进入到第一个分支中,也就是说明新传入的Message的优先级更高。在这个分支中,首先会把新传入的Message插入到原有Messages之前,并将当前Messages的头结点指向新传入的Message。最后将needWake标志位设置为mBlock,意在唤醒阻塞的队列。

    紧接着我们再来看第二个分支,如果传入的Message的优先级不如原来的Messages的第一个Message的话就会进入到这个分支中。我们先来看这段分支中的注释:

    1. 插入操作:指的是将消息插入到消息队列中的中间位置。通常情况下,消息会按照时间顺序排列在消息队列中,但在某些情况下,需要将消息插入到队列中的中间位置。
    2. 唤醒事件队列:消息队列中的消息是按照时间顺序排列的,通常情况下,只有当消息队列的头部有一个“屏障”(barrier)消息并且消息是队列中最早的异步消息时,才需要唤醒事件队列。

    在Android的消息处理机制中,消息队列是按照消息的**时间戳(或优先级)**来排列的。如果有一个需要等待的屏障消息位于队列的头部,并且接下来要处理的消息是队列中最早的异步消息,那么这个时候需要唤醒事件队列,以确保及时处理这些消息。这可以保证消息的顺序性和及时性。这段分支做的事情也很简单,就是链表的遍历,不断地循环Message链表直到找到新加入的Message应该在链表中的位置,这个位置具体就是由when延时时间来决定的:

     if (p == null || when < p.when) {
                    break;
         }
    
    • 1
    • 2
    • 3

    当找到延时时间比当前插入的Message大的Message时,将当前Message插入到找到的Message之前。按照这个逻辑我们也可以知道整个Message链表的结构就是when属性越小的Message排在越前面。

    handler分发Message

    Handler将Message发送出去之后(实际上是MessageQueue发送的)就是Looper的工作了,Looper需要将MessageQueue中的Message取出了,不过这个过程具体是在哪个方法中呢?我们来反推这个过程,就是从Handler的handleMessage方法入手,我们看看在哪里触发了这个方法:

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这是Handler中的方法,从这个方法的名字中我们也可以看出来这个方法是用来分发Message的。它首先会检查传入的Message参数是否带有callback回调,如果带有回调的话就会调用handleCallback方法来处理这个Message;否则进入分支,先判断当前Handler的mCallback参数是否为空,若不为空会执行mCallBackhandleMessage回调。最后才会执行我们一般重写的handleMessage方法。

    可以看到这里的优先级是: Message.callback > mCallback > handleMessage

    接着我们继续深入,可以在Looper的loopOnce方法中找到一句类似的:

    在这里插入图片描述

    虽然是标红的,我们看不到,但是Message确实有一个target属性是用来指示该Message应当被发送到哪个Handler中去。而这个loopOnce方法就是由Looper的loop方法触发的。还记得吗,我们使用Handler机制时也需要用loop来开启消息队列的循环,只不过在主线程中消息队列的循环在应用程序的初始化时就被执行了,所以我们在主线程中不需要显式调用loop来启动消息队列的循环。

    Looper开启消息队列循环

    找到了Looper在哪个方法中取出消息之前我们先来看看loop方法:

    public static void loop() {
        final Looper me = myLooper();
    	.......
        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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到这里出现了Binder的身影,第一段注释中提到"确保这个线程的身份与本地进程的身份相同,并跟踪这个身份标记的实际值。",这里涉及到的内容可能涉及到Android系统了:

    在Android中,每个线程都有一个调用身份,用于标识当前线程的身份和权限。Binder是Android的进程间通信(IPC)机制的一部分,它还用于跨线程通信。Binder.clearCallingIdentity()方法用于清除当前线程的调用身份,即将当前线程的身份标识重置为默认值。具体来说,这段代码的目的是在当前线程中设置调用身份(calling identity)为本地进程的身份,并记录下这个身份标识的值。在Android中,每个线程都有一个调用身份,用于标识线程的身份和权限。通过将当前线程的调用身份设置为本地进程的身份,可以确保当前线程在执行某些操作时具有本地进程的权限和身份。

    提炼重点来说就是确保Handler机制在进行跨线程时拥有足够的权限去执行一些相关任务。之后在for死循环中就调用到了loopOnce方法:

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }
    	.......
        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        Object token = null;
        if (observer != null) {
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        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);
            }
        }
       ......
        msg.recycleUnchecked();
    
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    这就是loopOnce的核心逻辑,首先从MessageQueue中取出下一个要处理的Message,如果到队尾就直接返回false结束loop循环。接着会记录下开始时间,检查observer对象,这个是Looper内置的观察者,是一个内部接口,从它的注释来看应该是用来在处理Message的各个时间点插入自定义的逻辑,但是它又是被标记为@hide的,不太清楚,我们也不管这个。

    之后loopOnce会记录下ThreadLocalWorkSource.setUid(msg.workSourceUid)的值,ThreadLocakWorkSource是一个用于跟踪工作源信息的类,工作源是用于标识应用程序中哪个部分产生了某项工作(例如,网络请求、定位服务等)的机制。ThreadLocalWorkSource 主要用于以下情况:

    1. 多线程环境下的工作源管理: 在多线程应用程序中,不同线程可能会执行不同的工作,并且这些工作可能属于不同的应用程序组件。ThreadLocalWorkSource 允许为每个线程分配一个工作源,以便在执行工作时跟踪它们的来源。
    2. 跨线程传递工作源: 当一个线程创建工作并将其传递给另一个线程执行时,可以使用 ThreadLocalWorkSource 来跟踪工作源的变化。这可以帮助确定工作的来源是哪个应用程序组件。
    3. 资源分配和管理: 在 Android 应用程序中,不同的工作可能需要不同的资源(如电池、网络连接等)。通过管理工作源,可以更好地控制和优化资源的分配和使用。

    显然在这里它就是用来在多线程下管理工作源和跨线程传递工作源的。记录下工作源之后会调用到Message的target的dispatchMessage,这里就会触发之前提到的handler分发Message,会对我们的Message进行相应的处理。这一切完成之后,就会调用Message的recycleunchecked方法,之前提到Message运用到了享元模式,所以这里会对其进行回收以便下次复用。

    MessageQueue如何获取下一个Message

    在上面Looper的loopOnce方法中我们会调用MessageQueue的next方法来获取下一个Message,所以在这里我们来看看这个next方法:

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
    
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
    
            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) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 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) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    这个next方法涉及到很多系统层面上的东西。首先就是Binder.flushPendingCommands()方法:

    Binder.flushPendingCommands() 方法是 Android 中的 Binder 机制的一部分,用于强制刷新 Binder 队列中的待处理命令。

    Binder 是 Android 中的进程间通信机制,它用于在不同的进程之间传递数据和调用方法。在进行跨进程通信时,数据和方法调用都会被打包成 Binder 命令,并被发送到目标进程的 Binder 队列中等待执行。

    Android 的 Binder 队列使用了一种延迟处理的机制,即不一定立刻执行命令,而是等待一段时间,然后批量执行。这可以提高效率,减少频繁的进程切换。但在某些情况下,可能需要立即执行某个 Binder 命令,而不希望等待延迟执行。

    Binder.flushPendingCommands() 方法就是用来满足这种需求的。它会强制将当前线程的 Binder 队列中所有待处理的命令立即执行,而不等待延迟执行。这可以确保某些重要的操作立即生效,而不受延迟执行的影响。

    通常情况下,开发者不需要显式调用 Binder.flushPendingCommands() 方法,因为 Android 系统会自动处理 Binder 命令的执行。但在一些特殊情况下,例如需要立即传递数据或确保某些操作的及时执行时,可以考虑使用该方法。但要注意,滥用该方法可能会导致性能问题,因此应谨慎使用。

    那么为什么要在next方法中刷新Binder队列中的待处理命令呢?因为在 Android 中,MessageQueue 负责管理消息和事件的处理顺序,而 Binder 机制是 Android 进程间通信的核心机制之一。因此,在消息队列中可能会包含需要通过 Binder 执行的命令。所以通过刷新Binder队列可以防止在处理消息时可能会错过 Binder 命令,或者在处理 Binder 命令时错过消息。这可能导致消息和 Binder 命令的执行顺序混乱,或者导致某些操作的延迟。

    除此之外还有一个Native方法–nativePollOnce,可以在网站上找到这个方法的源码,是用C++写的:

    nativePollOnce 是一个 JNI(Java Native Interface)方法,它用于与底层的 C/C++ 代码进行交互。在 Android 框架中,nativePollOnce 主要是用于 Looper 的消息循环机制,它用于等待和处理消息和事件。

    具体来说,nativePollOnce 的主要作用如下:

    1. 等待消息和事件:它会等待一定时间(由 nextPollTimeoutMillis 参数指定),直到有消息或事件需要处理。如果在指定的时间内没有消息或事件,它将返回,允许继续执行其他操作。
    2. 处理消息和事件:一旦有消息或事件需要处理,nativePollOnce 将会处理它们,包括执行相关的回调函数或处理事件的代码。
    3. 与底层系统通信:在 Android 系统中,有许多底层的系统服务和组件,这些组件通常是使用 C/C++ 编写的。nativePollOnce 方法可以与这些底层组件进行通信,以处理系统级别的消息和事件。

    这里这个native方法还有一个重要的作用,那就是它会调用Linux中的Epoll机制,什么是Epoll呢?简单来说当消息队列中没有消息的时候就会通过Epoll机制通知系统暂时休眠,这样Loop循环就不会长时间阻塞线程。这也是为什么在主线程中的Looper调用Loop后不会阻塞主线程的原因。

    我们先解决掉这两个疑问,不带着疑问看。接下来让我们回到next方法本身,这个方法本身还是很好懂的,首先查找到下一个Message,将链表的头指针向后移,如果列表中没有更多的Message了就会尝试执行空闲处理程序,需要说明的是,这个方法本身是一个死循环,最差也会返回一个null值(只有在mQuitting标志位true时),我们结合之前的loopOnce方法,理论上当mQuitting标志位为false时,loopOnce方法将永远不会返回false,也就是说loop方法也会一直循环执行下去。

    mQuitting标志位

    mQuittingMessageQueue 中的一个标志位,用于表示消息队列是否正在退出(quitting)。该标志位在消息队列即将退出时被设置为 true,以指示消息队列不再接受新的消息,并且正在处理完当前队列中的所有消息后将被销毁。

    具体来说,mQuitting 的意义和作用如下:

    1. 消息队列退出标志mQuitting 是一个标志,用于表示消息队列的退出状态。当消息队列处于退出状态时,不再接受新的消息。
    2. 退出消息的处理:通常,在 Android 应用中,当应用需要退出时,会向消息队列中发送一个特殊的退出消息,例如 quitquitSafely 消息。当消息队列接收到退出消息时,会设置 mQuittingtrue,表示消息队列即将退出。
    3. 防止新消息的接收:一旦 mQuitting 被设置为 true,消息队列将不再接受新的消息。这是为了确保在退出过程中不会有新的消息被添加到队列中,从而保证消息队列可以尽快地处理完当前队列中的所有消息。
    4. 安全退出mQuitting 的存在可以帮助确保消息队列在退出时能够安全地处理完所有消息,防止消息队列在销毁之前有未处理的消息。

    总之,mQuittingMessageQueue 中的一个标志位,用于表示消息队列的退出状态。它有助于确保消息队列在退出时能够安全地处理完所有消息,然后被销毁。

    Looper的创建

    最后我们再来讲一讲Looper的创建,Looper是通过prepare方法来创建的:

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里它会将自身的sThreadLocal变量设置为一个新的Looper对象,这里的sThreadLocal是一个类型为ThreadLocal的变量,关于ThreadLocal就是一个每个线程私有的数据存储类,每个线程都有其ThreadLocal的值,非本线程是无法获取到其ThreadLocal的值的。这就可以很好的结合Looper的特性,每个线程都只有一个与其绑定的Looper。

    Handler如何实现线程切换

    其实回过头来还有一个重要的问题,那就是Handler到底是如何将一个线程的任务发送到另一个线程使其执行的呢,这有涉及到了一个最基本的问题,评判一串代码在哪个线程执行的依据是什么?这里又要搬出那张经典的图片了:
    在这里插入图片描述
    可以看到堆区是所有线程共有的区域,我们创建Handler时实例也是被分配在这个区域中的。不过除了创建该实例的线程之外其他线程也可以访问这个变量。所以其他线程可以通过这个Handler来将Message发送到与其绑定的Looper中(具体来说是Looper管理下的MessageQueue),比如可能是这样的:
    在这里插入图片描述
    接下来如果线程一想要发送任务到线程二执行的话会通过Handler1来打包发送一个Message:
    在这里插入图片描述
    还记得我们之前说的Epoll机制吗?在MessageQueue空闲时将会进入休眠,但是这个情况下与Handler1关联的MessageQueue接收到了新的事件,自然Epoll会重新唤醒Looper进行轮询。这个Looper是ThreadLocal的也就是线程私有的,我们只有在线程二中才能访问,所以这个时候相当于是从线程二所私有的MessageQueue中取出了Message,之后Looper会在线程二中调用我们之前提到的loopOnce方法,这样就成功地在线程二中触发了其Message任务。

    总的来说,我觉得这个Handler的线程切换还是借助于消息队列和ThreadLoacl变量的特性。

    同步屏障

    上面已经分析完了整个Handler机制的基本原理,但是还有一些额外的机制。比如在API16中就加入了同步屏障机制,在看源码的时候我们经常看到的mAsynchronous就和它有关。关于同步屏障就是一种特殊的优先级机制,具体来说就是保证异步消息总是在同步消息之前执行;比如说View的绘制过程就会用到同步屏障保证垂直同步消息到来时主线程绘制一帧UI;

    当Looper调用next()获取消息时候,发现队列头部是一个同步屏障信息,就会跳过所有同步消息,寻找所有的异步消息执行:

            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    所以这个机制本质上就是一个为了顺序正确执行而存在的优先级机制。

    总结

    最后再让我们来看一看这张图回顾一下:

    在这里插入图片描述

  • 相关阅读:
    一个简单的基于Qt的MVC框架
    OpenHD改造实现廉价高清数字图传(树莓派+PC)—(五)gstreamer视频采集、传输和显示
    在亚马逊云科技Amazon SageMaker上部署构建聊天机器人的开源大语言模型
    【C++】哈希应用——海量数据面试题
    电脑上出现多个python版本环境变量配置(包括pip的配置)亲测有效!!!
    计算机毕业设计之java+springboot基于vue的书籍学习平台
    如何使用宝塔面板部署MySQL数据库,并结合内网穿透实现固定公网地址远程连接
    vue 下载的插件从哪里上传?npm发布插件详细记录
    HTML布局(HTML Layout)简介
    深入浅出了解MYSQL8特性注入是什么
  • 原文地址:https://blog.csdn.net/Tai_Monster/article/details/132853874