最近在看 EventBus
源码的时候,发现有用到了 ThreadLocal
这个类。刚好遇到这次作业, 赶紧学习一波。本篇学习参考了《Android开发艺术探索》,故依据的是 Android 5.0 的源码。
1. 不得不说的 Handler
- Android 的消息机制主要是指
Handler
的运行机制,Handler
的运行需要底层的MessageQueue
和Looper
的支撑。 MessageQueue
是消息队列, 内部储存了一组消息,以队列的形式对外提供插入和删除的工作。然而,它并不会去处理消息。Looper
是消息循环,它会以无限循环的方式去查找是否有新消息,如果有的话就处理消息。
2. 一个异常
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new LooperThread().start();
}
class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
}
};
}
}
}
这段代码是会抛出异常的,异常信息是这样的:
E/AndroidRuntime: FATAL EXCEPTION: Thread-387
Process: com.wzc.chapter_4, PID: 7821
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:208)
at android.os.Handler.<init>(Handler.java:122)
at com.wzc.chapter_4.MainActivity$LooperThread$1.<init>(MainActivity.java:25)
at com.wzc.chapter_4.MainActivity$LooperThread.run(MainActivity.java:25)
查看 Handler
源码,
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
。如果 mLooper
对象为 null
, 那么就会抛出上面日志里面的运行时异常。那么,怎样才能有一个 Looper
对象呢?
看一下 Looper
的文档:
Looper
类用于给线程运行一个消息循环。线程默认并没有和它们关联的消息循环;去创建一个Looper
对象,就要在运行循环的线程里调用Looper.prepare()
方法,然后再调用Looper.loop()
就可以让Looper
对象一直处理消息直到循环结束。
消息循环大多是通过Handler
类关联的。
修改代码:
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new LooperThread().start();
}
class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
}
运行一下,果然解决了上面的异常。
3. 升级代码
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new LooperThread("Thread1").start();
new LooperThread("Thread2").start();
}
class LooperThread extends Thread {
public Handler mHandler;
public LooperThread(String threadName) {
super(threadName);
}
@Override
public void run() {
Looper.prepare();
Log.d(TAG, "run: currThread=" + Thread.currentThread().getName()
+ ", Looper.myLooper()="+Looper.myLooper());
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
}
运行一下,查看日志:
D/MainActivity: run: currThread=Thread1, Looper.myLooper()=Looper (Thread1, tid 405) {41c92f18}
D/MainActivity: run: currThread=Thread2, Looper.myLooper()=Looper (Thread2, tid 406) {41c96f38}
可以看到不同的线程里有不同的 Looper
对象。那么,多个线程是如何存储它们对应的 Looper 对象?
先看一下产生 Looper
对象的地方, Looper.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()
方法中, 会 new
一个 Looper
对象,并把 Looper
对象设置给 sThreadLocal
对象。
再看一下获取 Looper
对象的地方,Looper.myLooper()
:
public static Looper myLooper() {
return sThreadLocal.get();
}
可以看到是从 sThreadLocal
对象中获取 Looper
对象。
sThreadLocal
是什么呢?是定义在 Looper
类中的一个静态成员变量。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
需要注意的是,这是个静态的 ThreadLocal
变量。也就是说,我们在 Thread1 和 Thread2 这两个线程中访问的是同一个 ThreadLocal
对象,但是它们通过 ThreadLocal
对象 sThreadLocal
获取到的值却是不一样的。这个地方很有意思。
到这里可以知道,ThreadLocal
负责了存储 Looper
对象。然而,这只部分回答了上面的问题。下面进入 ThreadLocal
类中寻找答案。
4. ThreadLocal
public class ThreadLocal<T>
这是一个泛型类,在我们的分析中,T
就是 Looper
。再看一下 ThreadLocal
的文档:
实现了一个线程本地的存储,也就是说,为了使每一个线程拥有它自己的值提供一个变量。所有的线程共享同一个
ThreadLocal
对象,但是每个线程只能从ThreadLocal
对象获取自己存的值,并且一个线程对于存储值的改变不会对其它线程存储的值造成影响。支持存储 null 值。
从文档中看,ThreadLocal
类确实能够回答我们上面的问题。下面从源码中找到依据。不假思索地,我们应该从 ThreadLocal
类的 get()
和 set()
方法中入手。
首先看 ThreadLocal 的 set 方法:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
在这个方法里,先通过 values()
方法来获取当前线程的 ThreadLocal
数据。看一下 values()
方法:
Values values(Thread current) {
return current.localValues;
}
而 current.localValues
是 Thread
类中一个成员变量:ThreadLocal.Values localValues;
在这个方法里, ThreadLocal
通过包访问权限直接获取了当前线程的 ThreadLocal.Values localValues
成员变量。
再回到 set()
方法中,如果 values
为 null
,那么就会对其进行初始化,初始化的代码比较简单:
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
再调用 Values
的 put()
方法,将参数中的 value
进行存储。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
接着看 ThreadLocal
的 get()
方法:
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);
}
在这个方法里,先取出当前线程的 Values
对象。如果这个对象不为 null
,那么就找到对应的值并返回;如果这个对象为 null
,就返回初始值。
来自《Android开发艺术探索》的结论:
从
ThreadLocal
的set
和get
方法可以看出,它们所操作的对象都是当前线程的 localValues 对象的 table 数组,因此在不同线程中访问同一个ThreadLocal
的set
和get
方法,它们对ThreadLocal
所做的读/写操作仅限于各自线程的内部,这就是为什么ThreadLocal
可以在多个线程中互不干扰地存储和修改数据。
参考
- Android 开发艺术探索