详细分析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