【Android】消息机制
首先要清楚的是,Android 的 UI 线程(也就是主线程)不能执行长耗时操作,否则会造成 ANR 程序无法响应异常,在子线程也无法直接更新 UI 元素,所以要多线程机制的参与,Android 提供了消息机制,其上层接口就是 Handler。
消息机制的 Handler 体系主要包括 Handler、Looper 和 MessageQueue,Handler 是消息的生产者和消费者,是消息的提交和处理入口;Looper 是消息的循环者,负责从 MessageQueue 取出 Message 消息,调用消息目标的 Handler 去处理;MessageQueue 则是消息队列,负责消息的自然排序存储。
1 个线程至多持有 1 个 Looper,每个 Looper 实例持有 1 个 MessageQueue,所以 Thread、Looper 和 MessageQueue 是对应的映射关系,不同的 Message 应该有不同的处理逻辑,所以 MessageQueue 允许注册多个 Handler。
Android 的消息机制概述
普通线程 Thread 初始化后不持有 Looper 实例,要通过调用 Looper.prepare 方法来获得 Looper 实例,并调用 Looper.loop 方法使 Looper 进入轮询循环,不断从 MessageQueue 获取信息。
new Thread(v -> {Looper.prepare();Looper.loop();
}).start();
Handler 的构造方法有 3 种,分别是
- 空参构造,内部会使用 Looper.myLooper 方法来获取当前线程的 Looper 并绑定
- Handler(Looper looper),明确指定消息的执行线程
- 带 Callback 构造,可以自定义消息处理逻辑,不用重写 handlerMessage 方法
所以使用原始 Thread 的消息队列构造应该是
Handler handler;
new Thread(v -> {Looper.prepare();handler = new Handler() {@Overrideboolean handleMessage(Message msg) {// 消息处理逻辑}}Looper.loop();
}).start();
这种初始化方法的缺陷是外部 handler 的生命周期依赖于匿名线程,handler 在 Looper.prepare 前是空指针,更优雅的方式是使用 HandlerThread
HandlerThread handlerThread = new HandlerThread("handlerThread"); // 线程名称
handlerThread.start(); // 启动 HandlerThreadHandler handler = new Handler(handlerThread.getLooper()) {@Overridepublic void handleMessage(Message msg) {// 消息处理逻辑}
};
初始化消息循环后,可以调用 handler 实例的 sendMessage 或 post 来提交任务,前者提交 Message 实例,执行并返回结果;后者直接提交 Runnable 直接执行逻辑,不关心消息标识。提交任务相当于投递到 MessageQueue,由 Looper 线程循环处理,调度执行提交 Message 的 Handler 的 dispatchMessage 方法,最后可能由 Runnable 执行,或根据构造 Handler 时提供的 Callback 执行,如果二者都为 null,则执行 handlerMessage 逻辑。Handler 消息机制默认是 Callback 的,也就是不支持返回值,可以通过回调接口或回复 Message 来获取执行结果。
持有关系上,Thread 持有 Looper 实例,Looper 持有外部 Thread 引用和 MessageQueue 实例,Message 实体类持有 int 类的消息类型句柄 what、Object 类的数据 obj、发送自身的 Handler 实例和可指定的处理方式 Callback,Handler 持有 Looper 和 MessageQueue 的引用。
Android 的消息机制分析
ThreadLocal 的工作原理
ThreadLocal 是线程内部的数据存储类,只有在指定线程中才可以获取到存储的数据,类似 MINECRAFT 的末影箱,当某些数据是以线程为作用域且不同线程具有不同的数据副本的时候,就可以考虑采用 ThreadLocal。
public void set(T value) {Thread currenThread = Thread.currentThread();Values values = values(currentThread);if (values == null) {values = initializeValues(currentThread);}values.put(this, value);
}
Thread 类内部有专门存储 ThreadLocal 数据的 ThreadLocal.Values localValues,在 localValues 内部有 Object 类型的数组 table,ThreadLocal 的值就存在 table 数组中。在现代 Android 中,存储 ThreadLocal 数据的字段变成 ThreadLocalMap threadLocals,本质是映射表,key 是 ThreadLocal 对象本身的弱引用,value 是该线程存储的具体对象,所以代码块中最后有向 Map 加键值对的 put 方法,覆盖具体对象 value 变成新值。因为 set 方法局限在独立线程内,所以 Thread 存储的 ThreadLocal 数据往往只存储 1 个键值对,也就是当前 ThreadLocal 弱引用和该线程的存储数据。
ThreadLocal 在消息机制中起到什么作用,让我们看 Looper 的 prepare 简化方法流程,其中的 sThreadLocal 就是 ThreadLocal 实例,Looper 内部使用它来保存线程对应的 Looper,创建 Looper 后将其绑定到当前线程的 ThreadLocal,Looper.myLooper 方法也是通过访问 ThreadLocal 来获得当前线程的 Looper。
private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();public static void prepare() {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper per thread");}sThreadLocal.set(new Looper());
}
消息队列的工作原理
消息队列即 MessageQueue,内部实现是单链表,首先我们要理解消息队列的特性,消息按时间顺序发送和处理,也就是 FIFO 先进先出,消息随时可能被插入或删除,因此消息队列的长度是动态变化的。
Android 的 MessageQueue 没有单独维护尾指针,所以插入普通消息要遍历至链表尾插入,时间复杂度 O(n),插入延时消息 MessageQueue 会从链表头指针开始按时间比较和遍历,找到延时消息的插入位置,取消息直接取链表头节点即可,所以是常数时间复杂度。
Handler 的工作原理
在概述部分我们知道,提交的消息事务会经过 Handler 的 dispatchMessage 方法,Message 消息本身如果通过 post 方法提交会带有 Runnable callback 执行体,也就是代码块的 handlerCallback 方法,Handler 会优先处理 post 消息,其次 Handler 会处理构造方法中若提供的 Handler.Callback mCallback 的 handleMessage 方法,这种方式避免重写 Handler 本身的 handleMessage 方法,方便复用,最后才会执行 Handler 本身的 handleMessage 方法。
值得注意的是,mCallback 的 handleMessage 方法会返回 boolean 来表示是否已经完全处理消息,类似 View 的事件分发机制,如果返回 false 则 dispatchMessage 方法会继续将消息传播到 Handler 本身的 handleMessage 来执行。
public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
private static void handleCallback(Message message) {message.callback.run();
}
主线程的消息循环
Android 的主线程就是 ActivityThread,主线程的入口方法是 main,该方法内系统会通过 Looper.prepareMainLooper 来创建主线程的 Looper 和消息队列,并通过 Looper.loop 开启消息循环。用户点击等事件和我们在 Activity 类代码中设定的任务都会作为消息事务进入主线程的消息队列,再由 Looper 不断分发交付处理。
public static void main(String[] args) {...Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}AsyncTask.init();if (false) {Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));}Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}
