- 《Android Handler機(jī)制》
- 《Android Handler分析(一) Handler和Message詳解》
- 《Android Handler分析 (二) MessageQueue詳解》
- 《Android Handler分析 (三) Looper詳解和Handler其他知識》
- 《Android 中的 HandlerThread 類詳解》
在前面的博客中我們介紹了Handler相關(guān)的幾個類(Handler、Message和MessageQueue),在這一篇博客中,我們介紹Handler機(jī)制中最后一個重要的類,Looper類。并介紹2個小知識點。
再說Looper之前,我們先來看一個相關(guān)的類:ThreadLocal類
ThreadLocal類
ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù),數(shù)據(jù)存儲以后,只有在指定線程中可以獲取到存儲的數(shù)據(jù),對于其它線程來說無法獲取到數(shù)據(jù)。一般來說,當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候,就可以考慮采用ThreadLocal。比如對于Handler來說,它需要獲取當(dāng)前線程的Looper,很顯然Looper的作用域就是線程并且不同線程具有不同的Looper,這個時候通過ThreadLocal就可以輕松實現(xiàn)Looper在線程中的存取。
ThreadLocal類的幾個特點:
- 每個線程都有自己的局部變量
每個線程都有一個獨立于其他線程的上下文來保存這個變量,一個線程的本地變量對其他線程是不可見的 - 獨立于變量的初始化副本
ThreadLocal可以給一個初始值,而每個線程都會獲得這個初始化值的一個副本,這樣才能保證不同的線程都有一份相同的拷貝 - 狀態(tài)與某一個線程相關(guān)聯(lián)
ThreadLocal 是為了方便每個線程處理自己的狀態(tài)而引入的一個機(jī)制
另外,ThreadLocal有一個內(nèi)部類ThreadLocalMap,這個類就是真正保存線程自己本地變量的容器。每一個線程都有自己的單獨的一個ThreadLocalMap實例,其所有的本地變量都會保存到這一個map中。
簡單示例:
// 創(chuàng)建一個ThreadLocal對象
private static ThreadLocal<Integer> integerThreadLocal = new InheritableThreadLocal<Integer>(){
// 重寫方法,指定一個初始值
@Override
protected Integer initialValue() {
return 0;
}
};
// 測試不同多個線程同時訪問
// 定義5個線程
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
// 創(chuàng)建多個線程
threads[i] = new Thread(){
@Override
public void run() {
// 獲取當(dāng)前線程的本地變量,然后累加
Integer integer = integerThreadLocal.get();
for (int i1 = 0; i1 < 5; i1++) {
integer += 1;
}
// 重新設(shè)置累加后的本地變量
integerThreadLocal.set(integer);
// 重新獲取值并打印出來
Log.i("MainActivity", Thread.currentThread().getName() + " - integer value : " + integerThreadLocal.get());
}
};
}
// 開啟線程
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
運行結(jié)果如圖:

