Android Handler 消息处理机制

看到这篇文章写于 2017.4.16,基于源码做了整理和分析,只能说仅写了表面的处理流程,知道 What 和 How,至于为何这样子设计以及一些深层的知识点并没有涉及。今天是 2022.4.6 日,重点整理一下 Why 与之前不清楚的点。

大纲

  • 什么是异步消息处理线程
  • Looper、MessageQueue、Message、Handler 源码分析与工作原理
  • 关于 Handler 几大关键问题
  • 源码流程分析:消息如何入队?如何出队被处理?消息队列何时被阻塞、何时被唤醒?阻塞唤醒机制如何?IdleHandler 何时被调用?loop 是否可停止?Message 如何实现回收和重复利用?

一、概述

Android消息机制主要是指 Handler 的运行机制以及 Handler 所依赖的 MessageQueue 和 Looper 的工作过程。

在具体谈论这三者之间关系之前,先来了解一下什么叫 异步消息处理线程

异步消息处理线程 启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理方法,执行完成后继续循环。当消息队列为空是,线程就会被阻塞等待。

这里无限循环和取消息是通过 Looper 来实现,消息队列指的就是MessageQueue,实为单链表,用来存放消息Message,Handler便是用来处理消息的。

Looper是一个特殊的概念,将一个普通线程变成一部消息处理线程就必须使其拥有 Looper,若需要使用 Handler 就必须为线程创建 Looper 。例如常用的主线程,也即UI线程,对应 ActivityThread,在其 main() 方法中创建时就会初始化 Looper,这也是在主线程中能使用 Handler 的原因。(详情可查看源码:ActivityThread.main())

如下为创建异步消息处理线程的示例代码:

Class LooperThread extends Thread {
    private MyHandler mHandler;
    class MyHandler extends Handler {
        public MyHandler(String name) {
            super(name);
        }
        
        @override
        public void handleMessage(Message msg) {
            // 处理消息
        }
    }
    
    @override
    public void run() {
        Looper.prepare();
    
        // do some initialization
        mHandler = new MyHandler();
        
        Looper.loop(); // 循环取消息
    }
}

接下来分别介绍下 Looper、MessageQueue 以及 Handler 的工作原理,对上述有疑问的,可继续查看以下内容。

二、关键流程分析

1、代码流程

一般我们在主线程使用 Handler 时,只需要构建消息,然后发送消息,然后 Handler 回调中处理消息。

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
        }
    };
    
    private void sendMessage() {
        Message msg = Message.obtain();
        handler.sendMessage(msg);
    }

前面也说到,ActivityThread 是异步消息处理线程,在其 attach() 方法中创建了 Looper 并且调用 Looper.loop() 使主线程无限循环,把各个关键类构建并相互关联的逻辑可简单抽象如下:

// 1. Thread.run() 创建 Looper 并存放到 ThreadLocal 中
Looper.prepare(); => sThreadLocal.set(new Looper(quitAllowed)); quitAllowed 默认为 true

// 2. new Looper() 构造时创建 MessageQueue
new Looper(quitAllowed); => mQueue = new MessageQueue(quitAllowed);

// 3. new Handler() 构造时关联 Looper 有 MessageQueue
new Handler(); => mLooper = Looper.myLooper(); mQueue = mLooper.mQueue;  mAsynchronous = async;

// 4. Looper.loop() 循环中,获取当前线程的 Looper,然后在其 MessageQueue 中取出 Message 消息,通过 msg.target.dispatchMessage(msg); 分发给 Handler 进行处理
Looper.loop() => Looper me = myLooper(); Message msg = me.mQueue.next(); msg.target.dispatchMessage(msg);
 
// 5. handler.sendMessage(Message)发送消息入队流程
Handler.sendMessage(Message); => msg.target = this; mQueue.enqueueMessage(msg, uptimeMillis);

// 6. 队列取消息&分发处理流程
Looper.loop(); => mQueue.next(); msg.target.dispatchMessage(msg);

整理类设计关系如下:

  • 一个 Thread 对应一个 Looper,并通过 ThreadLocal 保存一一对应关系;
  • 一个 Looper 对应一个 MessageQueue,MessageQueue 维持 Message 消息队列;
  • Thread 与 Handler 是一对多关系,Looper 与 Handler 也为一对多关系,反之不行(因为 Looper.prepare() 判断一个线程只能有一个 Looper);

总结就是,Thread、Looper、MessageQueue 是一对一关系,与 Handler 是一对多关系。

