Android基础回顾(2)——从源码角度理解Handler机制


我们知道在子线程中直接创建 Handler 会报 Can’t create handler inside thread that has not called Looper.prepare() 的错,而主线程是正常的,了解 Handler 原理的同学都知道这是因为 Handler 所在的线程必须有 Looper 实例,主线程系统已经在 ActivityThread中的main()方法中已经创建了,而我们自定义的子线程并没有创建,所以在子线程中创建 Handler 前就必须要调用 Looper.prepare() 创建属于子线程的 Looper 对象。

下面这样,在子线程中创建 Handler 不会报错。

new Thread(new Runnable() {  
    @Override  
    public void run() {  
        Looper.prepare();  
        Handler handler = new Handler();  
    }  
}).start(); 

虽然解决了问题,但是我们也要知道原理,要了解原理看源码肯定是最直接的方式了。

由于此错误是在创建 Handler 时抛出的,所以我们先从 Handler 的构造方法入手。

public Handler(Callback callback, boolean async) {    
    ...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    ...
}

在 Handler 的构造方法调用了 Looper 的 myLooper 方法获取 Looper 对象,然后判断是否为空,为空就抛出文章一开始提到的异常信息,说明 Handler 的存在的前提条件是必须有 Looper。什么情况下 mLooper 为 null 呢?这就要看看 Looper.myLooper() 源码了。

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

myLooper 方法很简单,就是从 sThreadLocal 中获取 Looper。看来 sThreadLocal 中保存的是当前线程中的 Looper 对象。我们再看看这个 sThreadLocal 对象到底是什么。

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

sThreadLocal 是 Looper 类的一个成员变量,类型为 ThreadLocal,ThreadLocal 保证了 Looper 的作用域是当前线程,别的线程不能访问,所以就算程序一开始就在主线程创建了 Looper 对象,该对象保存在 ThreadLocal 中,子线程也是访问不了的,导致 myLooper() 方法获取到的 Looper 为空,最后抛出了运行时异常。关于 ThreadLocal 详细解析见另一篇文章:Android基础回顾(3)——ThreadLocal原理。注释说的很清楚,没有调用 prepare() 方法前,sThreadLocal.get() 都会返回 null。那我们看看 prepare() 方法都干了哪些事。

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

prepare 方法里面,首先还是使用 sThreadLocal.get() 获取当前线程的 Looper 对象,如果存在,就抛出运行时异常,说明线程中值允许存在一个 Looper 对象, 也就是prepare 方法不能被调用两次;如果对象不存在,new 一个 Looper 对象保存到 sThreadLocal 中。

到这里,Handler 和 Looper 也已经成功创建了,接下来看看它们是如何一起做到线程间通讯的。

未完待续

评论