从开发的角度来说,Handler
是Android
消息机制的上层接口,这使得在开发过程中只需要和Handler
交互即可。Handler
的使用过程很简单,通过它可以轻松地将一个任务切换到Handler
所在的线程中去执行。
有时候需要在子线程中进行耗时的IO
操作,可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在UI
上做一些改变,由于Android
开发规范的限制,我们并不能在子线程中访问UI
控件,否则就会触发程序异常,这个时候通过Handler
就可以将更新UI
的操作切换到主线程中执行。
Android
的消息机制主要是指Handler
的运行机制,Handler
的运行需要底层的MessageQueue
和Looper
的支撑。MessageQueue
的中文翻译是消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表
。
Looper
的中文翻译为循环,在这里可以理解为消息循环。由于MessageQueue
只是一个消息的存储单元,它不能去处理消息,而Looper
就填补了这个功能,Looper
会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。Looper
中还有一个特殊的概念,那就是ThreadLocal
,ThreadLocal
并不是线程,它的作用是可以在每个线程中存储数据。我们知道,Handler
创建的时候会采用当前线程的Looper
来构造消息循环系统,那么Handler
内部如何获取到当前线程的Looper
呢?这就要使用ThreadLocal
了,ThreadLocal
可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal
可以轻松获取每个线程的Looper
。当然需要注意的是,线程是默认没有Looper
的,如果需要使用Handler
就必须为线程创建Looper
,我们经常提到的主线程,也叫UI
线程,它就是ActivityThread
,ActivityThread
被创建时就会初始化Looper
,这也是在主线程中默认可以使用Handler
的原因。
10.1 Android的消息机制概述
Android
的消息机制主要是指Handler
的运行机制以及Handler
所附带的MessageQueue
和Looper
的工作过程,这三者实际上是一个整体,只不过我们在开发过程中比较多地接触到Handler
而已。Handler
的主要作用是将一个任务切换到某个指定的线程中去执行。
Android
规定访问UI
只能在主线程中进行,如果在子线程中访问UI
那么程序就会抛出异常。ViewRootImpl
对UI
操作做了验证,这个验证工作是由ViewRootImpl
的checkThread
方法来完成的。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
由于这一点的限制,导致必须在主线程中访问UI
,但是Android
又建议不要在主线程中进行耗时操作,否则会导致程序无法响应即ANR
。系统之所以提供Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾。
- 系统为什么不允许在子线程中访问UI呢?
这是因为Android
的UI
控件不是线程安全的,如果在多线程中并发访问可能会导致UI
控件处于不可预期的状态。
- 为什么系统不为UI控件的访问加上锁机制呢?
缺点有两个:
- 首先加上锁机制会让
UI
访问的逻辑变得复杂;- 其次锁机制会降低
UI
访问的效率,因为锁机制会阻塞某些线程的执行。
鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI
操作,对于开发者来说也不是很麻烦,只是需要通过Handler
切换一下UI
访问的执行线程即可。
这里描述一下Handler
的工作原理,Handler
创建时会采用当前线程的Looper
来构建内部的消息循环系统,如果当前线程没有Looper
,那么就会报错。
解决报错信息,只需要为当前线程创建Looper
即可,或者在一个有Looper
的线程中创建Handler
也行。
Handler
创建完毕后,这个时候其内部的Looper
以及MessageQueue
就可以和Handler
一起协同工作了,然后通过Handler
的post
方法将一个Runnable
投递到Handler
内部的Looper
中去处理,也可以通过Handler
的send
方法发送一个消息,这个消息同样会在Looper
中去处理。其实post
方法最终也是通过send
方法来完成的。
send
方法的工作过程,当Handler
的send
方法被调用时,它会调用MessageQueue
的enqueueMessage
方法将这个消息放入消息队列中,然后Looper
发现有新消息到来时,就会处理这个消息,最终消息中的Runnable
或者Handler
的handleMessage
方法就会被调用。注意Looper
是运行在创建Handler
所在的线程中的,这样一来,Handler
中的业务逻辑就被切换到创建Handler
所在的线程中去执行了。