通过这些关系,可以看出这套流程就是 生产者-消费者模式,生产者为 Handler,消费者为 Looper,MessageQueue 则为中间队列。生产者可以有多个,但是消费者只有一个,消息队列也只有一个。

2、关键问题

1. Handler 设计理念与作用。

Handler 的意义就是为「切换线程」而存在,为解决子线程无法访问 UI 问题。常见使用场景:

  • 跨进程界面消息处理,AMS 中 Binder 线程与 ApplicationThread 通过 Handler 将消息丢给主线程处理。
  • 网络请求,子线程切换到主线程。
2. 为啥不建议子线程更新 UI ?
  • 降低 UI 访问效率。必然涉及到线程安全问题。若存在线程安全,则必然会有加锁阻塞,如此可能会在用户操作时,因为阻塞导致体感卡顿,体验差问题。
  • UI 处理复杂化。UI 创建与更新,本身简单逻辑,加锁后会整复杂。

分析:从「单线程模型」去理解,常见的 GUI 还有 Swing、Flutter 都是 单线程事件驱动
从《Java 并发编程实战》第9章有给出 “为什么 GUI 是线程的” 解说:

  • 早期的 GUI 应用程序都是单线程,且在 GUI 事件在 “主事件循环” 进行处理。
  • 当前 GUI 框架模型略有不同,“在模型中创建一个专门事件分发线程来处理 GUI 事件”。
    为何如此设计?因为多线程 GUI 框架,很容易出现「竞态条件和死锁」导致的稳定性问题。
  • 死锁一方面在于,输入事件处理过程与 GUI 组件的面向对象模型直接会存在错误的交互。
  • 用户操作,犹如 “气泡上升”,从操作系统传递给应用程序,而应用程序引发的动作,又如 “气泡下沉” 从应用程序返回到操作系统,如修改背景色触发系统绘制,如此要保证每个对象是线程安全的,避免顺序不一致。

另一方面,基于 MVC 模块分层设计,也会存在不一致锁定顺序的风险,如此也会出现死锁。

单线程 GUI 框架通过「线程封闭」机制来实现线程安全性,所有 GUI 对象都被封闭在事件线程中访问。

但是串行也有劣势,主要在于若事件链某一节点处理耗时严重,就会出现所谓的 ANR 。

3. Handler 如何实现线程切换?

说线程切换,不要认为真就线程切换,实际是指 “把子线程得到的消息数据” 通知给 “主线程”,“主线程” 获取到消息后再处理。 本质上就是一个在子线程生产,主线程消费的流程,中间通过消息队列实现数据共享。

简单理解,子线程发送消息,存到了 MessageQueue 中,主线程不停从 MessageQueue 取消息处理。【 生产者-Handler;消费者-Looper 】

4. Loop 不会导致死循环原因 。

第一点,主线程本身是要一直运行下去,因为要处理各个 View、界面变化,所以需要这个死循环保证程序一直运行,不会退出。【可参考 Java 应用程序执行完 main 就直接死掉了,但是开启个后台循环打印程序会一直存活】

第二点,真正会导致卡死的地方,在于处理某个消息耗时过久,导致掉帧、ANR,而不是 loop 本身。

第三点,主线程外,还有 Binder 线程会处理进程间通信消息,收到该类消息会交给 Handler 进行消息分发,所有 Activity 生命周期都是依靠主线程的 Looper.loop() 。

第四点,没有消息时,loop() 会阻塞,但是基于 epoll 机制,此时主线程会释放 CPU 进入休眠状态,直到下个消息到达,所以不会浪费 CPU 资源。

5. Handler 发送消息的几种方式?如何加入到队列中?

主要有两种发送方式:send 形式与 post 形式。

Message msg = Message.obtain();

// 1、send 方式,Message.callback = null
handler.sendMessage(msg);
handler.sendMessageAtTime(msg, System.currentTimeMillis());
handler.sendMessageDelayed(msg, 1000);

// 2、post 方式,Message.callback = Runnable
handler.post(() -> { });
handler.postDelayed(() -> { }, 1);

两者区别在于通过 next() 取出消息后,执行 msg.target.dispatchMessage() 分发消息时,

  • send 方式,最终回调给 handlerMessage() 方法处理消息;
  • post 方法,最终回给 post 的 Runnable.run() 方法处理。
  • 优先级为:msg.callback != null,则执行 msg.callback.run(); 否则为 handler.handleMessage();

