Android 的消息机制主要是指 Handler 的运行机制,Handler 底层需要 MessageQueue 和 Looper 的支撑。
主线程,也叫 UI 线程,即 ActivityThread,主线程被创建时就会初始化 Looper。
Handler 主要作用是将一个任务切换到指定的线程去执行,主要是为了在主线程中更新 UI,解决子线程无法访问 UI 的问题。ViewRootImpl 对 UI 操作做了验证,是由 ViewRootImpl 的 checkThread 方法来完成的。
系统为什么不允许在子线程中访问 UI?
因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问有可能导致 UI 控件处于不可预期的状态。
为什么不对 UI 控件加上锁机制?
首先,加上锁机制会让 UI 访问的逻辑变得复杂;其次,锁机制会降低 UI 的访问效率,因为锁机制会阻塞某些线程的执行,基于这两个缺点,最简单高效的做法就是采用单线程来处理 UI 操作。
Handler 会采用当前线程的 Looper 来构建内部的消息循环系统,如果当前线程没有 Looper,那么就会报错。解决办法就是,为当前线程创建 Looper 即可,子线程的 Looper 不会自动结束,需要手动结束。
Handler 创建完毕后,其内部的 Looper 以及 MessageQueue 就可以和 Handler 一起工作了,然后通过 Handler 的 post/send 方法将一个 Runnable 投递到 Handler 的内部的 Looper 中处理,post 方法最终通过 send 方法完成,send 方法会调用 MessageQueue 的 enqueueMessage 方法将消息放入消息队列,然后 Looper 会发现有新消息,就会处理这个消息,然后这个消息中的 Runnable 或者 Handler 的 handleMessage 方法就会被调用。
ThreadLocal 的工作原理#
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只能在指定的线程中可以获取到存储的数据,对于其他线程无法获取数据。
当某些数据是以线程为作用域,且不同的线程具有不同的数据副本时,可以考虑使用 ThreadLocal。
不同的线程中可以访问同一个 ThreadLocal 对象,但是他们的值是不一样的。原因是,不同线程访问同一个 ThreadLocal 的 get 方法,ThreadLocal 内部会从各自的线程中取出一个数组,然后再从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。
ThreadLocal 的 set 方法如下 (Api 版本 28):
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
会先获取当前线程的 ThreadLocal.ThreadLocalMap 如果 map 为空就创建并且把 value 存入,不为空直接存入。
再看下 get 方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
通过 get 和 set 方法可以看出,他们所操作的都是当前线程的 threadLocals 的 Entry 数组,因此不同线程访问同一个 ThreadLocal 的 set ,get 方法的操作权限仅限于各自线程的内部。
消息队列的工作原理#
MessageQueue 主要包含两个操作,插入和读取,读取本身会带着删除操作,插入和读取对应的方法为 enqueueMessage 和 next。其中 enqueueMessage 往消息队列中插入消息,next 取消息并且从消息队列中移除。
MessageQueue 内部是通过单链表来维护消息列表,单链表在插入和删除上比较有优势。
next 方法是一个无线循环的方法,如果消息队列中没有消息,那么 next 方法就一直阻塞在这里,当有新消息到来时,next 方法会返回这条消息并将其从单链表中移除。
Looper 工作原理#
Looper 在 Android 的消息机制中扮演着消息循环的角色,它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就立刻处理,否则就会阻塞在那里。
构造方法如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
通过构造方法,会创建消息队列,并把当前线程保存起来。
通过 Looper.prepare () 可以为当前线程创建 Looper,通过 Looper.loop () 来开启消息循环。
Looper.prepare();
Handler handler = Handler()
Looper.loop();
Looper 可以退出,Looper 退出后,Handler 发消息会失败, send 方法会返回 false。子线程,创建了 looper,所有事情处理完成后,就应该调用 quit/quitSafely 方法来终止循环,否则子线程会一直处于等待状态,如果退出 Looper 后,这个线程就会立刻终止,因此建议不需要的时候终止 Looper。
Looper 的 loop 方法是个死循环,唯一跳出循环的方式是 MessageQueue.next 返回了 null。当 Looper quit 后,会通知消息队列退出,当消息队列标记为退出桩体,它的 next 方法就会返回 null。也就是说 Looper 必须退出,否则 loop 方法就会一致循环下去。next 是个阻塞操作,没有消息时会阻塞,导致 loop 一致阻塞,如果返回了消息,Looper 就会处理消息msg.target.dispatchMessage
, 这里的 msg.target 是发送消息的 handler 对象,这样就保证了不会把消息发送给其他的 handler,最终交给了 dispatchMessage 方法处理,dispatchMessage 方法是在创建 Handler 时所使用的 Looper 中执行的,这样就成功将代码逻辑切换到了指定的线程中执行。
Handler 工作原理#
Handler 主要工作包括消息的发送和接收过程,可以通过 post/send 的一系列方法来实现,post 最终是通过 send 系列方法来实现的。看代码一目了然:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler 发送消息的过程仅仅是向消息队列中插入了一条消息,会根据 delayMillis 来判断插入位置。然后 MessageQueue.next () 会放回这条消息给 looper,Looper 收到消息后就开始处理,最终交给 handler 处理。再看下处理代码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,检查 Message 的 callback 是否为空,不为空就通过 handleCallback 处理,message 的 callback 是个 Runnable 对象,实际上就是 post 方法所传递的 Runnable 参数。
其次,检查 mCallback 是否为空,不为空就调用 mCallback 的 handleMessage 方法处理消息,Callback 是个接口,定义如下:
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*/
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
Callback 可以通过如下方式创建:Handler handler = new Handler(callback)
。他的意义,注释已说明:可以用来创建一个 Handler 实例,但不需要派生出 Handler 子类。日常开发中,创建 Handler 常见的方式就是派生出 Handler 的子类并重写其 handleMessage 方法,而 Callback 给我们提供了另一种方式。
最后,调用 Handler 的 handlerMessage 方法来处理消息。
Handler 还有一个构造方法,可以指定 Looper,方便切换线程。
主线程的消息循环#
主线程是指 ActivitThread,严格来说它不是线程,但可以把它当做主线程。
ActivityThread 并没有真正继承 Thread 类,只是往往运行在主线程,给人以线程的感觉,其实承载 ActivityThread 的主线程就是由 Zygote fork 而创建的进程。
ActivitThread 入口方法为 main,在 main 中通过 Looper.prepareMainLooper();
创建主线程的 Looper 和 MessageQueue,并通过 Looper.loop () 开启主线程的消息循环。
ActivitThread 中的 Handler 为 ActivitThread.H,它内部定义了一组消息类型,主要包含了组件的启动和停止过程。
ActivitThread 通过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通信的方式完成 ActivitThread 的请求后会回调,ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息会将 ApplicationThread 的逻辑切换到 ActivitThread 中执行,即切换到主线程中执行。
总结#
Android 的消息机制很重要,要充分理解其工作机制,以及 Handler, MessageQueue, Message, Looper 是如何共同工作的。