Handler消息机制源码全量解析

1,534 阅读19分钟

Handler消息机制源码解析

Android版本: 基于API源码26,Android版本8.0。

本片文章的目的在于全面的了解Handler。它是如何传递消息的?是如何阻塞和唤醒线程的(仅限于Java层面)?MessageQueue到底是怎么存储和取出Message?延迟消息是怎么被发送的?

一 Handler定义:

Handler是一套消息传递的机制。设计用来让工作线程跟主线程之间进行消息传递。同时也解决了多线程同时更新UI的问题。Android系统设定子线程是不能更新UI的,当子线程一定要做更新UI的操作时,可以使用Handler将消息从子线程带到主线程,并能保证消息的同步性。

二 基本用法:

  1. 在主线程中使用:

    //实例化Handler对象。
    Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
    };
    //创建并发送消息
    Message obtain = Message.obtain();
    obtain.what = 100;
    handler.sendMessage(obtain);
    
    
  2. 在子线程中使用,一旦使用该子线程是不会自动结束的,除非手动结束:

    //为子线程中创建Looper对象。
    Looper.prepare();
    //实例化Handler对象。
    Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
    };
    //创建并发送消息。
    Message obtain = Message.obtain();
    obtain.what = 100;
    handler.sendMessage(obtain);
    //开启消息循环。
    Looper.loop();
    

如果先调用Looper.prepare()、Looper.loop()再handler.sendMessage(obtain),那么消息会成功的发送成功吗?

三 源码解析:

Handle的消息传递过程中涉及到了几个重要的类:Handler、Message、Looper、MessageQueue。

  • Handler

    官方的解释:Handler允许您发送和处理MessageMessageQueue有关联的可运行对象Runnable。每个Handler实例都与一个线程和该线程的消息队列关联,在创建的时候就会直接关联起来。Handler将Message和Runnable传递到消息队列中,并在它们从消息队列中出来时执行它们。

    Handler被用在两个主要的地方:让Message和Runnable定时执行;在非UI线程中将Message或者Runnable发送到主线程。

    当应用程序创建进程时,其主线程专用于运行消息队列,该队列负责管理顶级应用程序对象(activities、broadcast receivers等)及其创建的任何窗口。

    也就是Handler是用来专门发送和处理消息的。发送是只发送到对应的消息队列就好了。

  • Message:

    官方解释:定义包含描述和可发送到{@link handler}的任意数据对象的消息。此对象包含两个额外的int字段和一个extra对象字段,在许多情况下允许您不进行分配。

    也就是Message是Handler传递的元数据,其内部可以包含很多信息:what、arg1、arg2,、obj等。Handler发送的也是Message对象,处理的也是Message对象。所以Message肯定是序列化的,继承自Parcelable。内部维护了一个缓存池,避免创建多余的消息对象,缓存池的设计是Android系统源码一贯的作风,在ViewRootImpl处理点击消息的时候,也有类似设计。该缓存池的大小为50,超过该缓存就会直接创建Messsage对象。

  • Looper:

    官方解释:Looper类用于为线程运行消息循环。默认情况下,线程没有与之关联的消息循环;若要创建一个消息循环,请在要运行该循环的线程中调用 prepare()方法,然后调用loop()使其处理消息,直到循环停止。

    也就是Handler只是将Message数据放到了MessageQunue中,Looper才是从MessageQunue中取出消息,并将消息发送到正确的Handle中去。一个线程中存在一个Looper,Looper中维护着MessageQueue。Looper.prepare()方法是创建Looper跟MessageQueue,再发送消息之后调用Looper.loop()方法,开启消息循环,也就是不停的从消息队列中那出消息处理。

  • MessageQueue:

    该机制中核心的存在。主要是用来操作Message的,实际上内部并没有一个集合或者队列去存储Message,而是由Message组成的单链表结构。在Message的内部有个next属性,属性声明为Message对象,也就是Message自身就可组成一个队列。MessageQunue的目的就是增删对比Message。比如:第一个Message对象来了(m1),那么它的next属性是空的,然后又来了一个Message(m2)。发现m1还没执行完毕,那么就将m2的对象赋值给m1的next属性,当m1执行完毕之后,取出自己的next属性,如果不为null,那就接着执行next属性表示的Message,也就是m2。

