在上一篇解析中,分析了Handler机制从sendMessage到handlerMessage的过程,在这个流程中有一个很重要的对象叫Looper,接下来分析一下looper,看看它在Handler机制中的那些事儿~
首先什么是Looper?
Looper是一个循环器,在handler中不停的从MessageQueue中取msg消息,然后发送给Handler进行处理。
第二个问题:Handler是如何获取Looper的?
上篇说到,在Handler的构造函数中会获取一个looper:


在上图中,可以看到handler里面的mLooper对象是通过Looper.myLooper()方法获取的,当获取的这个looper为空的时候会抛出一个异常
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
阅读一下这个异常提示:无法在当前线程中创建handler,因为他没有调用Looper.prepare()方法。这个异常通常情况下是在子线程中直接创建Handler才会发生,那么我们平时使用Handler的时候也没有调用过Looper.prepare()方法呀,怎么不会报这个错误呢?
再看下面这张图

Handler也提供了构造函数传参的方式获取looper。
总而言之,言而总之,要使用Handler,必须给Handler提供一个looper对象,可以通过传参的方式传入Handler,如果不通过传参,那么Handler就会自己去获取一个looper,当这个looper获取不到的时候,就会抛出异常。
在平时的开发中,很少是通过Handler的构造函数传入looper对象的,那么我们点进Looper.myLooper()方法,看Handler自己是怎么获取looper对象的:

首先了解一下ThreadLocal:

在ThreadLocal中有一个有个静态的内部类ThreadLocalMap,这里的ThreadLocalMap可以粗略的理解为一个map,通过(key,value)的形式保存数据。
ThreadLocal中还提供了两个方法,set()和get(),用于设置和获取ThreadLocalMap中的数据,先来看看set()方法:

getMap方法:

在getMap方法中可以看到返回的是Thread中的 threadLocals 属性


当Threadlocal去set的时候,发现线程内部的ThreadLocalMap为null,就会调用createMap方法,为线程内部的threadLocals赋值
总结一下ThreadLocal中的set方法: 当调用set方法的时候,会获取当前线程中的一个内部属性threadLocals,这个threadLocals可以看做是一个map。当这个threadLocals为空,实例化一个ThreadLocalMap给threadLocals,然后将需要保存的数据放入到线程的threadLocals中。当threadLocals不为空时,直接保存数据(这里的数据可以是任何类型,因为ThreadLocal是一个T泛型,当然也可以保存looper。)
再来看ThreadLocal中的get方法:

其实并不是这样的,通过前面的分析,我们可以知道ThreadLoacl对象的get和set方法只能取对应线程中的数据,比如说这样写:

为什么明明set了值在threadLocal中,取出来的s却为null呢?
因为ThreadLocal的get和set只针对同一个线程才能取到值,之前也有分析到,set和get方法操作的其实是线程内部的ThreadLocalMap,在示例代码中
threadLocal.set("主线程set");
这句代码是在UI线程中调用的,也就是说UI线程的ThreadLocalMap中是有值得,在取数据的时候,是在一个子线程中取的数据
new Thread(new Runnable() {
@Override
public void run() {
//子线程中取数据
String s = threadLocal.get();
}
}).start();
也就是说通过ThreadLocal.get()方法取到的数据是子线程中的ThreadLocalMap里面的值,当然就为null啦~
总结一下ThreadLocal:
只有在同一个线程中使用get和set方法才能存取到对应的值,比如说,在主线程中set,就一定要在主线程中get才能获取到值。
回到Handler的构造函数中来
//handler的构造函数中会调用
mLooper = Looper.myLooper();
//myLooper方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
我们平时在UI线程中使用Handler的时候,都是直接new Handler,并没有给Handler设置looper,但是使用的时候却不报错,那也就是说,UI线程一定在某一个时刻初始化了一个looper,那就从UI线程的入口开始找,这个UI线程到底是在哪里初始化looper的,我们进入到ActivityThread的main方法中
(android真正的入口是在ActivityThread的Main函数里面,感兴趣的同学可以自行了解~)

Looper.prepareMainLooper();
在6669行调用了
Looper.loop();
关于 Looper.loop()函数在上篇中已经分析过了,就是启动looper的循环,这里不再做阐述,主要来分析一下Looper.prepareMainLooper()方法:


可以看到在 Looper.prepareMainLooper() 方法中调用了 prepare(boolean quitAllowed) 方法,在prepare中通过sThreadLocalset了一个 new Looper(quitAllowed) ,也就是说在主线程初始化的时候,系统就已经为我们初始化好了一个looper对象,存在主线程的ThreadLocalMap当中,当我们在主线程中使用Handler的时候,Handler的构造方法会从主线程的ThreadLocalMap中获取looper对象,这个对象当然是存在的啦~
当我们在子线程中使用Handler的构造函数的时候,由于在子线程中,系统并没有为我们准备looper,所以会抛出没有looper的异常,只要我们在初始化Handler的时候手动给当前线程初始化一个looper就能在子线程中使用Handler了:

最后再来分析一下Looper.prepare()方法:
private static void prepare(boolean quitAllowed) {
// 如果线程中已经有一个looper对象了
if (sThreadLocal.get() != null) {
//抛出异常,保证每个线程中只有一个looper对象
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
在android中,每个线程都只能持有一个looper对象,否侧就会抛出一个异常。
关于Handler中Looper的分析就到这里啦~~~~
android Handler机制解析——sendMessage和handleMessage: juejin.cn/post/684490…