对于消息入队管理逻辑,MessageQueue 为单链表, 链表节点为 Message 对象,Message 拥有 next 指针,通过该指针对消息进行排队。如何确定消息的优先级?

  • 在 MessageQueue 中没有优先级的概念,只有执行时间早晚的概念,入队时,会根据执行时间进行排序,先执行的排前面,后执行的则插入到指定位置。发送消息有直接发送与延时发送,通过Message.when 属性确定消息执行时刻,计算公式:msg.when = SystemClock.uptimeMillis() + delayMillis; 真正对msg.when 进行赋值是在下方入队方法中。
  • mQueue.enqueueMessage() 中通过比对 msg.when,找到插入的位置则结束 for 死循环。
6. 如何理解同步屏障 ?

要理解这个问题,必须要知道 Handler 处理的消息类型,根据源码可把消息分为三类:

  • 同步消息,也即为一般消息,平常发送的都是这类;
  • 异步消息,Message 通过其 public void setAsynchronous(boolean async) 设置消息为同步还是异步的 ,判断是否异步消息使用 isAsynchronous(),所以可以在创建 Message 消息时设置该异步标签,另外 Handler 拥有 boolean mAsynchronous 属性,若 Handler 仅处理异步消息,则入队那一刻,该 Message 就会标记为异步的。
  • 同步屏障消息,若消息是通过 mQueue.postSyncBarrier() 方法入队的,则为同步屏障消息,通过 final int token = mNextBarrierToken++; msg.arg1 = token; 标记,该类消息是没有 obj 的。

表格对比如下:
7. 如何理解阻塞唤醒机制?
  • 入队时,若当前队列被阻塞,此时队列为空 || 首节点为同步屏障消息&&入队消息刚好是异步消息,则需要被唤醒,方法调用:MessageQueue.nativeWake(mPtr);
  • 出队时,若队列无消息 || 未到消息执行时间,则调用 nativePollOnce(ptr, nextPollTimeoutMillis); 进行阻塞。前者阻塞时间 nextPollTimeoutMillis=-1 表示一直阻塞,当有消息时被唤醒;后者 nextPollTimeoutMillis=msg.when - now ,到达执行时间被唤醒。

上述两个 native 方法,基于 Linux 的 I/O 多路复用 epoll 机制实现,原理待定。文章:

参考文章:

三、关键流程分析

1、生产者 - 消息入队

  • 流程:发送消息 -> 入队:send()/post() -> enqueueMessage
  • 无论是哪一种类型的消息,入队都是根据 msg.when 进行排队插入。消费时才会有内存屏障消息优先处理的逻辑。


2、消费者 - 消息出队

  • 流程:next() -> dispatchMessage() 处理消息

3、阻塞与唤醒

  • 流程:for 死循环 CPU 空转,资源利用率优化:无消息一直阻塞或未到执行时间,则阻塞直到有消息或执行时间到了,重新唤醒。

4、loop 死循环结束

  • 两种结束方式:安全结束,等待延时消息处理完再清空队列消息;非安全结束,直接清空队列所有消息。「注意:队列是否允许 quit,在创建消息队列时通过 mAllowedQuit 标记,主线程是为 false 不允许停止,当然如果 mAllowedQuit = true,然后调用 quit() 就会抛异常:Main thread not allowed to quit.
  • 对于已经停止的 loop,再次 enqueue 入队会抛 IllegalStateException 异常:${msg.target} sending message to a Handler on a dead thread;next() 取消息,发现若 mQuitting 了,就调用 dispose() 释放队列资源。

5、Message 资源回收和重复利用

消息回收时机:

  • 从队列中取出并且被 dispatch 处理了,回收方法:msg.recycleUnchedked();
  • 主动回收,系统源码中有些直接通过 recycle() 主动回收。

回收复用流程:

四、类设计理念

关键类图如下:

1、Looper 的工作原理

Looper 在 Android 的消息机制中扮演着消息循环的角色,具体来说就是一直不停地从 MessageQueue 中查看是否有新的消息,若有就立即取出处理,没有就会阻塞直到有新的消息。以下主要介绍的Looper 的 prepare()、构造和 loop() 方法。

1.1 Looper.prepare() 方法

源码如下:

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

首先会查看判断当前线程已经创建了 Looper ,如果已经创建了再次调用 prepare() 方法,就会抛异常,说明一个线程只会创建一次Looper;如果不存在,就创建一个 Looper 对象,并存放到 sThreadLocal 中。

