转载请注明链接:https://blog.csdn.net/feather_wch/article/details/79263855
详解Handler消息机制,包括Handler、MessageQueue、Looper和LocalThread。
本文是我一点点归纳总结的干货,但是难免有疏忽和遗漏,希望不吝赐教。
Handler消息机制详解(29题)
版本:2018/9/7-1(2359)
问题汇总
以问答形式将本文的所有内容进行汇总。考考你哟。
- Handler是什么?
- 消息机制是什么?
- MeesageQueue是什么?
- Looper是什么?
- ThreadLocal是什么?
- 为什么不能在子线程中访问UI?
- Handler的运行机制概述(Handler的创建、Looper的创建、MessageQueue的创建、以及消息的接收和处理)
- 主线程向子线程发送消息的方法?
- ThreadLocal的作用?
- ThreadLocal的应用场景
- ThreadLocal的使用
- ThreadLocal的set()源码分析
- ThreadLocal的get()源码分析
- MessageQueue的主要操作
- MessageQueue的插入和读取源码分析
- MessageQueue的next源码详解
- Looper的构造中做了什么?
- 子线程中如何创建Looper?
- 主线程ActivityThread中的Looper是如何创建和获取的?
- Looper如何退出?
- quit和quitSafely的区别
- Looper的loop()源码中的4个工作?
- Android如何保证一个线程最多只能有一个Looper?
- Looper的构造器为什么是private的?
- Handler消息机制中,一个looper是如何区分多个Handler的?
- 主线程ActivityThread的消息循环
- ActivityThread中Handler H是做什么的?
- Handler的post/send()逻辑流程
- Handler的postDelayed逻辑流程
- Handler的消息处理的流程?
- Handler的特殊构造方法中,为什么有Callback接口?
- Handler的内存泄漏如何避免?
消息机制概述(8)
1、Handler是什么?
- Android消息机制的上层接口(从开发角度)
- 能轻松将任务切换到Handler所在的线程中去执行
- 主要目的在于解决在子线程中无法访问UI的矛盾
2、消息机制?
Android的消息机制
主要就是指Handler的运行机制
Handler
的运行需要底层MessageQueue
和Looper
的支撑
3、MeesageQueue是什么?
- 消息队列
- 内部存储结构并不是真正的队列,而是单链表的数据结构来存储消息列表
- 只能存储消息,而不能处理
4、Looper是什么?
- 消息循环
Looper
以无限循环的形式去查找是否有新消息,有就处理消息,没有就一直等待着。
5、ThreadLocal是什么?
Looper
中一种特殊的概念ThreadLocal
并不是线程,作用是可以在每个线程中互不干扰的存储数据
和提供数据
。Handler
创建时会采用当前线程的Looper
来构造消息循环系统,Handler
内部就是通过ThreadLocal
来获取当前线程的Looper
的- 线程默认是没有
Looper
的,如果需要使用Handler
就必须为线程创建Looper
UI线程
就是ActivityThread
,被创建时会初始化Looper
,因此UI线程
中默认是可以使用Handler
的
6、为什么不能在子线程中访问UI?
ViewRootImpl会对UI操作进行验证,禁止在子线程中访问UI:
void checkThread(){
if(mThread != Thread.currentThread()){
throw new CalledFromWrongThreadException("Only th original thread that created a view hierarchy can touch its views");
}
}
7、Handler的运行机制概述(Handler的创建、Looper的创建、MessageQueue的创建、以及消息的接收和处理)
- Handler创建时会采用当前线程的Looper
- 如果当前线程没有
Looper
就会报错,要么创建Looper
,要么在有Looper
的线程中创建Handler
Handler
的post
方法会将一个Runnable
投递到Handler
内部的Looper
中处理(本质也是通过send
方法完成)Handler
的send
方法被调用时,会调用MessageQueue
的enqueueMessage
方法将消息放入消息队列, 然后Looper
发现有新消息到来时,就会处理这个消息,最终消息中的Runnable
或者Handler
的handleMessage
就会被调用- 因为
Looper
是运行在创建Handler
所在的线程中的,所以通过Handler
执行的业务逻辑就会被切换到Looper
所在的线程中执行。
8、主线程向子线程发送消息的方法?
- 通过在主线程调用子线程中Handler的post方法,完成消息的投递。
- 通过
HandlerThread
实现该需求。
ThreadLocal(4)
1、ThreadLocal的作用
ThreadLocal
是线程内部的数据存储类,可以在指定线程中存储数据,之后只有在指定线程中才开业读取到存储的数据- 应用场景1:某些数据是以线程为作用域,并且不同线程具有不同的数据副本的时候。
ThreadLocal
可以轻松实现Looper
在线程中的存取。- 应用场景2:在复杂逻辑下的对象传递,通过
ThreadLocal
可以让对象成为线程内的全局对象,线程内部通过get
就可以获取。
2、ThreadLocal的使用
mBooleanThreadLocal.set(true);
Log.d("ThreadLocal", "[Thread#main]" + mBooleanThreadLocal.get());
new Thread("Thread#1"){
@Override
public void run(){
mBooleanThreadLocal.set(false);
Log.d("ThreadLocal", "[Thread#1]" + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run(){
Log.d("ThreadLocal", "[Thread#2]" + mBooleanThreadLocal.get());
}
}.start();
- 最终
main
中输出true
;Thread#1
中输出false
;Thread#2
中输出null
ThreadLocal
内部会从各自线程中取出数组,再根据当前ThreadLocal
的索引去查找出对应的value
值。
3、ThreadLocal的set()源码分析
//ThreadLocal.java
public void set(T value) {
//1. 获取当前线程
Thread t = Thread.currentThread();
//2. 获取当前线程对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//3. map存在就进行存储
map.set(this, value);
else
//4. 不存在就创建map并且存储
createMap(t, value);
}
//ThreadLocal.java内部类: ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
//1. table为Entry数组
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//2. 根据当前ThreadLocal获取到Hash key,并以此从table中查询出Entry
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();