主要内容
Handler 的消息处理机制。
主要是关于 MessageQueue、Message、Looper、Handler 之间的关系。
Android 消息驱动机制的四要素
- 接收消息的消息队列–>MessageQueue
- 阻塞式的从消息队列中接受消息并进行处理的线程–>Thead+Looper
- 可发送的” 消息的格式”–>Message
- 消息发送函数–>Handler 的 post 和 sendMessage
MessageQueue 虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是单链表的数据结构。它只能存储消息,处理消息就用到了 Looper。
Looper 内部有一个无限循环,去查找有没有新消息,有就去处理,没有就一直等待。
Looper 中有一个 ThreadLocal,他不是线程,它的作用是可以在每个线程中存储数据。在 Handler 内部获取当前线程的 Looper,就是通过 ThreadLocal 来获取的。因为 ThreadLocal 可以在不用的线程中互不干扰的存储和提供数据,通过 ThreadLocal 可以获取每个线程的 Looper。
线程是默认没有 Looper 的,要想使用 Handler 就必须为线程创建 Looper。如果是 UI 线程,也就是 ActivityThread,在创建时就会初始化 Looper,所以我们可以在主线程中直接使用 Handler。
Handler 主要实现是将一个任务切换到指定线程。Android 中只允许 UI 线程更改 UI。在 ViewRootImpl 有一个 checkThread 方法对 UI 操作进行了验证。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
那么为什么不允许子线程访问 UI 呢?
Android 中的 UI 线程不是线程安全的。如果在多线程中访问可能会导致 UI 控件处于不可预知的状态。当然我们可以使用锁机制来,但是锁机制有两个问题:1、加上锁机制会让 UI 访问的逻辑变得复杂;2、锁机制会降低 UI 访问的效率。简单高效的方法就是采用单线程模型处理 UI。
——《Android 群英传》
使用 Handler 必须保证线程中有 Looper,否则就会报错。我们可以在线程中创建 Looper,也可以在有 Looper 的线程中创建 Handler。
Handler 通过 post 或 send 方法发送消息,然后交由 Looper 处理。同时,会调用 MessageQueue 的 enqueueMessage 方法将消息放到消息队列中,Looper 发现新消息,就会处理消息,最终消息中的 Runnable 或者 Handler 的 handleMessage 方法就会被调用。
ThreadLocal
ThreadLocal 是什么
是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中才可以获取,其他线程无法获取。
什么情况下使用 ThreadLocal
1、当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以使用
2、复杂逻辑下的对象传递,比如监听器的时候,有的时候函数调用过深,可以采用 ThreadLocal,它可以让监听器作为线程内的全局对象,在线程中内部只要通过 get 方法就可以获取监听器。
如果不采用 ThreadLocal,有两种方法,一种是将监听器以参数的形式进行传递,这种会让程序看起来很糟糕;一种是将监听器作为静态变量供线程访问,但这样不不可扩充的,n 个线程就要有 n 个静态变量。使用 ThreadLocal 每个线程都能获取自己的监听器对象。
ThreadLocal 实例
class MainActivity : AppCompatActivity() {
private val TAG = "tag"
private var mBooleanThreadLocal = ThreadLocal<Boolean>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mBooleanThreadLocal.set(true)
Log.d(TAG,"UI ============" + mBooleanThreadLocal.get())
object : Thread("Thread#1") {
override fun run() {
mBooleanThreadLocal.set(false)
Log.d(TAG,"Thread#1 ================== " + mBooleanThreadLocal.get())
}
}.start()
object : Thread("Thread#2") {
override fun run() {
Log.d(TAG,"Thread#2 ================== " + mBooleanThreadLocal.get())
}
}.start()
}
}
打印结果
07-05 14:26:55.992 6979-6979/? D/tag: UI ============true
07-05 14:26:55.992 6979-6999/? D/tag: Thread#1 ================== false
07-05 14:26:55.992 6979-7000/? D/tag: Thread#2 ================== null
可见不同线程访问的虽然是同一个 ThreadLocal 对象,到那时他们获取的结果是不一样的。不同线程访问同一个 ThreadLocal 的 get 方法,它内部会从各自的线程中取出一个数组,然后再从数据中根据当前 ThreadLocal 的索引去查找对应的 value 值。
ThreadLocal.set
在来看一下它的存储,也就是 put 方法。
ThreadLocal.get
reference 是什么
/** Weak reference to this thread local instance.
* 对此线程本地实例的弱引用
*/
private final Reference<ThreadLocal<T>> reference
= new WeakReference<ThreadLocal<T>>(this);
ThreadLocal 总结
从以上可以看出,他们操作的对象都是的当前线程的 localValues 对象的 table 数组,因为在不同线程访问同一个 Threadlocal 的 set 和 get 方法,它们对 ThreadLocal 所做的读写操作仅限于各自线程的内部。
注意:这是 API21 的源码,API25 采取了不同的实现,暂时没明白。
Looper
Looper 是连接 Handler 消息处理机制的关键,也可以说它是一个动力。Handler 将 Message 发到 MessageQueue 中,Looper 是一个死循环,从 MessageQueue 中不断的取消息,然后分发给 Handler(handleMessage)。
除了 UI 线程,普通的线程(HandlerThread 除外)是不带 Looper 的。如果想实现 UI 线程和子线程通信,需要在子线程实现一个 Looper。
- 判断是否已经有 Looper,调用 Looper.prepare()
- 做一些准备工作,比如暴露 Handler
- 调用 Looper.loop(),线程进入阻塞态
Looper.loop()
从代码中可以看出,loop() 中有一个死循环,不断的从 MessageQueue 中取消息,并且通过 dispatchMessage 方法分发。当没有消息时,next 方法就会阻塞在那里。
Looper.prepare()
从中可以看出,一个线程只能绑定一个 Looper 对象,如果再次绑定就会报错。
sThreadLocal.get() 返回的是 Looper 对象,由此可见 prepare() 方法只能调一次
构造函数
Looper 的退出
两种方法 quit 和 quitSafely
quit 直接退出,quitSafely 设定一个退出标记,把消息队列中的消息处理完之后才会退出。退出之后,Handler 发送的消息会失败,Handler 的 send 方法会返回 false。
Handler
从上面看一看到消息被分发到 dispatchMessage 方法中,那么看一下 Handler 的 dispatchMessage 方法。
Handler 也可以通过 Callback 来创建 Handler handler = new Handler(callback),当我们不想派生子类的时候就可以用这个。
post
这里看一下往消息队列中发送消息是怎么实现的
这里可以看到无论是 post 还是 sendMessage 都是调的 sendMessageDelayed 方法
看一下这个方法
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
最终调用的是 sendMessageAtTime 方法
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);
}
MessageQueue
MessageQueue 虽然称为消息队列,但是内部是通过单链表的结构来维护消息列表。
单链表的结构大致如下
它的在插入和删除上比较有优势。但是因为每个节点都有指针,空间利用率低,而且不支持随机读取数据。
从中可以大致了解 Handler 的消息机制。
参考
https://blog.youkuaiyun.com/iispring/article/details/47180325#t3
https://hit-alibaba.github.io/interview/Android/basic/Android-handler-thread-looper.html
《Android 开发艺术探索》