记录一下Android开发艺术探索中第10章 Android的消息机制的笔记
概述
- Android的消息机制主要是指Handler的运行机制,从开发的角度来说,Handler是Android消息机制的上层接口。
- Handler的作用主要是用来更新UI:有时候在子线程中进行耗时的I/O操作,可能是读取文件或者访问网络(多数情况是请求网络数据),耗时操作完成时候会需要在UI上进行一些改变,但是我们并不能在子线程中更改UI,所以这个时候可以通过Handler将更行UI的操作切换到主线程中进行。
- Handler的运行需要底层的MessageQueue和Looper的支撑。
- MessageQueue内部存储了一组消息,以队列的形式对外提供插入和删除的操作,但是其内部的存储结构采用的是单链表的数据结构。MessageQueue只是用来存储消息的。
- Looper以无限循环的方式去查看MessageQueue中是否有消息,如果有的话就处理消息,没有的话就一只处于等待的状态。
- Looper中的特殊概念:ThreadLocal,ThreadLocal并不是线程,它用来获取每个线程中存储的数据。Handler创建的时候会采用当前线程的Looper来构造循环系统,而获取当前线程的Looper就需要用到ThreadLocal。
- ThreadLocal可以在不同线程中互不干扰的存储并提供数据,通过ThreadLocal可以获取每个线程的Looper。注意:线程默认是没有Looper的,如果需要使用Handler就需要手动为线程创建Looper。
- 主线程也叫UI线程,他就是ActivityThread,ActivityThread被创建的时候就会默认初始化Looper。
- Android系统之所以提供Handler是为了解决在子线程中无法访问UI的矛盾。
- 系统为什么不允许在子线程中访问UI?这是因为Android的UI控件是线程不安全的,因此如果多线程并发访问可能导致UI控件处于不可预期的状态。
ThreadLocal
- ThreadLocal是一个线程内部的数据存储类,可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据。
- 当某些数据是以线程为作用域并且不同线程需要获取不同数据副本的时候,就可以考虑采用ThreadLocal。对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程,并且不同线程具有不同的Looper。这样通过ThreadLocal就可以轻松的实现Looper在线程中的存取。
ThreadLocal演示
首先定义一个ThreadLocal对象,选择Boolean类型的,如下所示:
|
|
然后分别在主线程、子线程1、子线程2中设置和访问它的值,如下所示:
|
|
在主线程中将mThreadLocal的值设为true,子线程1中设置为false,子线程2中不设置,也就是null。安装程序,运行代码,Log如下所示:
|
|
由此可以看出,虽然在不同的线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal获取到的值确实不一样的。这是因为不同的线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前的ThreadLocal的索引去查找出对应的value值。不同的线程中的数组是不同的,因此ThreadLocal可以在不同线程中维护一套数据的副本并且彼此互不干扰。这里粗略介绍下ThreadLocal,详细内容可以看ThreadLocal解析
MessageQueue
- MessageQueue指的就是消息队列,其主要包含插入和读取两个操作,分别对应enqueueMessage和next方法。其中读取方法自身包含了删除操作。
- MessageQueue的内部实现是一个单链表,主要是因为单链表在插入和读取上有速度优势。
- 关于MessageQueue的详细分析可以看Android消息机制——MessageQueue解析
Looper
- Looper在消息机制中扮演着消息循环的角色,它会不停的从MessageQueue中查看是否有新消息,如果有就会立即处理,如果没有就会一只阻塞在那里。
- 构造方法 如下所示:
|
|
在构造方法中,Looper创建了一个MessageQueue对象,然后将当前线程对象保存起来。
- Handler工作需要Looper,如果没有Looper就会报错,通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环,代码如下所示:
|
|
- 除了prepare方法外,还有一个prepareMainLooper方法,这个方法主要用来给主线程,也就是ActivityThread创建Looper使用的,其本质也是通过prepare来实现的。
- 由于主线程的特殊性,Looper提供了一个getMainLooper的方法,通过它可以在任何地方获取到主线程的Looper。
- Looper可以退出,可以调用quit或者quitSafely来退出一个Looper,两者的区别是:quit会直接退出Looper,而quitSafely会设定一个退出标记,然后把消息队列中已经有的消息处理完之后才会安全退出。
- Looper退出后通过Handler发送的消息会失败,这个时候Handler的send方法会返回false。在子线程中如果手动为其创建了Looper,那么所有的事情做完之后应该手动调用quit方法来终止循环,否则这个字线程就会一只处于等待状态,而如果退出Looper之后,这个线程会立即终止。
下面看Looper的两个重要的方法
prepare()
Looper的prepare方法如下:
|
|
sThreadLocal是一个ThreadLocal对象,ThreadLocal上面有详细介绍。首先判断sThreadLocal是否为null,如果不为null则抛出异常,这就说明了prepare方法不能被调用两次,从而保证了一个线程中只有一个Looper。而如果sThreadLocal为null的时候,则将一个Looper的实例放入TheradLocal。
loop()
loop是Looper中最重要的一个方法,只有调用了loop之后消息训话系统才会真正起作用,代码如下:
|
|
首先通过ThreadLocal获取到sThreadLocal存储的Looper实例,如果me为null则直接抛出异常,也就是说loop方法必须在prepare方法之后运行。
可以看到loop方法是一个死循环,跳出循环的唯一方法就是msg == null,也就是queue.next() == null。由于MessageQueue的next方法是一个阻塞操作,当没有新消息时会一只阻塞在那里,这也导致loop一直阻塞在那里。当MessageQueue的next方法返回了新的消息,则Looper就会去处理这条消息:
|
|
其中,msg.target就是发送这条消息的Handler对象。这样,Handler发送的消息又交给了它的dispatchMessage来处理。最终释放消息占据的资源。
所以Looper的主要作用就是:
- 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
- loop()方法,不断从MessageQueue中去取消息,交给消息的target属性(也就是Handler)的dispatchMessage去处理。
Handler
一般使用Handler的时候都是先初始化一个实例,首先来看Handler的构造方法:
|
|
构造函数中通过Looper.myLooper()获取到了当前线程保存到Looper实例,然后又获取到了mLooper中的MessageQueue,这样就使得Handler的实例与Looper中的MessageQueue关联上了。
Handler主要包括了消息的发送和接收过程,消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post最终也是通过send来实现的。我们看一下最常用的sendMessage方法:
|
|
再看sendMessageDelayed方法:
|
|
再看sendMessageAtTime方法:
|
|
最终调用了sendMessageAtTime方法,可以看到,方法内部直接获取了MessageQueue,然后调用enqueueMessage方法,方法如下:
|
|
该方法首先将msg.target赋值为this,即Handler的实例,在之前分析的loop方法中会取出每个msg然后交给msg.target.dispatchMessage(msg)去处理消息。最终enqueueMessage方法会调用queue的enqueueMessage方法,该方法的作用是在MessageQueue中插入一条新的消息。有关enqueueMessage方法的分析,可以参考Android消息机制——MessageQueue解析
之前说过Handler的工作需要Looper,那么Looper会调用prepare和loop方法。在当前执行的线程中保存着一个Looper实例,它会获取到保存到一个MessageQueue对象,然后进入一个死循环中,不断的从MessageQueue中读取Handler发来的消息,然后再调用Handler中的dispatchMessage方法,该方法如下:
|
|
可以看到 ,该方法最终调用了handleMessage方法,也就是我们在Handler中重写的handlerMessage,根据msg.what来进行消息的实际处理。
消息流程总结
以上就是整个消息流程,我们总结一下:
- 首先通过ThreadLocal(Looper.prepare()方法)在本线程中保存一个Looper实例,然后该实例中又保存有一个MessageQueue对象。Looper.prepare()在每个线程中职能调用一次,保证了每个线程中只有一个MessageQueue。
- Looper.loop()会让当前线程进入一个无限循环,loop 方法又会调用MessageQueue的next方法,也就是不断的从MessageQueue中读取消息,然后回调msg.target.dispatchMessage() 方法(msg.target就是Handler的实例)。
- Handler的构造方法会获取当前线程保存的Looper实例,进而与Looper实例中的MessageQueue相关联。
- Handler的sendMessage方法会将msg的target赋值为handler自身(msg.target = this),然后将msg插入MessageQueue中。
- 在构造Handler的时候,我们会重写handlerMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法来处理我们自己的逻辑。