簡書:https://www.jianshu.com/p/3523bed4d88f前言Handler是個老生常談的問題,我相信幾乎所有的Android開發者都會使用Handler,那關於Handler還有什麼好講的嗎?Handler如果僅僅是使用的話,確實沒什麼好講的,但是Handler卻是一個幾乎所有面試官都會問的問題,不同的要求問的深度也不一樣,今天我就帶大家學習一下關於Handler你所必須要掌握的知識。
Handler消息機制首先有四個對象我們必須要了解一下Handler、Looper、ThreadLocal還有MessageQueue。
Handler首先我們要明白Handler消息機制是用來幹嘛的?Handler是把其他線程切換到Handler所在的線程,注意,這裡不是UI線程。雖然我們的大多數使用Handler的場景,都是我們在子線程做了一下耗時的操作(IO或者資料庫),當子線程執行完以後我們可能需要更新UI,這個時候我們用Handler來處理(sendMessage()或者post())就把子線程切換到了UI線程了。假如說,我們現在有這麼一個需求,線程A發個信息給線程B(線程A、線程B都不是主線程),這個時候我們用Handler依然可以做,只需要在線程B中創建好Handler就可以了(關於如何在子線程創建Handler我會在下面詳細說明)。所以,Handler並不是把其他線程切換到主線程(UI線程),而是切換到它所在的線程,這一點一定要弄清楚。
LooperHandler消息機制裡面最核心的類,消息輪詢。Handler要想切換線程(或者說發送消息),必須要先獲得當前線程的Looper,然後調用Looper.loop()方法把MessageQueue裡面的message輪詢出來"交"給Handler來處理,至於如何「交」給Handler的,下面我會通過源碼帶大家分析。
ThreadLocalThreadLocal是Looper內部的一個,它雖然前面有個「Thread」,但其實它並不是線程,它相當於一個線程內部的存儲類。剛才在講Looper的時候我們說到,「Handler要想切換線程(或者說發送消息),必須要先獲得當前線程的Looper」,那如何獲得當前線程的Looper呢?正是通過ThreadLocal,它可以在不同線程之間互不幹擾地存儲數據。ThreadLocal裡面有一個內部靜態類對象ThreadLocalMap,通過其set()和get()方法對數據對象進行保存和讀取。
MessageQueueMessageQueue——消息隊列,雖然它叫消息隊列,但實際上的結構並不是一個隊列,而是一種單鍊表的數據結構,只是使用列隊的形式對數據進場做添加或者移除。MessageQueue是在Looper被創建的時候由Looper生成的。同一線程下,Handler發送的所有message都會被加入到MessageQueue裡面,當Message被loop()以後,就會從消息隊列中移除。Message我沒有單獨拿出來,因為確實比較簡單,就是消息本身,它可以攜帶兩個int型參數,如果要傳比較複雜的參數就用obj屬性,what屬性用來區分是哪個Handler發送的信息。
小結一下Handler是把其他線程切換到它所在的線程,使用Handler發消息之前要先創建Looper對象,創建和讀取Looper都需要使用ThreadLocal,它可以在不同線程之間互不幹擾地保存數據。在創建Looper對象的同時也把MessageQueue創建好了,Handler所發的message會被添加到MessageQueue對象裡面,Looper.loop()方法以後會把MessageQueue上的Message輪詢出來。連接這幾個對象的還有一條很重要的線,那就是線程,記住,以上的過程都要保證Handler、Looper、MessageQueue在同一線程,這樣,才能保證Handler的消息機制被正確使用。
子線程創建Handler注意:我們這裡是為了讓大家更好地學習Handler,所以要在子線程創建Handler,現實使用場景,很少會在子線程創建Handler
在主線程(UI線程)創建Handler,我相信所有人都會
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainHandler = new Handler();
mainHandler.post(new Runnable() {
@Override
public void run() {
Log.e("qige_test", "thread_name=" + Thread.currentThread().getName());
}
});
}我們先按照主線程的方式在子線程創建一個Handler試一下,看看會有什麼樣的結果
new Thread(){
@Override
public void run() {
childHandler=new Handler();
childHandler.post(new Runnable() {
@Override
public void run() {
Log.e("qige_test","thread_name="+Thread.currentThread().getName());
}
});
}
}.start();沒錯,如圖所示還沒有創建Looper,那麼如何創建Looper呢?圖中有句話已經給出了答案——Looper.prepare(),同時為了讓我們發送的消息被輪詢到,還必須要調用Looper.loop(); 所以在完整的在子線程創建Handler的代碼如下:
new Thread(){
@Override
public void run() {
//創建Looper,同時創建MessageQueue
Looper.prepare();
childHandler=new Handler();
childHandler.post(new Runnable() {
@Override
public void run() {
Log.e("qige_test","thread_name="+Thread.currentThread().getName());
}
});
//輪詢
Looper.loop();
}
}.start();這樣就可以在子線程裡面創建Handler了,看到這裡,你可能會問,為什麼我在主線程創建Handler的時候,沒有調用Looper.prepare()和Looper.loop()?這個問題我們先放一下,我們先從源碼角度把Handler消息機制分析一下。
從源碼分析先看一下創建Looper和MessageQueue的方法Looper.prepare()
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//ThreadLocal來了
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}這裡就出現了我剛才說的ThreadLocal對象。Android規定每個線程有且僅有一個Looper,所以為了防止不同的線程之間的Looper相互影響,使用ThreadLocal對象來存儲Looper。
再看一眼new Looper()的源碼
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}MessageQueue也出來了,創建Looper的同時創建MessageQueue。
下面我們看Handler.post()或者是Handler.sendMessage()他們本質是一樣的,把消息加入到消息隊列當中
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}post()或者sendMessagexxx()最終都會調用sendMessageAtTime(Message msg, long uptimeMillis)方法,我們直接看這個方法的源碼
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//關鍵代碼部分
return enqueueMessage(queue, msg, uptimeMillis);
}都很簡單,最後一步enqueueMessage(queue, msg, uptimeMillis);是把消息加入隊列,咱們再點開看一下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//把當前的Handler賦值給msg.target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}關鍵要看的地方是 msg.target = this剛才講Message的時候沒有提,Message的target屬性就是一個Handler對象,這裡直接把當前的Handler對象賦值過去了。最後一行:queue.enqueueMessage(msg, uptimeMillis)是把message加入到消息隊列裡面,具體的實現我抓不到了,知道這句話是把message加入到MessageQueue裡面就行。
下面分析* Looper.loop() 方法的關鍵部分源碼public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
**標註1**
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
....
..
**標註2**
for (;;) {
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
.
try {
**標註3**
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
**標註4**
msg.recycleUnchecked();
}
}看源碼的時候注意看一下標註,一個個地來講;
標註1:保證了Handler、Looper和MessageQueue在同一線程
標註2:沒有看錯,就是一個無線死循環,Looper會不停地對MessageQueue進行輪詢。這時候,你可能會問,這種無限死循環會不會很浪費資源?其實並不會,因為當沒有message被添加到隊列的時候,程序會進入阻塞狀態,對資源的消耗是很小的。
標註3:還記得剛才講地msg.target吧,這裡 msg.target.dispatchMessage(msg);就是handler.dispatchMessage(msg),直接去看Handler的dispatchMessage(msg)方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}這裡面msg.callback是個Runnable對象,也就是當我們使用handler.post(Runnable r)的方法的話,這時候就直接去調用Runnable對象裡面的東西了,如果使用的是handler.sendMessagexxx(),就是去調用我們重寫的handleMessage(msg)方法了。
如果調用post()方法發送消息mainHandler.post(new Runnable() {
@Override
public void run() {
//這裡會被調用
Log.e("qige_test","thread_name="+Thread.currentThread().getName());
}
});如果調用sendMessagexxx()
mainHandler.sendEmptyMessage(0);
mainHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
//這裡會被調用
Log.e("qige_test","what="+msg.what);
}
};我們再回到上一級源碼
以上基本就是一個完整的Handler消息機制的過程,我再帶大家好好回顧一下:
1.Looper.prepare();//這一步創建了Looper和MessageQueue
關於在主線程創建Handler的疑問
2.handler.sendMessagexxxx(); // 把message添加到MessageQueue上
3.Looper.loop();//輪詢消息
4.handler.dispatchMessage(msg);//處理消息好了,到了該解決歷史遺留問題的時候了,為什麼我們在主線程創建handler不需要調用Looper.prepare()和Looper.loop()呢?
這是因為,主線程已經給我們創建好了,在哪裡創建好的呢?
在Java裡面,有一個程序的主入口,就是靜態main方法public class Test {
//程序入口
public static void main(String... args){
}
}在Android裡面呢,同樣有這麼一個main方法入口,它在ActivityThread類中。在我們App啟動過程中,會調用到ActivityTread的main()方法(由於篇幅問題,該過程沒有辦法詳細講,後期會推文章單獨說一下),下面我們直接看ActivityTread的main()方法的源碼
為了方便理解,還是只看關鍵部分代碼public static void main(String[] args) {
....
//創建Looper和MessageQueue對象,用於處理主線程的消息
Looper.prepareMainLooper();
//創建ActivityThread對象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (創建新線程)
thread.attach(false);
//消息輪詢
Looper.loop();
}看到這裡,我想你應該明白了為啥在主線程裡創建Handler不需要調用Looper.prepare()和Looper.loop()方法了吧,因為在App啟動的時候,ActivityThread已經給我們創建好了。不過,需要注意的是,我剛才在講Looper.loop()源碼的時候說過這裡面會有一個無限死循環,那麼程序運行到這裡豈不是要永遠卡在這了呢,那我們的Activity、Service都是咋執行的呢?看關鍵的源碼這一句thread.attach(false),注釋上其實解釋的很清楚,通過Binder創建了新的線程,在這個新的線程裡面運行了Activity、Service等組件,而之所以Android為什麼採用這種方式,讓我們留在以後再說,你只需要知道,Looper.loop()不會造成卡頓,主線程已經給我們創建好了Looper和MessageQueue對象就可以了。
為什麼寫這篇文章開篇我就說過,Handler幾乎所有人都會用,但僅僅會用是不夠,要知其然更知其所以然。很多面試官願意問Handler相關的問題,好好閱讀這篇文章,它會讓你在面試的時候事半功倍,希望對大家有所幫助,點個讚!
創作不易,點個「在看」