美文网首页安卓
烂大街的 Android 消息机制再来回顾一下

烂大街的 Android 消息机制再来回顾一下

作者: jkwen | 来源:发表于2021-06-28 18:53 被阅读0次

    Android 的消息机制应该是入门 Android 就会接触到的,很多人第一个接触的可能就是 Handler 的使用,但其实那只是冰山一角,今天就来回顾梳理一下这方面的知识点,虽然很基础,但依然值得再去看,按哲学的思想,每次来看都是新的。

    Looper

    提供了 prepare 方法,当然考虑到主线程的特殊性,单独提供了 prepareMainLooper 方法。prepare 的工作主要就是创建 Looper 对象,并将该对象和当前线程相关联。

    用来做关联的就是 ThreadLocal 数据结构,更准确的说 ThreadLocal 内部有个ThreadLocalMap 静态类,它是线程对象为 key,与之关联的对象为 value 的一个集合。

    MessageQueue

    在创建 Looper 对象时创建,就是我们常说的消息队列。消息队列的操作将通过 Looper 对象实现,例如 enqueueMessage,next 等。MessageQueue 里消息队列的实现用到了链表结构。

    Message

    作为消息的载体,它是一个链表节点形式的数据结构。里面包含了一个常用字段,what, 表示消息类型,好在处理消息时能知道怎么处理,arg1, arg2, 用来简单的传递一些 int 值,obj, 按照源码注释,其实是用于进程间传递的数据,但用于平常的数据传递也可以,data, 这个是 Bundle 类型,除前面说的场景以外,其他情况的数据传递就可以用 setData 方法来赋值,callback 是个 Runnable 类型的回调,后面在 Handler 里会用到。

    说 Message 是个链表节点,那就必须会有指向下一个的 next 变量。另外,可能考虑到 Message 对象会频繁的创建和释放,在实现上,增加了一个消息池的缓存机制。

    这个消息池其实也是一个链表。当需要创建新消息时,先从消息池的链表头拿一个,如果消息池里没有,就会新创建。当消息用完后,会被回收进池子(从消息池头部插入的方式),当然如果池子无限制的缓存下去也是不现实的,所以池子最大是 50.

    有了以上三个角色仿佛消息机制可以运转了,但可能考虑到面向对象的设计原则吧,对于消息的发送和处理工作交给了 Handler,而 Looper 则负责运营好消息队列。这样一来,各自职责清晰单一。

    Handler

    Handler 对象就是那个打工人,它归顺于它的消息队列(或者说 Looper,线程),一但来消息了就处理,一但需要发消息了就发送,可如果不是它所属的消息队列里有消息,它是不会管的。

    Handler 对象的创建方式主要分两类,一类是指定 Looper,这样一来也就指定了线程,和消息队列,另一类则是默认的以当前所在线程关联的 Looper 为默认值。

    在消息处理上,我们需要留意一下处理优先级,

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

    首先,如果 Message 对象的 callback 变量不为空,则会对该回调进行处理消费,且不再处理其他字段的数据。这种情况对应怎么样发消息呢?就是通过 Handler 的 post 方法,消息会以 Runnable 的形式添加到队列中,常用的还有 postDelayed。

    其次,Handler 的构造方法可以传入 Callback 接口对象,之后能通过它去回调 Message 对象并处理。这种情况是针对,不想创建 Handler 子类,仅想去处理消息的场景,其实我们平常应该用这种情况比较多,但实际上我们通常会去实现 Handler 子类,并重写 handleMessage 方法,来处理消息,这也就是最后的消息处理。

    所以对于一个消息的处理,首先判断消息本身的 callback 存不存在,不存在则会通过 Handler 对象的 mCallback 处理,如果 mCallback 对象也不存在,再去看 handleMessage 方法能不能处理。

    一个消息队列可以有多个 Handler 对象吗?

    构造方法里没有看到数量的限制,所以对于同一个线程,可以创建任意多个 Handler 对象。

    A Handler 对象发出的消息可以由 B Handler 对象处理吗?

    这点应该是不行的,A Handler 发送的消息只能 A Handler 处理。举个反例,A Handler 能处理 what == 1 的消息,B Handler 也能处理,此时如果 A 发送了一个该类型消息,B 去处理了,那么结果可能就不是 A 想要的,或者 A 都感知不到。

    再结合源码看下,

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //Handler 发送消息最终会调用该方法
        //Message 对象的 target 变量就是一个 Handler 类型
        //此时 Message 将当前发送的 Handler 赋值给了 target,两者有了某种关系
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    添加到消息队列后,Looper 对象会在检查消息队列时发现这个消息并取出准备进行处理。这个检查是个无限循环,也就是说在该线程的生命周期上都会一直检查。

    public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next();
            try {
                //从这句可以看到,Message 对象调用的就是 target 变量的 dispatchMessage 方法
                //这也就验证了 A Handler 发出的消息只能 A Handler 处理。
                msg.target.dispatchMessage(msg);
            }
            //将 Message 对象回收到消息池里
            msg.recycleUnchecked();
        }
    }
    

    由此分析,再延伸出两个问题,

    这里的无限循环不会造成线程卡死吗?

    这原理上分析起来还挺麻烦,简单来说就是采用了 Linux 的 pipe/epoll 机制,在执行 queue.next 这步时,如果队列里没有消息就会调用 nativePollOnce 使线程进入休眠状态,这样一来线程就会释放资源,代码执行也就阻塞在这里了。当有新的消息进入队列后,会调用 nativeWake 唤起线程,这样也就能继续执行下去了。

    如何结束这个无限循环?

    调用 quit 方法,但对于主线程,默认是不允许退出的,如果强行退出将会抛异常结束。因为 Android 设计本身就是基于这套消息机制,像 Activity,Service 等都基于此,主线程循环的退出也就意味着应用生命周期的结束。

    还有一点要了解

    当我们发出消息后,不想要这个消息了,那么在消息被处理之前可以通过 removeMessages 方法来移除指定 what 的消息,如果想要移除该 Handler 相关联的所有消息,则可以调用 removeCallbacksAndMessages 方法,入参指定为 null 即可。

    参考内容

    Android中为什么主线程不会因为Looper.loop()里的死循环卡死

    相关文章

      网友评论

        本文标题:烂大街的 Android 消息机制再来回顾一下

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