详细分析Handler消息机制

作者: 聪葱忙忘 | 来源:发表于2020-05-15 17:37 被阅读0次

    Handler既然是一种消息机制,那它肯定会有个存放消息的数据结构,没错它就叫Message。

    Message的链表结构

    在分析Handler之前,基于Message是链表结构的,先来写一个链表。

    public class MyMessage{
            public String what;
            //用一个全局变量保存下一个MyMessage的引用。
            public MyMessage next
    }
    

    简单的链表通过一个变量next来保存下一个数据的引用,那么只要拥有头结点,就可以获取所有的链表数据了。

    简单地概括一下Handler的消息机制运作。

    首先是在某个线程上,我叫它线程A。
    啊这个线程A他去吃韩国烤肉

    1. 线程A去吃韩国烤肉,韩国烤肉都有个炉子,要有这个炉子并且打开它才能开始吃,这个炉子就是Looper,所以线程A一开始要创建一个Looper。而且对于一个人只有一个烤炉,你进店说要两个烤炉可是要被赶出去的。
      所以对于一个线程来说,有且仅有一个Looper。
    2. 韩国烤肉好多肉吃的,有雪花牛肉,猪颈肉,牛欢喜,猪大肠等等,这些肉就是Message了。
    3. 韩国烤肉有个好处就是有服务员小姐姐帮你烤,线程A手指着这个雪花牛肉说,我要烤这个,线程A的hand就是handler了,handler.enqueueMessage这个操作就相当于线程A手指这个雪花牛肉告诉服务员小姐姐说我要烤这个,实际上是服务员小姐姐把肉放烤炉里面烤的,她就是MessageQueue。
    4. 烤炉Looper不停地烤啊烤,然后小姐姐MessageQueue看到肉快熟了,就拿出来(MessageQueue.next())。拿出来后线程A的手(hand)就可以去拿肉message来吃了,也就是hander的处理,所以handler还得处理消息,自己点的牛欢喜,喊着泪也要自己吃完。
    5. 肉在小姐姐没拿出来之前都不能吃,因为生的不能吃(执行时间未到),所以MessageQueue.next()有阻塞操作。
    6. 烤炉上的这么多肉就是消息池,链表结构的一些message,他们衔接在一起,按时间顺序,早烤早熟。

    好了简单扯完后继续深入分析。

    Looper

    以在应用程序的入口ActivityThread.Main()开始。
    在main函数执行之前,系统已经为应用程序创建了一个进程。一个进程开始肯定会有一个线程,这个就是程序的主线程。

    public static void main(String[] args) {
        ...
        //为主线程创建Looper
        Looper.prepareMainLooper();
    }
    

    接着看prepareMainLooper

    public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    
     private static void prepare(boolean quitAllowed) {
            //不允许重复创建
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            //创建Looper并存入sThreadLocal,一个线程只有一个Looper
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
    

    在看下Looper的构造函数

     private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
     }
    

    构造函数里面实例化了一个MessageQueue对象。(就是一个烤炉给你安排了一个服务员小姐姐)。并且保存了当前线程。对于在ActivityThread来说就是应用进程的主线程。

    MessageQueue

     MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            mPtr = nativeInit();//mPtr记录native消息队列的信息,NativeMessageQueue的指针
        }
    

    native初始化并返回了NativeMessageQueue的指针给java层保存起来。
    继续看一下MessageQueue有什么重要的变量和方法。

    public final class MessageQueue {
         Message mMessages;
         Message next() {
            ...
        }
        boolean enqueueMessage(Message msg, long when) {
            ...
        }
    }
    

    mMessages其实贯穿了next和enqueueMessage。
    这是当然的,放消息和取消息肯定是离不开消息对象
    服务员小姐姐烤肉,放和拿的都是肉。
    那么先来看看这肉。

    Message

    public final class Message implements Parcelable {
        public int what;
        int flags;
        long when;
        Bundle data;
        Handler target;
        Runnable callback;
        Message next; //有需要的时候通过next衔接成一个链表。
        
        public static Message obtain() {
            synchronized (sPoolSync) {
                //先从缓存池里面拿,判断缓存池的头节点也同样是message类型的sPool是否为null
                if (sPool != null) {
                    Message m = sPool;  //sPool不为null,用m存起来,return用
                    sPool = m.next;//把缓存池的头节点sPool指向原来头节点的下一个 因为原来准备被使用了,下一个obtain操作智能使用原来的next的message
                    m.next = null;//断开原来头节点对下一个next的指向,方便释放内存
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();//头节点为null,代表缓存池没有,需要new一个message
    
        }
    
        public void recycle() {
            if (isInUse()) {
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            recycleUnchecked();
        }
      void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
             synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) { //缓存池最大数量为MAX_POOL_SIZE 50
                    next = sPool; //这里是把执行recyle操作的message的next指向当前缓存池的头结点
                    sPool = this; // 然后把执行recyle操作的message变成缓存池的头结点,典型的链表操作
                    sPoolSize++;
                }
            }
        }
    

    Message的obtain和recycleUnchecked两个方法实现了一个缓存池,是个以链表结构实现的队列,先进先出。每一次都从缓存池的头节点取message容器,用完了就放回缓存池的头节点并衔接上之前的缓存池的头部。

    再回到MessageQueue

    MessageQueue 的发消息

    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.");
            }
    

    msg.target就是handler,一般而言handler不能为空。
    msg.isInUse()是判断是否在使用中,inUse() true的话说明已经在被使用了,不能重复处理。

     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;
                }
    

    判断MessageQueue是否退出了,对于在主线程而言,这个mQuitting 是false的不能退出,如果退出了,就把msg.recycle掉然后return不继续执行。

                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 {
                    //异步消息,消息屏障等
    
    1. 首先通过makeInuse把传进来的msg 标志成inUse避免重复发。
    2. 把msg的时间设置成传进来的when时间戳。
    3. 新增一局部变量指向mMessages实例。
    4. needWake 是否需要唤醒阻塞的布尔值。
    5. 判断p,即mMessages为null的话就是之前没有任何消息,传进来的msg就是最新的消息。
    6. 时间戳为0,或者传进来的when也就是msg的when是否比mMessages的when时间戳要小,即可以认为msg就是要处理的最新消息。
    7. msg.next = p; 把p接在最新要处理的消息的next处,衔接起来,这里相当于把msg放到链表头,next指向旧的mMessages。
    8. mMessages = msg,看得出mMessages其实就是消息池的链表头,这里把mMessages更新成msg。
    9. needWake = mBlocked 这里我先告诉你应该是true。
    10. else 部分是处理异步消息或者消息屏障的。先忽略。

    接着就是执行nativeWake

    if (needWake) {
        nativeWake(mPtr);
    }
    

    这里是唤醒在某个在阻塞等待的地方。因为有消息msg了,然通知别人来拿。
    好比服务员小姐姐看到肉烤熟了。

    MessageQueue 取消息

    Message next() 方法

     final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    

    还没初始化就return null。

     for (;;) {
                nativePollOnce(ptr, nextPollTimeoutMillis);//阻塞操作,用于提取消息队列中的消息
    

    永不停止的循环里,有nativePollOnce个阻塞住,前面的nativeWake就是在这里唤醒的,通过ptr来一对一关联。

    synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();//获得当前的时间戳
                    Message prevMsg = null; //局部变量preMsg,初始值为null
                    Message msg = mMessages;// 消息池的链表头也就是最新要处理的消息的对象的引用赋给msg。
    

    此时msg就是最新要处理的消息。

    //消息屏障
    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());
                    }
    

    上面是消息屏障,消息也是个message不过没有target。正常的消息不会走这里因为target都不为null。
    消息屏障这里是要从链表头部开始遍历,直到找到msg.isAsynchronous()为true也就是异步消息,这个异步消息后面就可以通过preMsg.next来获取。msg = msg.next,也就是说msg就是要处理的最新异步消息。

    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不为空就是代表msg 是最新的异步消息
                                prevMsg.next = msg.next; // prevMsg->msg->msg.next 这里相当于把msg remove了,变成了prevMsg->msg.next()
                            } else {// 这里是普通的同步消息处理
                                mMessages = msg.next;//要返回的是msg,这里把消息池链表头更新为msg.next,就是链表头指向下一条消息。
                            }
                            msg.next = null; //断开msg跟消息池得连接方便释放内存
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse(); //标志成已使用
                            return msg;
                        }
                    }
    
    1. 首先判断时间,now < msg.when ,就是说你这个msg还没到时间执行

    2. if (prevMsg != null) {//prevMsg不为空就是代表msg 是最新的异步消息
      (1). prevMsg.next = msg.next; // prevMsg->msg->msg.next 这里相当于把msg remove了,变成了prevMsg->msg.next()

    3. if (prevMsg != null) 为false // 这里是普通的同步消息处理
      (1). mMessages = msg.next;//要返回的是msg,这里把消息池链表头更新为msg.next,就是链表头指向下一条消息。

    4. msg.next = null;//断开msg跟消息池的连接。

    5. msg.markInUse(); //标志成已使用 最后返回。

    以上一个普通的msg在MessageQueue里面的收发就完成了。就是说小姐姐把肉放进烤炉烤熟了然后放一边让你拿了。

    next()下半段的的pendingIdleHandler处理

    在取消息的里还有当if (msg != null) 为false的逻辑,这里代表这没有消息处理了。对于在ActivityThread的Looper的MessageQueue而言,就是主线程闲置了,此时MessageQueue会处理一些任务。
    就好比,肉都放烤炉上了,小姐姐没事干了,她去拿点调味料或者配料给你(免费加芝士,我吃!)

     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);
    

    这里就是把判断有没有Idle任务,这个我们开发的时候是可以自己定义idle任务和添加的。如果有没有就下次了(下次一定),并把mBlocked = true,状态设置为不阻塞,好让在enqueueMessage能执行唤醒操作。
    把这些任务放在mPendingIdleHandlers数组。

      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);
                        }
                    }
                }.
    

    遍历数组并执行每一个Idler的queueIdle方法。Idler任务的queueIdle有个返回值,如果这个返回值是false,就mIdleHandlers.remove(idler);就代表只执行一次。
    我们自定义Idler任务的话可以在queueIdle做想在线程闲置的时候做的操作,如果需要不停地在一闲置就执行的话,把queueIdle的返回值return true即可。

    好比跟小姐姐说,你一有空就帮我拿点芝士吧...

    Handler

    终于来到线程A的手了。
    吃韩国烤肉你除手点点要烤什么;=> Handler.enqueueMessage
    还要自己含泪吃完; ==> Handler.handleMessage(Message msg)
    一般是这样的。

    Handler.enqueueMessage

     private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    

    handler封装了好几个发送消息的方法,包括post(Runnable r),sendEmptyMessage(int what),sendMessage(Message msg)等等最终都是调用了enqueueMessage方法。
    而enqueueMessage也很简单直接使用了MessageQueue的enqueueMessage,前面已经分析过了。

    小伙子想法很多,最后还是手点点让MessageQueue小姐姐来烤了。
    msg.target = this;
    把发送消息的handler,放在msg的tartget变量上了。
    这块肉你哪只手点的,想怎么吃?!

    Handler.handleMessage

      public void handleMessage(Message msg) {
        }
    

    这里是完全由子类实现,你想怎么吃都可以。(建议雪花牛肉卷起芝士然后占点沙茶酱加酱油,手动拇指)。

    不过可能有人不明白为什么最后在这里处理消息,其实这只是一般情况,大多数是在这里处理的。
    下面再看看Looper。

    再回到Looper

    loop()

     for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    ...
            try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    

    最后会执行msg.target.dispatchMessage(msg),也就是从MessageQueue获得最新要处理的message的target执行dispatchMessage(msg)。
    前面handler发消息的时候msg.target = this就把handler放在target上了。
    所以你点的,你要自己吃啊。
    就是说handler发的这个消息,要自己来处理。

    Handler.dispatchMessage.

    public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    
    1. msg.callback 不为空的话就执行handleCallback(msg);里面是
     private static void handleCallback(Message message) {
            message.callback.run();
        }
    

    执行了message.callback.run();
    也就是说如果message本身有callback的话就执行它的callback方法,就好比这块肉是块全肥的,天然的callback就是让它一边去吧。然后小姐姐烤完了,就让它一边去了。

    1. if (mCallback != null) 就执行mCallback.handleMessage(msg)并且return不执行下面了。
      看看mCallback这个全局变量在哪里初始化
     public Handler(Callback callback, boolean async) {
          mCallback = callback;
    }
    

    这里看得出我们可以用一个Callback来处理这些msg。
    雪花牛肉给女朋友吃,我吃雪花

    1. handleMessage(msg); 如果前面两个都不是的话就执行handleMessage了。这也是一般情况的用法。

    关于唤醒

    是否能nativeWake的布尔值,needWake = mBlocked;在发送普通消息的时候,是直接把mBlocked这个值赋给needWake的。可这就奇怪了。
    阅读源码的时候发现 mBlocked = true;只有在pendingIdleHandlerCount <= 0 也就是说闲置的时候才会为true。那么逻辑上好像说不通。

    1. 虽说可能在刚MessageQueue创建的时候就进入闲置状态的话,这个值设置为true后,有新消息过来因为needWake = mBlocked == true,可以执行唤醒。
    2. 但是发送完第一条后,在next获取到msg后就执行mBlocked = false;所以即使后面连续发好几条过来,也是不会唤醒的。
    3. 按我翻阅网上的blog,nativePollOnce如果没有nativeWake的话是会一直阻塞的。
    4. 有个叫removeSyncBarrier的方法有nativeWake方法,其实去掉消息屏障。消息屏幕一般是在屏幕刷新用的。刷新完了就把消息屏障去掉,然后唤醒,执行之前被屏障的消息。
    final boolean needWake;
    
                Message prev = null;
                Message p = mMessages;
                while (p != null && (p.target != null || p.arg1 != token)) {// 跳出循环的时候prev 不是消息屏障,p是消息屏障
                    prev = p;
                    p = p.next;
                }
    if (p == null) { // 如果发现没有消息屏障,抛出异常
                    throw new IllegalStateException("The specified message queue synchronization "
                            + " barrier token has not been posted or has already been removed.");
                }
                if (prev != null) {// prev 不为空,说明消息屏障并不在链表头
                    prev.next = p.next; // 这个操作相当于把消息屏障 p remove掉,
                    needWake = false; // 这种情况不需要唤醒
                } else {
                    mMessages = p.next;  // prev 为空,证明链表头开始的就是屏障,p.next就是屏障连接者的最新的消息
                    needWake = mMessages == null || mMessages.target != null; // mMessages为null可以执行idle任务,或者target不为空就说明不是消息屏障也需要唤醒
                }
                p.recycleUnchecked();
    
                // If the loop is quitting then it is already awake.
                // We can assume mPtr != 0 when mQuitting is false.
                if (needWake && !mQuitting) {
                    nativeWake(mPtr);
                }
    

    可以看的出来,removeSyncBarrier是有唤醒操作的,说明在屏幕刷新完后会唤醒阻塞。

    流程总结

    韩国烤肉挺好吃的。

    发消息:Looper.loop() => handler.enqueueMessage(msg) =>MessageQueue.enqueueMessage(msg)

    收消息:MessageQueue.next => Looper.loop() msg =>Message.target. dispatchMessage
    => 1. handleCallback(msg);
    => 2. mCallback.handleMessage(msg)
    => 3. handler.handleMessage(msg)

    相关文章

      网友评论

        本文标题:详细分析Handler消息机制

        本文链接:https://www.haomeiwen.com/subject/hxbtohtx.html