Note: 简单介绍一下 sThreadLocal 这个 ThreadLocal 成员变量。

  • ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定的线程中可以获取到存储的数据,对于其他线程就无法获取到数据,也即 Looper 的作用域是以线程为作用域并且不同的线程拥有不同的 Looper,可通过 ThreadLocal 实现 Looper 在不同线程中的存取。
  • 这里要记住一个线程只对应一个 Looper对象,如果在主线程中创建,就属于主线程,在子线程中创建就属于子线程。
1.2 Looper构造方法

源码如下:

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

在构造方法会创建一个 MessageQueue,也即消息队列,然后将当前的线程保存起来,也即保存创建 Looper 的线程。

1.3 Looper.loop() 方法

主要代码如下:

public static void loop() {
    // 从 sThreadLocal 中取出 Looper 对象
    final Looper me = myLooper();
    // 为空,代码当前线程没有主动调用 prepare() 方法
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 取出 Looper对应的 MessageQueue消息队列
    final MessageQueue queue = me.mQueue;
    // 开始执行无限循环
    for (;;) {
        // 从队列中取出一个Message 消息,next()在队列中没有消息时会阻塞操作
        Message msg = queue.next(); // might block
        // 这里当取出的消息为空时,是跳出循环的唯一条件
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            // msg.targert为Handler对象,最初由 Handler.sendMessage()发送的消息,最终交由Handler 处理
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        msg.recycleUnchecked();
    }
}

Q:loop() 退出循环的唯一条件是从消息队列中取出的消息为 null,那么何时才会为 null 呢?
A:虽然 mQueue.next() 是一个阻塞方法,当队列中没有消息时,会一直阻塞,当主动调用 Looper.quit() 和 quitSafely(boolean) 方法时,就会调用 MessageQueue.quit() 方法,通知消息队列退出,当消息队列被标记为退出状态时,它的 next() 方法就会返回 null。
Q:msg.target.dispatchMessage() 实际上是交给 Handler 处理,那么怎样交由对应的线程来处理呢?这个问题等到 Handler 工作原理时来解答。

2、Handler 的工作原理

Handler 的主要工作包括消息的发送和接收。消息的发送可以通过 post 的一系列方法以及 send 的一系列方法,不过最终都是通过 send 来实现的。

2.1、Handler.sendMessage() 方法

我们经常会使用这样的方式发送一条消息:

Message msg = new Message();
msg.what = 0x11;
msg.obj = "Hello, world !";
handler.sendMessage(msg);

查看 sendMessage() 方法源码可知,所有的 send() 和 post() 最终都会调用 handler.enqueueMessage(mQueue, msg, updateMillis) 方法,继续查看该方法源码如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 设置 Message的 target成员变量为 Handler.this,将 Handler 和该 Message 绑定在一起
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 调用 MessageQueue 的进队方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

    从上可知,Handler 发送消息的过程仅仅是向消息队列中插入一条消息,MessageQueue的 next() 方法就会返回这条消息给 Looper,Looper 收到消息就开始处理,最终消息由 Looper 交由 Handler 处理,也即 <code>msg.target = handler.dispatchMessage() </code>方法被调用,这是消息就进入处理阶段。

2.2、Handler.dispatchMessage() 方法

消息处理方法dispatchMessage() 对应源码如下:

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

处理过程如下:
(1)首先,检查 Message 的 callback 是否为 null,不为 null,就调用 handleCallback(msg) 方法处理消息。

  • Message 的 callback 为 Runnable 对象,实际上是通过 Handler.post(Runnable) 传递而来的,这里会调用 <code>Message getPostMessage(Runnable r)</code> 方法来给 msg 的 callback 赋值;
  • handleCallback(msg) 方法 逻辑只是执行:<code>message.callback.run();</code>

(2)其次,检查 mCallback 接口是否为 null,不为 null,就回调 mCallback.handleMessage() 方法。

  • 这里提供接口回调方法,就可以不用派生一个子类重写 handleMessage() 方法,可以直接回调 mCallback 的 handleMessage() 方法。

(3)最后,调用 handle.handleMessage() 方法来处理消息,也即当派生了子类时需要重写该方法。

