Android Handler 源码分析

175 阅读4分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

概述

平时我们执行耗时的操作,比如网络请求,IO操作等,需要在子线程中运行,不然会阻塞主线程。 而执行完网络请求等耗时操作后通常需要更新UI,如果在子线程中更新UI,那么程序会崩溃。

因为Android的UI是线程不安全的。

解决的方案是只需把更新UI的操作切换到主线程即可,这时就轮到Handler出场了,相信大家都对Handler的用法很熟悉了。当我们在子线程向服务端拉取数据后,主线程是不知道的,这时Handler在子线程发送一个消息到主线程告诉主线程:我已经请求数据完毕,现在你要更新UI了。然后handlerMessage方法接收到消息即可处理数据更新UI。

这一切都是那么的自然。

Android中的消息机制,由四个角色承担。分别是Handler,Looper(消息循环),MessageQueue(消息队列),Thread。

看到下图这四个角色的关联,先有一个大概的认识

image.png

那接下来就从源码的角度一起来学习消息机制吧

先从handler对象的创建开始,接着再分析handler怎么发送消息。

我们通常在主线程中创建handler,看看他的构造方法。

public Handler() { 
    this(null, false); 
}

可以看到这个无参数的构造方法,内部使用this关键字调用含有两个参数的构造方法。那就找到两个参数的构造方法,源码如下:

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看到Looper调用myLooper方法获取到Looper对象, 如果mLooper == null的话,会抛出

Can't create handler inside thread that has not called Looper.prepare()

的异常。大概的意思就是无法在没有调用Looper.prepare()的线程中创建handler。 我在刚开始学习Handler的时候经常会遇到这个错误。不急,等下在分析到底为什么,现在我们只需要知道如果Looper.myLooper()没有获取到Looper对象的话就会报这个错。 到了这里,Handler和Looper就建立起了关联。接着往下看完最后几行代码

mQueue = mLooper.mQueue;

从Looper对象中取出MessageQueue对象并赋值。MessageQueue就是消息队列,那么他里面存储着很多消息吗? 到了这一步,Handler通过Looper与MessageQueue也建立起了关联。

我们跟踪Looper的myLooper方法进去,解决为什么会抛出Can’t create handler inside thread that has not called Looper.prepare()异常。

myLooper方法源码如下:

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

只有一行代码,从线程中取出Looper对象,那么我们有理由相信,这个ThreadLocal是通过set方法把Looper对象设置进去的。

想一想ThreadLocal在哪里把Looper对象设置进去了呢。回到刚才想要解决的问题:Can’t create handler inside thread that has not called Looper.prepare() 。那会不会是Looper的prepare方法呢?

public static void prepare() {
    prepare(true);
}

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));
}

找到了线索,ThreadLocal确实是在Looper的prepare方法里把Looper对象设置进去的,而且从第一行的判断可以知道,一个线程只有一个Looper对象。 到了这里,Looper与ThreadLocal建立起了关联。可以看下Looper的构造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

创建了一个MessageQueue对象。

好,结合我们的分析可以知道,如果Looper没有调用prepare方法,ThreadLocal的get方法就会返回空,那么Looper.myLooper()也会返回空,所以就抛出了

Can't create handler inside thread that has not called Looper.prepare()的异常。

那么问题又来了,我们写程序时好像没有手动调用Looper.prepare()吧,也不会抛出异常。前面提到,我们通常都是在主线程,也就是UI线程中创建handler的。而在主线程中,系统已经为我们创建了一个Looper对象,所以不会抛出异常了。。。而那些会抛出异常报错的情况,是在子线程中创建的handler,但是又没有调用Looper.prepare()去创建Looper对象。 继续前进。那就来看看,主线程在什么时候创建了Looper对象吧。