当了解完每个类的职责之后,发送的消息的流程就会清晰的很多。

一般的,一个Thread应该只有一个Looper和MessageQueue。可以有多个Handler对象。当你在主线程中创建Handler的时候,你是不需要调用Looper.prepare()的,因为主线程已经创建过了。创建Looper的线程决定了消息最终是在那个线程中被处理的。你在子线程中使用Handler对象发送消息,如果你的Looper是主线程中创建的,那么处理消息的还是在主线程中,你发送消息的线程环境是无关紧要的。

Handler创建源码解析:

Handler类是个普通的Java类,可以被继承。创建方式为:

//一般通过new的方法去创建。
Handler handler = new Handler();

Handler有七个构造方法,三个是被隐藏掉的。被注解@hide注释之后,通过api是访问不到的:

  //Handler.java  
    public Handler() {
        this(null, false);
    }
    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Looper looper) {
        this(looper, null, false);
    }

    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
   /**
     * @hide
     */
    public Handler(boolean async) {
        this(null, async);
    }

   /**
     * @hide
     */
    public Handler(Callback callback, boolean async) {
        //。。。省略代码
        //获取Looper对象。
        mLooper = Looper.myLooper();
        //。。。省略代码
        //获取MessageQueue。
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    /**
     * @hide
     */
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到构造参数中可以传入的类型:Callback接口,Looper对象,布尔值async。首先Callback接口也是处理消息的:

//Handler.java 
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
}

Handler类内部也有handleMessage()方法,使用的时候完全可以重写该方法。Handler又提供该接口的目的也就是你可以不用实现Handler类中的方法也能处理消息,这样自由一点。

如果构造方法中你传入了Looper对象,就不需要去创建Looper对象了。至于布尔值async则是决定当前发送的Message是否是异步的消息,异步消息可以不受同步屏障的影响。在ViewRootImpl接收到绘制信号的时候,就会在UI Handler中发送一个同步屏障,这将阻止当前Handler处理所有的同步消息,然后让该此异步消息先执行。Handler的创建过程很简单,接下来看Message的创建过程。

Message创建源码解析:

Message类是final的不能被继承。实现了Parcelable接口。Message并没有特殊的构造方法,所以直接就可new出来:

Message message = new Message();

另外系统推荐的获取Message的方法为:

Message obtain = Message.obtain();

obtain()方法有很多的重载方法,根据参数的不同构造不同的Message对象。该过程都是在Message内部实现的,对于调用着来说是透明的,下面来分析下基础的obtain()方法:

//Message.java。
public static Message obtain() {
        synchronized (sPoolSync) {
            //判断缓存池是否为null。
            if (sPool != null) {
                Message m = sPool;
                //取出一个Message。
                sPool = m.next;
                m.next = null;
                //默认的flag为0。
                m.flags = 0; // clear in-use flag
                //大小减一。
                sPoolSize--;
                return m;
            }
        }
        //否则就new一个
        return new Message();
    }

sPool是一个Message对象,相当于缓存队列的头指针。Mesage有个next属性,该属性还是Message对象。当调用该方法的时候,就从sPool中取出下一个Message也就是m.next,然后将m.next赋值给sPool,也就是重置队列头,然后返回之前的队列头m。这种是典型的单向链表结构,Android系统的源码中很多地方都用到。然后在Message的reaycle()方法中,将使用过的Message存放到sPool代表的队列中。

//Message.java。
public void recycle() {
        if (isInUse()) {
            return;
        }
        recycleUnchecked();
    }

    void recycleUnchecked() {
    //。。。省略代码
        synchronized (sPoolSync) {
            //判断缓存的数量是否小于规定值的
            if (sPoolSize < MAX_POOL_SIZE) {
                //将队列头指针指向下一个元素
                next = sPool;
                //队列头重置,这样新的消息就变成了队列的第一个元素,之前sPool表示的消息就变成该消息的next。
                sPool = this;
                sPoolSize++;
            }
        }
    }