2.3、Handler 构造方法

    之前说过,在线程中使用 Handler 就必须先创建 Looper,通过查看 Handler 的构造方法可知,创建 Handler 时需要一个特定的 Looper 作为参数,如果没有传递 Looper 参数,就直接使用 Looper.myLooper() 方式获取。
    大家知道,Looper 是作用于线程中的,一个线程仅对应一个 Looper,所以由该 Looper 创建的 Handler 和 MessageQueue 必定也是和该线程一一对应的,这也就能用来说明msg.target.dispatchMessage() 方法在处理消息时,一定也是在对应线程中处理的,也即若是在主线程中创建,则 Handler 对应在主线程中处理消息,若在子线程中创建,就会在子线程中处理消息,此时需要注意在这里是不能进行UI更新操作。

public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
public Handler(Callback callback, boolean async) {
    // 因为是从 ThreadLocal 中获取,所以是从创建了 Looper 的线程中获取
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

3、MessageQueue 的工作原理

    消息队列 MessageQueue 主要包含两个操作:插入和读取。读取操作也即取出一条 Message消息,随后这条消息就会从消息队列中移除。
    需要注意得是消息队列内部实现并不是队列,而是通过一个 单链表来处理的(单链表插入和移除有优势)。

3.1、enqueueMessage(Message, long) 方法

    消息Message 创建后,由 Handler.sendMessage(),然后插入到 MessageQueue,主要源码如下:

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) {
        // 调用 Looper.quit()实际是调用 mQueue.quit()终止消息循环
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        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 {
            // 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;
}
3.2、Message next() 方法

    next() 方法是一个无限循环的方法<code>for(;;)</code>,会出现以下几种情况:
(1)当调用 Looper.quit() -> mQueue.quit() 停止消息循环,直接返回 null;
(2)若消息队列中没有消息,next() 方法就会一直被阻塞(如何判断队列中没有消息呢?根据闲置的Handler 个数来判断,当没有闲置的 Handler 时,就阻塞队列?);
(3)当有新消息时,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;
    }
}

4、Message 对象

    Message 也即消息实体,实现了Parcelable接口,所以是可用于进程间通信。
    首先来讲下如何创建 Message的,官方推荐使用 <code>Message.obtain()</code> 或者 <code>Handler.obtainMessage()</code> 方法来创建 Message 对象,而不建议使用构造方法创建,前者是如果回收池中存在,就直接从已回收的对象池中获取,不用重新分配内存,否则就重新创建,后者直接构造,每次都需要从堆中分配内存,具体实现可看 Message.obtain() 方法:

public static Message obtain() {
    synchronized (sPoolSync) {
        // 如果是第一条消息,就会直接创建
        if (sPool != null) { // sPool为静态Message对象
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

    sPool 是一个 静态Message 对象,它被赋值是在 Message.recycleUnchecked() 方法中,此方法就是回收一个不再 in-use 的 message,消息回收后,就会清除该 Message.this 的所有信息(成员变量值),并且当消息回收池中的消息的 size 没有超过最大数量 MAX_POOL_SIZE = 50 时,会将回收的该条消息保存在 sPool 中。
由此可知一条 Message 从创建到回收的过程如下:
(1)当使用 Message.obtain() 创建消息时,如果是该线程消息队列中的第一条 Message,就会直接 new Message();
(2)当消息被处理完毕被回收时,会将该条消息的引用保存到 sPool 中,sPoolSize 累计加 1;
(3)当创建第二条消息时,sPool != null,将 sPool 赋值给一个 Message 对象,这样 m就指向了之前回收的对象的内存地址,然后将 sPool 指向下一个被回收的对象,sPoolSize 减 1,,返回对象 m,如此便不用重新申请内存空间。

5、Android 消息处理机制总流程图

消息传递总流程图.png

五、总结

此次针对 Handle 消息处理机制重新做了整体的分析,有些知识重新了解了下,比如同步屏障、阻塞唤醒流程、IdleHandler 作用,当然 epoll 机制还没有去深入了解。基础的知识点可以汇总一下:

  • 异步消息处理整体就是基于「生产者-消费者模式」来设计的,知晓这个理念整体流程就清晰了;
  • MessageQueue 是一个单链表实现的消息队列,消息是通过 msg.when 进行排队插入;
  • 消息入队的方式决定其处理流程,send、post、posSyncBarrier 几种方式,前两者可并为一般消息(同步、异步),后一种则为同步屏障消息,遇到此类标签,则表明队列中该消息后的第一个异步消息要优先处理,不再遵循队列顺序处理;
  • 队列阻塞与唤醒:当队列为空时,一直阻塞,重新添加消息会被唤醒;当还没有到消息执行时间,则阻塞直到该消息可执行则被唤醒。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348

推荐阅读更多精彩内容