我們可以看到5個線程最終打印的值都相同,也就說明了每一個線程第一次獲取的值都是0,也就是定義的初始值,每一個線程拿到了初始值的副本,然后操作的也是各自拿到的副本,操作結(jié)果不會對初始值有影響。
ThreadLocal的幾個方法說明:
// get()方法
public T get() {
// 獲取當(dāng)前執(zhí)行線程
Thread t = Thread.currentThread();
// 取得當(dāng)前線程的ThreadLocalMap實例
ThreadLocalMap map = getMap(t);
// 如果map不為空,說明該線程已經(jīng)有了一個ThreadLocalMap實例
if (map != null) {
// map中保存線程的所有的線程本地變量,我們要去查找當(dāng)前線程本地變量
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果當(dāng)前線程本地變量存在這個map中,則返回其對應(yīng)的值
if (e != null)
return (T)e.value;
}
// 如果map不存在或者map中不存在當(dāng)前線程本地變量,調(diào)用setInitialValue()方法返回初始值
return setInitialValue();
}
private T setInitialValue() {
// 我們創(chuàng)建ThreadLocal時重寫的方法,默認(rèn)返回null
T value = initialValue();
// 獲取當(dāng)前線程,然后取得當(dāng)前線程的ThreadLocalMap實例
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 不為null,直接設(shè)置初始值
if (map != null)
map.set(this, value);
// 為null,為當(dāng)前線程創(chuàng)建一個ThreadLocalMap實例,并設(shè)置初始值
else
createMap(t, value);
return value;
}
// 創(chuàng)建一個ThreadLocalMap實例并賦值
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// set()方法
public void set(T value) {
// 大部分和setInitialValue()方法一樣,
// 先獲取ThreadLocalMap實例,獲取到了設(shè)置值,沒有獲取到就創(chuàng)建一個并設(shè)置指定的值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 獲取當(dāng)前線程的ThreadLocalMap實例
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 移除當(dāng)前線程的ThreadLocalMap實例
public void remove() {
// 先獲取到,不為null就移除
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
了解完了ThreadLocal,接下來我們就來看一下Looper類。
Looper 類
1. 部分成員變量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); // ThreadLocal對象
private static Looper sMainLooper; // 主線程的Looper對象
final MessageQueue mQueue; // 與Looper綁定的MessageQueue對象
final Thread mThread; // 當(dāng)前Looper所在的線程
2. 構(gòu)造方法
// 構(gòu)造方法用private修飾,表示不能其他類不能創(chuàng)建,需要一個boolean類型的參數(shù)
private Looper(boolean quitAllowed) {
// 在構(gòu)造方法中與MessageQueue綁定
// 將boolean類型的參數(shù)quitAllowed傳給MessageQueue的構(gòu)造
// 我們在MessageQueue中說過這個參數(shù)的作用,true表示隊列不能退出,false表示能退出
mQueue = new MessageQueue(quitAllowed);
// 初始化mThread變量為當(dāng)前線程
mThread = Thread.currentThread();
}
3. 初始化方法 prepare()
// 準(zhǔn)備方法,調(diào)用帶參數(shù)的prepare()方法
public static void prepare() {
// 參數(shù)為true,表示隊列可以退出
prepare(true);
}
// 帶一個參數(shù) quitAllowed 的prepare()方法
private static void prepare(boolean quitAllowed) {
// 先從sThreadLocal獲取當(dāng)前線程的Looper對象,如果獲取到了,表示當(dāng)前線程已經(jīng)有一個Looper了
// 拋出一個異常,表示在一個線程當(dāng)中只能創(chuàng)建一個Looper對象
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 當(dāng)前線程沒有創(chuàng)建過Looper,那么就創(chuàng)建一個Looper并指定與Looper綁定的MessageQueue可以退出
sThreadLocal.set(new Looper(quitAllowed));
}
// 這個方法是專門為主線程創(chuàng)建Looper的
public static void prepareMainLooper() {
// 同樣調(diào)用帶參數(shù)的prepare()方法創(chuàng)建Looper,但是參數(shù)為false,
// 表示與Looper綁定的MessageQueue可以退出,也就是主線程的MessageQueue不能退出
prepare(false);
// 進(jìn)入同步代碼塊
synchronized (Looper.class) {
// 判斷成員變量 sMainLooper 是否為null,如果不為null,表示主線程已經(jīng)創(chuàng)建過了,拋出異常
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// sMainLooper 為null,調(diào)用myLooper()方法給 sMainLooper賦值
sMainLooper = myLooper();
}
}
4. 獲取方法
// 從sThreadLocal中取出當(dāng)前線程的Looper對象并返回
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
// 獲取與當(dāng)前線程綁定的MessageQueue對象
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
// 獲取當(dāng)前Looper所在的線程
public @NonNull Thread getThread() {
return mThread;
}
5. loop()方法,Looper類中最重要的一個方法
public static void loop() {
// 獲取與當(dāng)前線程綁定的Looper對象
final Looper me = myLooper();
// 為null,表示當(dāng)前線程沒有Looper對象
// 還不是Looper線程,拋出異常,沒有調(diào)用Looper.prepare()方法
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 是Looper線程,初始化queue指向當(dāng)前線程的MessageQueue對象
final MessageQueue queue = me.mQueue;
// 確保這個線程是運行在本地進(jìn)程
Binder.clearCallingIdentity();
// 保存一個用于跟蹤的身份令牌
final long ident = Binder.clearCallingIdentity();
// 進(jìn)入無限循環(huán)
for (;;) {
// 調(diào)用MessageQueue的next()方法從消息隊列中去消息
// 有可能會阻塞,next()方法在上一篇博客中有詳細(xì)說明
Message msg = queue.next(); // might block
if (msg == null) {
// 沒有消息表示消息隊列是退出狀態(tài),直接返回
return;
}
// 如果調(diào)用了setMessageLogging(@Nullable Printer printer)方法
// 那么就調(diào)用Printer接口中的方法打印日志信息
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 找到Message了,調(diào)用Handler中的dispatchMessage(msg)方法,分發(fā)和處理消息
// msg.target表示Message的目標(biāo)Handler,前面的博客強調(diào)過這個變量
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// 獲取一個新的身份令牌,和原來的身份令牌進(jìn)行比較
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
// 如果兩個身份令牌不同,打印一個錯誤級別很高的日志(What The Fuck)
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
// 釋放/回收消息
msg.recycleUnchecked();
}
}
在《Android Handler分析(一) Handler和Message詳解》這篇博客中,我們最后說到的了Handler中的dispatchMessage(msg)方法,當(dāng)時只是說讓大家記住,然后說了是在Looper中調(diào)用的,在這里就能看到了具體的調(diào)用位置及時間。
6. quit()退出方法
// 調(diào)用MessageQueue中的方法,在上一篇博客中有說明
public void quit() {
// 參數(shù)為false,表示非安全退出
mQueue.quit(false);
}
// 安全退出MessageQueue,參數(shù)為true
public void quitSafely() {
mQueue.quit(true);
}
擴(kuò)展相關(guān)知識
1. 主線程的Looper是在哪里初始化的
我們打開ActivityThread.java類,找到main()方法
public static void main(String[] args) {
...
// 調(diào)用Looper中的專門為主線程創(chuàng)建Looper對象的方法
Looper.prepareMainLooper();
...
// 開始輪詢,取消息
Looper.loop();
}
2. 在子線程創(chuàng)建一個Handler并開啟輪詢
new Thread(new Runnable() {
public void run() {
Looper.prepare(); // 此處獲取到當(dāng)前線程的Looper,并且prepare()
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();
}
};
handler.sendEmptyMessage(1);
Looper.loop(); // 開始輪詢
}
}).start();
以上代碼中注意:如果需要在子線程創(chuàng)建Handler,子線程必須是一個Looper線程, Looper.prepare(); 和 Looper.loop(); 都必須調(diào)用,否則會報錯, handleMessage() 方法運行在子線程。
如果子線程不是Looper線程,但是還是想在子線程中創(chuàng)建Handler對象,那么就是用如下方式創(chuàng)建:
new Thread(new Runnable() {
public void run() {
//Looper.prepare(); // 此處獲取到當(dāng)前線程的Looper,并且prepare()
// 在子線程創(chuàng)建Handler,但是綁定到主線程的Looper中
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();
}
};
handler.sendEmptyMessage(1);
//Looper.loop(); // 開始輪詢
}
}).start();
通過以上方式創(chuàng)建時,雖然Handler還是在子線程創(chuàng)建,但是handleMessage() 方法運行在主線程。
Android Handler消息機(jī)制寫到這里基本上寫完了,最后在對Handler機(jī)制做一個簡單的總結(jié):
- 我們創(chuàng)建Handler對象并重寫handlerMessage(Message msg)方法,然后在線程中使用Handler對象發(fā)送一個消息,在Handler類中實際上調(diào)用了MessageQueue中的方法將消息根據(jù)時間進(jìn)行排序,然后喚醒Looper,讓輪詢器不斷的獲取消息隊列中的消息(調(diào)用MessageQueue中的next()方法),取到消息之后就通過Message的目標(biāo)Handler(也就是Message中的target變量)調(diào)用Handler的handlerMessage(Message msg)方法根據(jù)Message的what不同來分別處理消息;
- 如果在創(chuàng)建Handler時指定了一個callback,并且回調(diào)中重寫的handlerMessage(Message msg)方法返回了true,那么,就不會再調(diào)用Handler中的handlerMessage(Message msg)方法了;
- 一個線程只能創(chuàng)建一個Looper對象,如果創(chuàng)建多個就會報錯;
- 主線程的Looper是系統(tǒng)創(chuàng)建的,并且MessageQueue不能退出;
- 在子線程中我們需要使用Handler,那就要調(diào)用Looper.prepar()方法初始化Looper,要想執(zhí)行Handler的handlerMessage(Message msg)方法,那還要調(diào)用Looper.loop()方法開啟輪詢,我們也可以在子線程中創(chuàng)建Handler并綁定到主線程的Looper上;
- MessageQueue對象不能創(chuàng)建,它是在初始化Looper時就自動創(chuàng)建的,并和Looper綁定到一起。