Handler源码解析——子线程建立Handler为什么报错?

前言

Android 提供了Handler和Looper来来知足线程间的通讯,而前面咱们所说的IPC指的是进程间的通讯。这是两个彻底不一样的概念。java

Handler先进先出原则,Looper类用来管理特定线程内消息的交换(MessageExchange);android

一、为何会有Handler机制?

咱们刚说Handler机制的主要做用是将某一任务切换到特定的线程来执行,咱们作项目可能都遇到过ANR(Application Not Response),这就是由于执行某项任务的时间太长而致使程序没法响应。这种状况咱们就须要将这项耗时较长的任务移到子线程来执行,从而消除ANR。而咱们都知道Android规定访问UI只能在主线程中进行,若是在子线程中访问UI,那么程序就会抛出异常。而Android提供Handler就是为了解决在子线程中没法访问UI的矛盾。数组

二、Handler源码解析

子线程中建立Handler为啥会报错?

首先,咱们先看一个例子,咱们在子线程中建立一个Handler。app

new Thread(new Runnable() {
      @Override
      public void run() {
        new Handler(){
          @Override
          public void handleMessage (Message message){
            super.handleMessage(message);
          }
        };
      }
    },"MyThread").start();

咱们运行时会发现,会抛出异常:Can't create handler inside thread that has not called Looper.prepare()async

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:204)
        at android.os.Handler.<init>(Handler.java:118)
        at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1$1.<init>(MainActivity.java:21)
        at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1.run(MainActivity.java:21)
        at java.lang.Thread.run(Thread.java:764)

到底是为何会抛出异常呢?下面咱们经过Handler源码来看看。ide

Handler的构造方法

当咱们建立Handler对象的时候调用的是下面的方法:函数

/**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
    public Handler() {
        this(null, false);
    }

咱们看到注释中说:++默认的构造函数将这个Handler与当前的线程的Looper关联,若是当前线程没有Looper,那么这个程序将没法接收消息,所以会抛出异常。++ 到底是怎么抛出异常的呢?咱们继续往下看:oop

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();//注释1
        if (mLooper == null) {
            throw new RuntimeException(//注释2
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

咱们看到在这里开始有个if语句,因为FIND_POTENTIAL_LEAKS默认值是false因此咱们不须要去管它,注释1处,这里调用了Looper.myLooper(),咱们看看它的源码:ui

Looper.myLooper()

/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这个方法的做用就是返回与当前线程相关连的Looper对象。这里又调用了ThreadLocal.get(),this

ThreadLocal

ThreadLocal是一个线程内部的数据存储类,经过它能够在指定的线程中存储数据,数据存储之后只有在特定的线程中能够获取到存储的数据,对于其余线程来讲则没法获取到。ThreadLocal用一句大白话来说解,++就是看上去只new了一份,但在每一个不一样的线程中却能够拥有不一样数据副本的神奇类。++ 其本质是ThreadLocal中的Values类维护了一个Object[],而每一个Thread类中有一个ThreadLocal.Values成员,当调用ThreadLocal的set方法时,实际上是根据必定规则把这个线程中对应的ThreadLocal值塞进了Values的Object[]数组中的某个index里。这个index老是为ThreadLocal的reference字段所标识的对象的下一个位置。
下面咱们来看它的get方法。

ThreadLocal.get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

在该方法的第二行调用了getMap方法。就是去获取当前线程的ThreadLocalMap对象。可是这个对象是在何时建立的呢?

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

由于抛出异常了因此咱们猜想这个值多是null。说到这里可能有些迷茫,咱们回头看看注释1处那里紧接着,咱们看到若是获取到的myLooper为空(至于为何会返回为空咱们看完后面回过头来看就很明白了)的的话,就会抛出咱们前面看到的异常。

......
        mLooper = Looper.myLooper();//注释1
        if (mLooper == null) {
            throw new RuntimeException(//注释2
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        ......

异常中说若是在子线程中建立Handler必需要调用Looper.prepare()方法,那么咱们想确定是在调用该方法的时候作了一些操做,可能跟后面消息的接收和处理相关,经过后面的源码咱们会发现其实这个方法是对当前线程建立一个Looper对象,咱们来看源码:

Looper.prepare()

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

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

咱们调用是没有传参的prepare,他会调用内部的传参的prepare方法。咱们看到注释3处又调用了ThreadLocal.get(),假设咱们第一次调用Looper.prepare(),那么这个值确定是空的。若是不是空的话前面Looper.myLooper()就不会为空,也就不会抛出异常了。(那么当在一个线程中第二次调用该方法的时候,他的返回值就不会是空,系统会抛出异常一个线程只能建立一个Looper。也就是说一个子线程中Looper.prepare()只能调用一次。)因此这里确定走ThreadLocal.set()方法,而且新建了一个Looper对象做为入参:

ThreadLocal.set()

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

咱们看到第二行仍是调用了getMap因为第一次调用,因此这个返回值仍是空的。因此应该走了createMap(),下面咱们看看它的源码:

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

咱们看到这里才对当前线的ThreadLocal进行了赋值。这个方法中咱们看到ThreadLocal已Map的结构存储了当前线程对应的Looper。以线程为Entry也就是Key,以Looper为Value。咱们回过来再看,Looper.myLooper()。

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//注释4
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

其实发现他就是去获取当前线程的Looper。当没有调用Looper.prepare()来建立Looper时,当前线程的ThreadLocalMap对象为空,因此前面的ThreadLocal.get()方法会调用setInitialValue这个方法,

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

咱们看到这个方法返回了value,value是经过一个方法返回的,下面咱们看看这个方法:

protected T initialValue() {
        return null;
    }

这个方法单纯的就是返回null,因此也就是至关于ThreadLocal.get()返回null===>Looper.myLooper()返回null。因此Handler会在注释2处抛出异常。

总结

回过头来,咱们仔细想一想为何会抛出异常来?就是由于:
Handler对象是基于Looper的,每一个Handler必须有一个Looper,这个Looper是在调用Looper.prepare()的时候建立的,这个Looper会以Map的形式存储在当前线程的ThreadLocal中。当当掉用Looper.myLooper()方法就是去在当前线程的ThreadLocal中拿到当前线程的Looper对象。

相关文章
相关标签/搜索