【Android学习笔记】初探Thread、Looper和handler之间的联系

前几天写程序的时候碰到这么一个问题,当时想在子线程中Toast一段话,不废话上代码

        new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Toast.makeText(MainActivity.this,"Loading......",Toast.LENGTH_SHORT).show();
                    }
                };
            }
        }).start();

因为之前在主线程中使用Handler的时候,初始化也没有给他传入过什么属性,就直接用了,然后报错

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

当时我就纳闷了,这Looper先前倒是听说过,但是从没研究过,去网上查了点资料后对代码进行了修改

        new Thread(new Runnable() {
            @Override
            public void run() {
                new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Looper.prepare();
                        Toast.makeText(MainActivity.this,"Loading......",Toast.LENGTH_SHORT).show();
                        Looper.loop();
                    }
                };
            }
        }).start();
在添加了
Looper.prepare()和Looper.loop()

之后,就能够运行了。

出于程序员刨根问底的职业好奇心驱使,我查看了一下源码,了解了其中的运作道理。

1.Thread类中的ThreadLocal和ThreadLocalMap

ThreadLocal是线程中的局部变量,是每个线程单独持有的。以下是官方描述:

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.

大致意思是,ThreadLocal实现了线程本地存储。所有线程共享同一个ThreadLocal对象,但不同线程仅能访问与其线程相关联的值,一个线程修改ThreadLocal对象对其他线程没有影响。

如下图所示,我们可以将ThreadLocal理解为一块存储区,将这一大块存储区分割为多块小的存储区,每一个线程拥有一块属于自己的存储区,那么对自己的存储区操作就不会影响其他线程。对于ThreadLocal,则每一小块存储区中就保存了与特定线程关联的Looper。 

【Android学习笔记】初探Thread、Looper和handler之间的联系_第1张图片
也就是Thread通过ThreadLocalkey来取得自己的Looper对象。

2.为什么在子线程中Handler不能直接使用

首先我们来看看Looper的作用:

/**
 * 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.
 */

在Looper的源码中有这么一段描述,大概的意思就是如果一个线程没有Looper对象的话,那么在这个线程中运行的Hanlder就不能从消息队列中取得消息。这个是网上找的Handler运行机制图

【Android学习笔记】初探Thread、Looper和handler之间的联系_第2张图片

可以看出,Handler通过Looper从消息队列(消息队列是Looper内置的)中将Message取出来。那么Handler是如何获得Looper对象的呢?

我们来看一下Handler的构造方法

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

无参构造回调了另一个构造方法

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()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

可以看到其中有一句mLooper =Looper.myLooper(),我们看看它的具体实现

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

然后看看get()方法的实现

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//this是ThreadLocal对象
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;//value即为Looper对象
            return result;
        }
    }
    return setInitialValue();
}

可以看到get()方法会先获取当前线程,然后获取它的ThreadLocalMap,然后用ThreadLocal作为Key,取得它对应的Looper对象。

而子线程中一开始是没有Looper对象的,而要通过Looper.prepare()将其变成Looper线程,我们来看一下源码:

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

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

然后看一下get()方法的实现:

public T get() {
    Thread t = Thread.currentThread();//获取当前操作的线程
    ThreadLocalMap map = getMap(t);//获取Thread中的ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//通过key取得键值对的Entry对象,key为ThreadLocal,value是线程对应的Looper
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;//value就是Looper对象
            return result;//返回Looper后抛出异常
        }
    }
    return setInitialValue();
}

然后我们看一下setInitialValue()的实现:

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);//插入map,此时value为null
    else
        createMap(t, value);//map不存在就创建
    return value;//此时返回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));
    }

因为返回了null,所以执行sThreadLocal.set(newLooper(quitAllowed)):

public void set(T value) {//从传入参数也可以看出,插入的值为Looper
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

至此,由于当前线程调用了Looper.prepare(),它就获取了一个Looper对象,并存储在了它的threadLocals中(就是上面提到的那个map)


3.为什么在主线程中使用Handler不用调用Looper.prepare()

我们在Looper的源码中仔细找找可以发现有这么一个方法:
/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

根据英文说明可以看出,这个方法会由Android系统调用,为主线程自动准备一个Looper对象,而上面也说到,Handler在创建的时候,会去获取当前线程的Looper对象,所以在主线程中不需要使用Looper.prepare()方法来获得Looper对象。


4.总结

根据我自己的理解,Thread、Handler和Looper三者的关系是Thread中的ThreadLocal用于存储和它相关的许多对象,其中包含了它私有的Looper对象,Handler为了能在子线程中接收消息(或修改UI),就必须获得当前线程的Looper。

你可能感兴趣的:(【Android学习笔记】初探Thread、Looper和handler之间的联系)