Android 中的消息机制浅析

Handler 是 Android 中引入的一种让开发者参与处理线程中消息循环的机制。每 个 Hanlder 都关联了一个线程,每个线程内部(ThreadLocal)都维护了一个消息队列 MessageQueue,这样 Handler 实际上也就关联了一个消息队列。可以通过 Handler 将 Message 和 Runnable 对象发送到该 Handler 所关联线程的MessageQueue 中,然后该消息队列一直在循环拿出一个 Message,对其进行处理,处理完之后拿出下一个Message,继续进行处理,周而复始。当创建一个 Handler 的时候,该 Handler 就绑定了 当前创建 Hanlder的线程。从这时起,该 Hanlder 就可以发送 Message 和 Runnable 对象到该 Handler 对应的消息队列中,当从MessageQueue 取出某个 Message 时,会让 Handler 对其进行处理。

Android 中消息机制主要为 Handler 的消息机制,其底层需要 MessageQueue(消息队列用单链表来实现) 和 Looper 的支持。MessageQueue 只负责存储消息,Looper 可以来处理消息。Looper 中的 ThreadLocal 可以在每个线程中存储数据。Handler 创建时候会采用当前线程的 Looper 来构造消息循环系统,这时候就是 ThreadLocal 来获取当前线程的 Looper 了。Handler 主要作用就是将一个任务切换到某个指定的线程中去执行。Handler 创建时候,会采用当前线程的 Looper 来构建消息循环系统。

ThreadLocal 的工作原理

ThreadLocal 是一个 线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储之后,只有在指定线程中才可以获取的存储的数据,对于其他线程则无法获取到数据。对于 Handler 来讲它需要获取 当前线程的 looper ,这时候 ThreadLocal 就是不二之选。从 ThreadLocal 的 set 和 get 方法可以看出 ,他们所操作的对象都是当前线程的 LocalValues 对象的 table 数组,因此在不同线程中访问同一个 ThreadLocal 的 set 和 get 方法,他们对 ThreadLoca 所做的操作仅限于各自线程的内部。对于 ThreadLocal 的一些详细操作和解释可以参见 《Android 开发艺术探索》 P379.

MessageQueue 的工作原理

消息队列在 Android 指的是 MessageQueue . MQ 中包含两个操作:插入和读取。读取操作本身会伴随着删除操作。enqueueMessage 对应着插入方法即往消息队列中插入一条消息。next 作用是从消息队列中取出一条消息并且将其从消息队列中移除。MQ 虽然称为消息队列但是其内部实现还是单链表,对插入和删除操作友好嘛。next 是一个无限循环的方法,如果消息队列中没有消息则其一直阻塞。

Looper 的工作原理

Looper 在消息循环机制中扮演着循环的角色。一直不停的查看 MQ 是否有消息。有消息就立刻处理。没有就一直阻塞。Looper 的构造方法会创建一个 MQ,然后将当前的线程对象保存起来:

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

为线程创建 looper:

new Thread("Thread2")
  public void run(){
  	Looper.prepare();// 为当前线程创建一个 Looper
  	Handler h = new Handler();
  	Looper.loop();// 开启
}}.start();

Looper 除了 prepare() 之外还提供了 prepareMainLooper 方法。其主要是主线程 ActivityThread 创建 Looper 使用的(因此在主线程中我们不用显示的去写 prepare() 方法)。本质也是通过 prepare() 方法来实现的。

而如果我们没有给 Looper 设置 prepare() 就会抛出异常(在子线程中)。为什么呢?我们就要问一下 prepare() 里面到底做了什么?看一下源码啦:

public static final void prepare() {  
    if (sThreadLocal.get() != null) {  
        throw new RuntimeException("Only one Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper());  
} 

原来是给每一个线程至多设置一个 Looper 对象啊!而且是通过前面的 ThreadLocal 来设置的!Looper 最重要的一个方法是 loop 方法,只有调用了 Loop 后,消息循环系统才会正真的起作用。

public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    while (true) {  
        Message msg = queue.next(); // might block  
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }  
            if (me.mLogging!= null) me.mLogging.println(  
                    ">>>>> Dispatching to " + msg.target + " "  
                    + msg.callback + ": " + msg.what  
                    );  
            msg.target.dispatchMessage(msg);  
            if (me.mLogging!= null) me.mLogging.println(  
                    "<<<<< Finished to    " + msg.target + " "  
                    + msg.callback);  
            msg.recycle();  
        }  
    }  
}  

如上即为 loop() 方法。loop() 方法的核心就是调用 MQ 的 next 方法,而 next 是一个阻塞操作,如果没有消息就一直阻塞在那里。一旦返回消息 msg.target.dispatchMessage(msg) 就会处理这条消息。其中 msg.target 就是发送这条消息的 Handler 对象。

Handler 的工作原理

问题:Handler 为什么会造成内存泄漏? 简单点将就是 message 持有 Handler 的引用。而 Handler 又潜在的持有外部类的引用。详细解释以及解决办法参见这里

在前面的实例中我们通过 handler 将 msg 发送出去。我们自然想知道 handler 将 msg 发送到哪里去了?为什么后续还可以在 handler 中去处理 msg 呢?

通过查看源码,我们发现所有 sendMessage() 方法最终会通过一个叫 sendMessageAtTime() 的方法将 msg 发送出去。发送的目的地又是哪里呢?进一步查看源码是 MessageQueue 中。其中插队和出队的操作分别交给了 enqueueMessage() 和 Looper.loop() 这两个方法。

它的简单逻辑就是如果当前 MQ 中存在 msg (即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。在 loop() 方法中,每当有一个消息出队,就将它传递到 msg.target 的 dispatchMessage() 方法中,那这里 msg.target 又是什么呢?其实就是 Handler 的对象。可以在 sendMessageAtTime( ) 方法中看到。但是此处不同的是 handler 的 dispatchMessage 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就成功的将逻辑代码切换到指定的线程中去了。

来看一下 Handler 中的 dispatchMessage() 以及 Handler 处理消息的过程。

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  // 对应着 Handler 的 post 用法
        handleCallback(msg);  
    } else {  // 对应着 handler 的 sendMessage 方法
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);  
    }  
}  

我们总结出 Handler 处理消息的过程: 首先检查 Message 的 callback 是否为 null,不是 null 的话就通过 handleCallback 来处理消息。Message 的 callback 是一个 Runnable 对象。实际上就是 Handler 的 post 方法所传递的Runnable 参数。该种写法可以参见 实例一。

其次,检查 mCallback 是否为 null,不为 null 则调用 mCallback 的 handleMessage() 方法,否则直接调用 Handler 的handleMessage() 方法,并将消息对象作为参数传递过去。Callback 是一个接口:

public interface Callback{
  public boolean handleMessage(Message msg);
}

该种写法可以参见实例二。

主线程的消息循环

Android 主线程就是 ActivityThread,入口方法为 main。在 main 中系统会通过 Looper.prepareMainLoper() 来创建主线程的 Looper 以及 MessageQueue,并通过 Looper.loop() 来开启主线程的消息循环。

主线程的消息循环开启之后,ActivityThread 还需要一个 Handler 来和消息队列进行交互,这个 Handler 就是 ActivityThread.H,它内部定义了一组消息类型包含了四大组件的启动和停止过程。

引申阅读

另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行 UI 操作:

  • View 的 post() 方法。
  • Activity 的 runOnUiThread() 方法。
  • 思考一下为什么建议在 viewHolder 前面添加 static 方法。

如果觉得本文对你有帮助,请打赏以回报我的劳动