提及Android的消息機(jī)制玫鸟,相信這是Android開(kāi)發(fā)者非常熟悉秒紧,并且相當(dāng)基礎(chǔ)的一個(gè)知識(shí)點(diǎn)了。那這里還有什么需要講的呢盆赤?這里蒿囤,先拋出一個(gè)問(wèn)題:
問(wèn)題
我們?cè)谑褂肏andler的時(shí)候客们,都知道其必須要跟一個(gè)Looper綁定的。而在UI線程可直接初始化Handler來(lái)使用蟋软,但是在子線程則不行镶摘,系統(tǒng)會(huì)拋出一個(gè)必須調(diào)用Looper.prepare()
的異常信息嗽桩。緣由在于岳守,當(dāng)初始化Handler
的時(shí)候,其會(huì)通過(guò)Looper
來(lái)獲取當(dāng)前的Looper碌冶,代碼如下:
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()");
}
//省略
}
那么湿痢,問(wèn)題來(lái)了,為什么在子線程中扑庞,通過(guò)Looper.myLooper()
方法獲取的就是為空呢譬重?如果有人回答了Looper是線程相綁定的,那它是如何做到綁定的? 如果還知道答案的話罐氨,那就可以跳過(guò)本篇文章了臀规。
代碼分析
1. Looper的myLooper方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
此方法只是通過(guò)從變量sThreadLocal
中取出一個(gè)值。那么它的值是哪里來(lái)的呢栅隐?
2. 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));
}
可以看出的是調(diào)用了這個(gè)方法之后塔嬉,會(huì)在sThreadLocal
中存在一個(gè)新建的Looper
對(duì)象玩徊。那么看看這個(gè)sThreadLocal
是什么東西呢?
3. sThreadLocal的定義
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
是一個(gè)靜態(tài)的ThreadLocal的變量谨究,并且泛型指向的是Looper
對(duì)象恩袱。另外,注釋中告訴了我們胶哲,如果我們不調(diào)用prepare
方法的話畔塔,那get
方法返回的是null。所以現(xiàn)在就引出咱們的重頭戲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.
這段話的意思是實(shí)現(xiàn)了一個(gè)線程相關(guān)的存儲(chǔ)澈吨,即每個(gè)線程都有自己獨(dú)立的變量。所有的線程都共享者這一個(gè)ThreadLocal
對(duì)象碾盟,
并且當(dāng)一個(gè)線程的值發(fā)生改變之后棚辽,不會(huì)影響其他的線程的值。
2. 實(shí)現(xiàn)
ThreadLocal的類定義使用了泛型ThreadLocal<T>
冰肴,其中T指代的是在線程中存取值的類型屈藐。(對(duì)應(yīng)Android中使用的ThreadLocal, T則存放的類型為L(zhǎng)ooper)
- 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
方法如下:
Values values(Thread current) {
return current.localValues;
}
方法中,先通過(guò)Thread.currentThread
來(lái)拿到當(dāng)前線程熙尉,再拿到線程的values屬性联逻,并對(duì)此values屬性進(jìn)行賦值,其中key為當(dāng)前的ThreadLocal
對(duì)象检痰,value則是當(dāng)前要存放的值包归。而這個(gè)values對(duì)象,其中維持了一個(gè)一維的object數(shù)組铅歼,采用偶數(shù)為key, (索引為index)奇數(shù)為value(索引為index + 1)的數(shù)據(jù)結(jié)構(gòu)公壤。
- 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);
}
在進(jìn)行取值的時(shí)候,也是現(xiàn)獲取當(dāng)前線程椎椰,然后根據(jù)當(dāng)前ThreadLocal的hash值與values的mask標(biāo)志位進(jìn)行與操作厦幅,來(lái)獲取到當(dāng)前ThreadLocal在這個(gè)線程的values中的位置,并通過(guò)判斷其存放的key是不是當(dāng)前ThreadLocal慨飘,若是的話确憨,則返回index+1對(duì)應(yīng)的值,即是我們所存放的值瓤的;若不是的話休弃,則需要通過(guò)values的getAfterMiss方法來(lái)進(jìn)行更進(jìn)一步詳細(xì)的搜索。
總結(jié)
ThreadLocal通過(guò)獲取當(dāng)前線程中的values屬性圈膏,從而實(shí)現(xiàn)了每個(gè)單獨(dú)線程的信息綁定塔猾。這樣的話,Android的消息機(jī)制中稽坤,Looper便是采用ThreadLocal作為存儲(chǔ)結(jié)構(gòu)丈甸,所以looper對(duì)象的存儲(chǔ)只會(huì)在當(dāng)前線程中医增,子線程若是使用消息機(jī)制的話,必須調(diào)用Looper.prepare方法來(lái)在線程中新建一個(gè)Looper的對(duì)象老虫。