Message的创建很简单,需要注意的就是这个缓存池的原理。

消息发送流程源码解析:

Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });

 Message obtain = Message.obtain();
 obtain.what = 100;
 handler.sendMessage(obtain);

上述代码即可发送一个消息数据。不管是sendMessage()还是sendMessageDelayed()或者sendEmptyMessage()方法,最终都是调用的sendMessageAtTime()方法,sendEmpty***系列是Handler自己创建了一个空的Message,只是在使用着看起来是发送了一个空的消息一样,这里不再分析:

//Handler.java。
public boolean sendMessageAtTime(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);
    }

该方法中只是判断了MessageQueue是否为null。

//Handler.java。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       //该消息的目标Handler为当前发送消息的Handler。
        msg.target = this;
        //如果是异步消息的话就设置为异步标记。
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

对Message对象做了一些标记之后就调用了MessageQueue的enqueueMessage()方法,这两个标记都是很重要的,一个是设置目标Handler,这样消息就不会让错误的Handler处理。另外一个是表明消息是否是异步的,一般使用的时候都是同步消息,UI线程毕竟是线程不安全的:

//Message.java。 
public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

接着看queue.enqueueMessage()方法:

//MessageQueue.java。
boolean enqueueMessage(Message msg, long when) {
       //如果消息没有目标Handler就抛出异常。
        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.");
        }
       //做了同步处理
        synchronized (this) {
            //当前的消息队列已经结束了。mQuitting = true。
            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;
            }
            //标记正在处理中
            msg.markInUse();
            //消息的倒计时,多久之后发出。
            msg.when = when;
            //mMessages表示当前准备处理消息。
            Message p = mMessages;
            boolean needWake;
            //1.当前要处理的消息为null。2.需要马上发送,延迟时间为0。3.如果当前要处理的消息的延迟比正在处理的消息时间段。
            //上面三个条件满足一个就执行。如果是第一条消息if肯定会执行,如果来了第二条Message,但是第一条Message还没执行完,也就是mMessages不为null,消息的when还等于0,那么也要直接执行,也就是添加到队列的头部。所以markInUse()方法还是很有必要的。
            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 {
                //当下一条消息来的时候发现前面还有消息没执行完,并且当前的消息也不是立马就执行的,或者等待的时间比正在执行的消息要长,那么就执行到这边。
               
                //p.target == null表示当前的消息是一个同步屏障。
                //needWake = true,说明当前的前一条消息是个同步屏障,并且当前的这条消息是个异步消息。
                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;
            }
            //插入队列中间。通常我们不必唤醒事件队列,除非队列的开头有一个屏障,并且消息是队列中最早的异步消息。       
            //判断是否唤醒线程。
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

上面的入对列操作很复杂:

  1. 一条新的消息到了,调用消息的markInUse方法。
  2. 判断是否要立马执行该消息。条件有三个:当前要处理的消息为null;延迟时间为0;如果当前要处理的消息的延迟时间比正在处理的消息时间短。
  3. 满足步骤2的三个条件,就将当前的消息放到消息对列的头部位置。needWake变量赋值。
  4. 当一条新的消息到来的时候,不满足步骤2,也就是当前有正在执行的消息,并且该消息需要延迟发送,延迟的时间还比较长。那么就会执行else方法。
  5. 走到else方法中,要先判断当前正在执行的Message是否是一个同步屏障,也就是 p.target == null,然后判断当前的消息是否是一个异步消息,因为异步消息的话,是需要立马执行的。然后有个For循环,判断当前的队列中是否有个异步消息还在处理中,有的话也就是 if (needWake && p.isAsynchronous())会执行,那么此条消息就不能立马执行。否则就要加入到队列中等待了。
  6. 判断needWake,是否唤醒线程。

经过上面的步骤,Handler将发送的Message放入到了消息队列中。Handler类的职责已经结束了。下面来看Looper到消息队列中取数据。

Looper源码解析:

Looper类的注释中说明了,在使用的时候要先调用Looper.prepare()方法,最后调用Looper.loop()方法。下面先看下prepare()方法:

   //Looper.java。
   //该方法是可以在任何线程中调用。
   public static void prepare() {
        prepare(true);
    }

    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));
    }
    //该方法在ActivityThread创建的时候会被调用。
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

