alighters

程序、写作、人生

ThreadLocal在Android消息机制中的作用

| Comments

提及Android的消息机制,相信这是Android开发者非常熟悉,并且相当基础的一个知识点了。那这里还有什么需要讲的呢?这里,先抛出一个问题:

问题

我们在使用Handler的时候,都知道其必须要跟一个Looper绑定的。而在UI线程可直接初始化Handler来使用,但是在子线程则不行,系统会抛出一个必须调用Looper.prepare()的异常信息。缘由在于,当初始化Handler的时候,其会通过Looper来获取当前的Looper,代码如下:

1
2
3
4
5
6
7
8
9
10
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()");
  }
  //省略
}

那么,问题来了,为什么在子线程中,通过Looper.myLooper()方法获取的就是为空呢?如果有人回答了Looper是线程相绑定的,那它是如何做到绑定的? 如果还知道答案的话,那就可以跳过本篇文章了。

代码分析

1. Looper的myLooper方法

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

此方法只是通过从变量sThreadLocal中取出一个值。那么它的值是哪里来的呢?

2. Looper的prepare方法

1
2
3
4
5
6
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));
}

可以看出的是调用了这个方法之后,会在sThreadLocal中存在一个新建的Looper对象。那么看看这个sThreadLocal是什么东西呢?

3. sThreadLocal的定义

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

是一个静态的ThreadLocal的变量,并且泛型指向的是Looper对象。另外,注释中告诉了我们,如果我们不调用prepare方法的话,那get方法返回的是null。所以现在就引出咱们的重头戏ThreadLocal了。

ThreadLocal

1. 定义

先看一下官方的解释:

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code 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 {@code null} values.

这段话的意思是实现了一个线程相关的存储,即每个线程都有自己独立的变量。所有的线程都共享者这一个ThreadLocal对象, 并且当一个线程的值发生改变之后,不会影响其他的线程的值。

2. 实现

ThreadLocal的类定义使用了泛型ThreadLocal<T>,其中T指代的是在线程中存取值的类型。(对应Android中使用的ThreadLocal, T则存放的类型为Looper) + set方法

1
2
3
4
5
6
7
8
public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

其中的values方法如下:

1
2
3
Values values(Thread current) {
  return current.localValues;
}

方法中,先通过Thread.currentThread来拿到当前线程,再拿到线程的values属性,并对此values属性进行赋值,其中key为当前的ThreadLocal对象,value则是当前要存放的值。而这个values对象,其中维持了一个一维的object数组,采用偶数为key, (索引为index)奇数为value(索引为index + 1)的数据结构。 + get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

在进行取值的时候,也是现获取当前线程,然后根据当前ThreadLocal的hash值与values的mask标志位进行与操作,来获取到当前ThreadLocal在这个线程的values中的位置,并通过判断其存放的key是不是当前ThreadLocal,若是的话,则返回index+1对应的值,即是我们所存放的值;若不是的话,则需要通过values的getAfterMiss方法来进行更进一步详细的搜索。

总结

ThreadLocal通过获取当前线程中的values属性,从而实现了每个单独线程的信息绑定。这样的话,Android的消息机制中,Looper便是采用ThreadLocal作为存储结构,所以looper对象的存储只会在当前线程中,子线程若是使用消息机制的话,必须调用Looper.prepare方法来在线程中新建一个Looper的对象。

版权归作者所有,转载请注明原文链接:/blog/2016/06/25/threadlocal-in-android-message/

给 Ta 个打赏吧...

Comments