android Handler机制解析——Looper和ThreadLoacl

837 阅读5分钟

在上一篇解析中,分析了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对象的:

在myLooper()方法调用了sThreadLocal.get()获取一个looper并将其返回,那再来看看 sThreadLocal是如何获取到looper对象的

首先了解一下ThreadLocal:

在ThreadLocal中有一个有个静态的内部类ThreadLocalMap,这里的ThreadLocalMap可以粗略的理解为一个map,通过(key,value)的形式保存数据。

ThreadLocal中还提供了两个方法,set()和get(),用于设置和获取ThreadLocalMap中的数据,先来看看set()方法:

getMap方法:

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

在每一个线程中都有一个线程内部的ThreadLocalMap保存数据,线程中的这个ThreadLocalMap默认是为空的

当Threadlocal去set的时候,发现线程内部的ThreadLocalMap为null,就会调用createMap方法,为线程内部的threadLocals赋值

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

再来看ThreadLocal中的get方法:

看到这里,原来Handler内部是这样获取到looper对象的,嗦嘎嗦嘎。嗯?不对啊,在整个流程中,我们发现Handler只是在构造函数中通过Looper.myLooper()去get了一个looper对象,但是。。。。这个looper对象是在哪里set的呢?前面分析了一大堆set,结果set没调?

其实并不是这样的,通过前面的分析,我们可以知道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函数里面,感兴趣的同学可以自行了解~)

在ActivityThread的main函数中可以看到在6642行调用了

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…