prepare()系列方法就是创建一个Looper对象。采用prepareMainLooper()方法创建的Looper不会终止消息循环,其他的都可以被终止掉,也就是quitAllowed = true/fasle的问题。Looper创建结束之后看下loop()方法:

//Looper.java。
public static void loop() {
        final Looper me = myLooper();
         //。。。省略日志代码
        //Handler跟Looper用的是同一个队列。
        final MessageQueue queue = me.mQueue;
        //确保此线程的标识是本地进程的标识,并跟踪该标识令牌的实际内容。
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            //从消息队列中取出下一条消息。
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //。。。省略日志代码
            try {
                //msg.target就是Handler,然后分发消息。
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
              //。。。省略日志代码
            }
           //。。。省略日志代码
            //消息处理结束之后回收消息。
            msg.recycleUnchecked();
        }
    }

loop()方法要做的就是不停的从MessageQueue中获取消息,所以就使用了For死循环。拿到消息之后,开始分发消息,最后将消息回收。重点在于Message的next()和Handler的dispatchMessage()以及最后Message的recycleUnchecked()方法了。先看next()方法:

//MessageQueue.java
Message next() {
        //如果消息循环已经退出并被释放,则返回此处。如果应用程序在退出后尝试重新启动不受支持的循环程序,则可能发生这种情况。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //空闲时待处理的IdleHandler的数量。默认为-1。
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        //下次轮询超时毫秒
        int nextPollTimeoutMillis = 0;
        //开启死循环。要么阻塞,要么获取到Message。
        for (;;) {
            //将当前线程中挂起的任何绑定器命令刷新到内核驱动程序。在执行可能会阻塞很长时间的操作之前调用,以确保已释放任何挂起的对象引用,以防止进程占用对象的时间超过所需时间,这将非常有用。
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //该方法可能会被阻塞,nextPollTimeoutMillis = -1的时候,将无限期的阻塞。也就是说当该方法有返回值的时候,那就代表该过程不会被阻塞,也就是会执行下面的代码逻辑,也就是将会至少取出一个消息。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 获取系统时间。
                final long now = SystemClock.uptimeMillis();
                //该Message是表示将被执行的消息的前一个消息,有可能是从消息队列的中间取出一个消息执行,这样需要将被取出消息的next拼接到被取出消息的上一个消息的next上,这样队列不会被断开。
                Message prevMsg = null;
                //队列的头消息。每次取出一个消息的时候都会将mMessages指向新的消息。
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                   //执行到这里说明当前的消息是一个同步屏障,也就是将阻止所有的同步消息。
                   //找出队列中的第一个异步消息,没有异步消息msg就是null。 
                    do {
                        //找出消息的上一个消息。
                        prevMsg = msg;
                        //当前找出的消息。
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                //这里并不是else语句,所以就是不是同步屏障就直接执行到这里。
                if (msg != null) {
                    if (now < msg.when) {
                        // 下一条消息尚未准备好。设置一个超时,以便在它准备好时唤醒。也就是nextPollTimeoutMillis参数其实就是上面nativePollOnce()本地方法的参数,也就是阻塞的时长。
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //走到这里说明找到了符合要求的Message,准备返回了。
                        mBlocked = false;
                        if (prevMsg != null) {
                            //prevMsg不为null表示当前的msg是一个异步消息,那么其值表示msg消息的上一个消息。也就是msg是从队列中间取出来的,那么就需要把队列拼接完整了。
                            prevMsg.next = msg.next;
                        } else {
                            //走到这里说明msg是一个同步消息,也是从队列的头部获取到的,那么就重置mMessages为msg的下一个消息。
                            mMessages = msg.next;
                        }
                         //清空该消息的next属性,这样该消息就是一个独立的消息。在上一步中该消息的next属性已经赋给了队列中的其它Message。
                        msg.next = null;
                        //标记正在使用
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 没有消息就为-1.-1就表示该线程将无限期的阻塞。
                    nextPollTimeoutMillis = -1;
                }
                //处理完所有挂起的消息后,立即处理退出消息。
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //。。。省略了空闲时处理IdleHandler的逻辑。IdleHandler对象,IdleHandler是一个接口,也就是继承了该接口的事物将会在MessageQueue空闲的时候去处理。GC机制就是采用的这个东西触发的。
               if (pendingIdleHandlerCount <= 0) {
                    //没有东西可以执行了,就开始等待工作。
                    mBlocked = true;
                   //continue继续执行for循环,然后在执行nativePollOnce()方法,线程阻塞。
                    continue;
                }
        }
    }

在Looper的loop()方法中第一个重要的就是MessageQueue的next()方法。该方法有以下几点:

  1. 先开启一个For死循环,做的跟Looper的loop()方法一样。之后先阻塞线程,通过调用nativePollOnce()方法。当线程被唤起,也就是nativePollOnce()方法有返回值了,就执行后面的逻辑。
  2. 先获取当前的时间,然后跟消息的延迟时间做对比。mMessages变量表示的就是消息队列中的头布局,在MessageQueue的equeueMessage()方法中先被赋值,先是说第一此赋值的,接着在next()方法中还会被赋值。
  3. 判断Message是不是一个同步屏障,稍后解释。也就是msg.target == null,是的话就要拿出队列中第一个异步消息,也就是Message的isAsynchronous()方法返回true。如果不是同步屏障,就继续往下走。
  4. 这里再次都Message判空,是因为上一步,如果当前的消息是同步屏障的话,又找不到异步消息,那就可能返回null。
  5. 消息不为null的时候,就判断消息的延迟时间是否已经到了。如果还没到的话,就计算出剩余的事件,然后传值给nextPollTimeoutMillis变量,第1步的时候nativePollOnce()方法会用到该值,判断倒计时多久之后唤醒线程。所以该倒计时时间确定之后,MessageQueue的next()方法中的for循环再次执行到nativePollOnce()方法的时候,就会执行该倒计时。
  6. 如果需要立马执行的话,mBlocked先变为false,也就是在MessageQueue的queueMessage()方法此时添加Message的时候就不用唤醒线程了。之后判断prevMsg是否为null,prevMsg表示了当前要执行的消息msg的前一个消息,不为null,表示msg是从队列中间位置取出来的执行的,那么这一步就得把队列继续链接起来。如果prevMsg为null,也就是说msg是从队列中的第一个位置取出的,只需要将mMessages变量重新指向msg的下一个消息就好了。
  7. 将要执行的消息msg的next属性清空,然后返回。
  8. 如果出现了同步屏障,并且下一个异步消息也没找到,那就是msg就是null 的,nextPollTimeoutMillis = -1,for循环的下次将会被阻塞住,这是个next()方法中的for死循环。
  9. 判断是否要退出。主线程是不允许被退出的。
  10. 处理那些空闲的时候才处理的IdleHandler事件,然后结束之后,mBlocked被置为true。也就是说当MessageQueue中没有任何事情可做的时候,mBlocked才是true。

上述就是MessageQueue的next()方法。经过该方法后继续看Looper的loop()方法:

//Looper.java。
public static void loop() {
        //。。。省略代码
        for (;;) {
            //。。。省略代码
            try {
                //msg.target就是Handler,然后分发消息。
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
              //。。。省略日志代码
            }
           //。。。省略日志代码
            //消息处理结束之后回收消息。
            msg.recycleUnchecked();
        }
    }

这里获取到Message的target属性,然后调用了dispatchMessage()方法。target属性是在Handler的enqueueMessage()方法中赋值的:

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

也就是当前Handler对象。之后就是Handle的分发方法:

//Handler.java
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

代码一目了然。如果msg.callback不为null,说明当前的消息是Runnable对象。不是的话,判断mCallback是否为null,也就是Handler的构造方法中的参数Callback,不为null就调用Callback的handleMessage()方法。再接着就是调用自身的handleMessage()方法。到这Handle的源码分析就结束了!重点难点全在MessageQueue类中。

结尾:

文章可能会有理解错误的地方,希望大家多多评论指出~
我的Github
我的掘金
我的简书
我的CSDN