【总结】Android消息机制
Android消息机制主要是指Handler的运行机制,以及MessageQueue和Looper的工作过程。
概述
从开发的角度来说,Handler是Android消息机制的上层接口,开发中只需要关心Handler的交互即可。
很多人认为Handler的作用是更新UI,这个认为没有错但是比较片面,它只是Handler的一种使用场景。
Handler的主要作用是将一个任务切换到某一个指定的线程中去执行。
Handler的运行需要底层的MessageQueue和Looper的支撑。
MessageQueue叫做消息队列,是用链表来实现的一个存储消息的列表。
Looper是混唤起,会以无限循环的形式查找消息。线程默认没有Looper,需要手动为其创建,但是主线程即UI线程,会在创建的时候自动初始化一个Looper。
ThreadLocal是Looper中的一个特殊概念,它不是线程而是一种数据载体,它可以互不干扰的为每一个线程存储/提供数据。
由于Android的UI控件是线程不安全的,多线程并发访问会导致控件处于不可预计的状态,但若加线程锁,又会导致其访问逻辑变得复杂并且低效,得不偿失。所以Android在设计上,规定访问UI线程只能在主线程中进行,并且在ViewRootImpl接口中对UI操作做了验证。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(...);
}
}
并且提供了Handler机制,解决在子线程中访问UI线程的问题。
Handler的使用此处不表,这次的重点是Handler以及其底层类的运作原理。
MessageQueue
MessageQueue,即消息队列,内部通过一个单链表的数据结构来维护消息列表,使用单链表而非队列是因为单链表在插入以及删除操作上比较有优势。
消息队列主要有两个操作,插入和删除(读取)。
enqueueMessage是插入方法,向队列中插入一条消息
next是删除方法,从消息队列中取出一条消息,并将其从队列中删除。并且next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会被阻塞,直到有新消息出现。
ThreadLocal
介绍Looper之前必须先介绍ThreadLocal这个线程内部的数据存储类。
ThreadLocal也叫线程本地变量,它的特性是,它可以互不干扰的为每一个线程存储/提供数据,使用的时候会对线程自身的内存进行操作,并且提供了get/set方法来访问。简单来说ThreadLocal可以为不同的线程提供不同的数据副本。
白话有点抽象,举个例子:
private ThreadLocal<String> stringThreadLocal;
stringThreadLocal = new ThreadLocal<>();
stringThreadLocal.set("main");
Log.e(TAG, "run: " + stringThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
stringThreadLocal.set("Thread1");
Log.e(TAG, "run: " + stringThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: " + stringThreadLocal.get());
}
}).start();
12-26 16:25:57.208 19506-19506/com.zx.studyapp E/WindowActivity: run: main
12-26 16:25:57.209 19506-19800/com.zx.studyapp E/WindowActivity: run: Thread1
12-26 16:25:57.210 19506-19801/com.zx.studyapp E/WindowActivity: run: null
ThreadLocal底层也是用Map实现,可以近似的理解为,此Map的key是线程标识,value就是当前线程所对应的值。
ThreadLocal的这个特性,正好满足Handler中Looper的需求:Looper的作用域是当前线程,并且不同的线程拥有不同的Looper。
Looper
Looper 就是循环器,它会以无限循环的形式,向MessageQueue中查找新消息,有新消息就立即处理,否则就阻塞。
Handler工作的时候需要Looper循环器的参与,但是除了主线程,其他线程不会默认初始化一个Looper供我们使用,这时候就需要手动创建一个Looper。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//创建一个Looper
Handler handler = new Handler();
Looper.loop();//开启消息循环
}
}).start();
Looper的loop方法中有一个死循环,用来读取MessageQueue中的新消息。
Looper除了prepare方法之外,还提供了prepareMainLooper方法为主线程创建Looper使用,本质上还是prepare方法。
此外,Looper还提供了两个方法来退出消息循环,分别是quit和quitSafely,他们的区别是:
quit会直接退出Looper;
quitSafely会设定一个标记,当队列中已有的消息全部处理完成后,才会退出。
Looper退出后,Handler发送的消息就会失败,即send方法返回false。在子线程中,应该在所有任务处理完成后立即调用quit方法来退出消息循环,否则这个子线程就会持续处于等待状态,无法终止。
Handler
Handler主要工作是发送和接受消息。
Handler有一个特殊的构造方法,是通过特定的Looper来构造,这个方法会在默认构造方法中被调用,所以如果当前线程没有Looper的话,就会抛出无法创建Handler的异常。
Handler发送主要通过post或者send方法来实现(最终都是通过send实现)。Handler发送消息的过程,实质上就是向消息队列中插入一条数据。
Handler.send在子线程发送了一条消息,之后Looper的loop方法不再阻塞,通过MessageQueue.next获取了新消息,接着通过msg.targer.dispatchMessage方法,将其传递给Looper所在线程的Handlder,完成了一次消息传递。
如下图:
handler工作流程.png