持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
1.基本概念
很多时候我们将Handler仅用来更新UI,但是更新UI仅仅是Handler的一个场景。有时候需要在子线程中进行耗时的I/O操作,可能是读取文件也或者是网络操作,当耗时任务完成可能需要在UI上做出一些更新。
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue是一个消息队列,对外提供插入和删除的操作,虽然它是一个队列但是它的内部却是以单链表的数据结构来存储消息队列。Looper在消息机制中的功能是消息循环,MessageQueue是一个消息队列,它只能用来存储消息而不能处理消息,这个时候Looper的存在就恰好的解决了这个问题,Looper会以无限循环的方式查找消息队列中是否有新的消息,有新消息则处理,没有则等待。Looper中还有一个特殊的概念——ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。ThreadLocal可以在不同的线程中互不干扰的存储并提供消息,通过ThreadLocal可以获取每个线程中的Looper。需要注意的是线程是默认没有Looper的,如果需要使用Handler的时候就必须为线程创建一个Looper。经常提到的UI线程他就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
2.Android的消息机制概述
Handler的主要作用是将一个任务切换到指定的线程中去,因为Android规定UI只能在主线程中更新,如果在子线程中更新将会抛出异常。但是Android又建议不要在主线程中做一些耗时操作因为这样会导致ANR,而我们常常从服务器端请求数据然后拿到服务端返回的数据才能更新UI,这时候必须要在子线程中进行拉取工作,拉取完毕后又不能在子线程中直接访问UI,那么Handler在这个时候就起了很大的作用。
系统为什么又不允许在子线程中访问UI呢?因为Android的UI线程是不安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么系统为什么不对UI控件的访问加上锁机制呢?原因有两点:首先加上锁机制会让UI访问的逻辑变得复杂;其次会降低UI的访问效率,因为锁机制会阻塞某些线程的执行。
Android的消息机制分析
1.ThreadLocal的工作原理
ThreaLoacl是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在相爱昵称内部只要通过get方法就可以获取到监听器。如果不采用ThreadLocal,那么就会有以下两种方法:第一种是将监听器作为静态变量供线程访问;第二种是将监听器通过参数的形式在函数调用栈中进行传递。
ThreadLocal 的值在table数组中的存储位置总是为ThreadLocal的reference字段所表示的对象,比如ThreadLocal的reference对象在table数组中的索引为index,那么ThreadLocal的值在table数组中的索引就是index + 1。
2.消息队列的工作原理
消息队列在Android中指的是MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。虽然MessageQueue叫消息队列,但是它的内部实现并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表插入和删除上比较有优势。
3.Looper的工作原理
Looper在消息机制中扮演着消息循环的角色,不停地从MessageQueue 中查看是否有新消息,如果有则立刻处理,没有则一直阻塞组那里。
Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。由于主线程Looper比较特殊,所以Looper提供了一个getMainLooper方法 通过它可以在任何地方获取到主线程的Looper。Looper也是可以退出的,Looper提供了quit和quitSafely来退出一个Looper。二者的区别是:quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的消息处理完毕后才安全退出。
在子线程中,如果手动创建了Looper那么在所有的事情完成后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议在不需要的时候主动终止Looper
Looper的loop方法的工作过程:loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit方法或者quitSafely来通知消息队列退出,当消息队列被标记为退出状态时,它的next就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。``````
4.Handler的工作原理
Handler的工作主要包含发送和接收,消息的发送是通过post一系列和send一系列方法实现的,post一系列方法最终是通过send的一系列方法来实现的。Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
dispatchMessage的实现如下所示
public void dispatchMessage(Message msg){
if(msg.callback != null){
handleCallback(msg);
} else {
if(mCallback != null){
if(mCallback.handleMessage(msg)){
return;
}
}
handleMessgae(msg);
}
}
Handler 处理消息的过程是:
首先,检查Message的callback是否为null,不为null就通过hanleCallback来处理消息。Message的callback是一个Runnable对象,实际上就是Handle的post的方法所传递的Runnable参数。