Handler到底是如何完成執行緒切換的?

2021-09-22 01:52:50 字數 4197 閱讀 1138

handler到底是如何完成執行緒切換的?這個問題要從handler最初的用法和原理講起。

首先我們列出正常情況下乙個handler使用的步驟然後講解分析如何實現,這裡不對一些基礎的概念做解釋,具體的請查閱原始碼。

handler的使用步驟

1.呼叫looper.prepare();

2.建立handler物件;

3.呼叫looper.loop()方法。

4.執行緒中傳送訊息。

這樣我們就建立好了乙個handler可以採用sendmessage等方法傳送訊息。

那麼這幾步都幹了什麼?

第一步:呼叫prepare方法

private static void prepare(boolean quitallowed) 

sthreadlocal.set(new looper(quitallowed));

}

建立了乙個looper物件儲存在了乙個threadlocal物件中,至於threadlocal是幹什麼的,查詢有關threadlocal如何儲存執行緒中資料的資料

上面**中建立了乙個loper物件,new looper的時候我們可以看到:

private looper(boolean quitallowed) 

// 建立了乙個messagequeue物件,這個就是乙個訊息佇列用來儲存我們傳送的message,其實這個messagequueue是乙個單鏈表。

後邊我們會知道這裡建立了乙個messagequeue物件,從字面上來看就是乙個訊息佇列,其實他是乙個鍊錶,鍊錶的資料結構就是便於插入和刪除。

第二步建立handler

public handler(callback callback, boolean async)
從構造方法中我們知道了hanlder中獲取了乙個looper物件,這個looper中儲存了乙個mqueue.這個lopper物件就是我們前面儲存在threadlocal中的那個。

第三步呼叫loop()

public static void loop() 

final messagequeue queue = me.mqueue;

binder.clearcallingidentity();

final long ident = binder.clearcallingidentity();

for (;;)

......

final long start = (slowdispatchthresholdms == 0) ? 0 : systemclock.uptimemillis();

final long end;

try

我們清楚的看到:loop()方法中獲取了looper物件,取出了訊息佇列mqueue,然後開始迴圈。同時呼叫了messagequeue的next()方法取出乙個訊息最後呼叫了這個訊息的dispathchmessage();看完原始碼我們知道這個msg.tagget.dispatchmessage()就是handler中的訊息處理的方法。

public void dispatchmessage(message msg)  else 

}handlemessage(msg);

}}

這個方法的**中我們看到,首先判斷了callback是不是null.這是因為handler使用有兩種方法,一種可以通過建立callback的方式。最後呼叫了hanlder的handlemessage(msg);這個方法就是我們經常自己實現的訊息處理方法。所以我們就到達了目標。

第四步:傳送訊息

呼叫sendmessage()等方法傳送訊息,追蹤傳送訊息的方法,最後呼叫了以下的方法:

public boolean sendmessageattime(message msg, long uptimemillis) 

return enqueuemessage(queue, msg, uptimemillis);

}

這裡拿到了我們前面handler構造方法儲存的mqueue,然後呼叫了enqueuemessage()方法。

private boolean enqueuemessage(messagequeue queue, message msg, long uptimemillis) 

return queue.enqueuemessage(msg, uptimemillis);

}

最後看queue.enqueuemessage(msg, uptimemillis);在queue中處理了訊息。

boolean enqueuemessage(message msg, long when) 

......

msg.markinuse();

msg.when = when;

message p = mmessages;

boolean needwake;

if (p == null || when == 0 || when < p.when) else

if (needwake && p.isasynchronous())

}msg.next = p; // invariant: p == prev.next

prev.next = msg;}}

return true;

}

也就是通過訊息要執行的時間,最後把訊息插入鍊錶中。

這樣我們就完成了訊息的傳送!

此刻:主線程的loop()方法一直在迴圈處理著訊息,我們傳送的訊息就會到loop()方法中去,最後交給handler處理。

那麼到底是怎麼樣就完成了執行緒的切換呢?

其實就是這個訊息傳送的過程,我們在不同的執行緒傳送訊息,執行緒之間的資源是共享的。也就是任何變數在任何執行緒都可以修改,只要做併發操作就好了。上述**中插入佇列就是加鎖的synchronized,handler中我們使用的是同乙個messagequeue物件,同一時間只能乙個執行緒對訊息進行入隊操作。訊息儲存到佇列中後,主線程的looper還在一直迴圈loop()處理。這樣主線程就能拿到子執行緒儲存的message物件,在我們沒有看見的時候完成了執行緒的切換。

所以總結來講就是:

1.建立了乙個looper物件儲存在threadlocal中。這個looper同時持有乙個messagequeue物件。

2.建立handler獲取到looper物件和messagequeue物件。在呼叫sendmessage方法的時候在不同的執行緒(子執行緒)中把訊息插入messagequeue佇列。

3.在主線程中(ui執行緒),呼叫looper的loop()方法無限迴圈查詢messagequeue佇列是否有訊息儲存了。有訊息就取出來呼叫dispatchmessage()方法處理。這個方法最終呼叫了我們自己重寫了訊息處理方法handlemessage(msg);這樣就完成訊息從子執行緒到主線程的無聲切換。

最後補充:我們經常使用handler沒有建立looper呼叫looper.pepare()和looper.loop()是因為在activitythread中已經建立了主線程的looper物件,儲存在了threadlocal中,我們在建立hander的時候就會從threadlocal中取出來這個looper。

從主線程類activitythread的入口方法main()方法中我們可以清楚的看到系統幫我們如何實現的:

public static void main(string args) 

if (false)

// end of event activitythreadmain.

trace.traceend(trace.trace_tag_activity_manager);

looper.loop();// 開始呼叫loop()方法

throw new runtimeexception("main thread loop unexpectedly exited");

}

this到底是誰

js中函式的4種呼叫方式 1.作為普通函式來呼叫 alert window.xx undefined function t t alert window.xx 333 解釋 作為普通函式來呼叫this時,this的值指向 windwo,準確的說,this為null,但被解釋成window,在ecma...

Segmentation fault到底是何方妖孽

那麼對於任何沒有經過 mmu對映過的虛擬空間的位址,不管程序是執行寫操作還是讀操作,作業系統都會捕捉到這個錯誤的非法訪問,然後輸出乙個 segmetation fault 的錯誤提示資訊並強行終止程序。程式之所以會時不時的出現 segmetation fault 的根本原因是程序訪問到了沒有訪問許可...

孔子到底是誰?

提起孔子,想必中國人都知道,甚至比熟悉人民幣還熟悉他。因為對他的炒作已經延續了幾千年,使得這位山東大漢紅的發紫,但孔子到底是誰呢,擁有這麼大的魔力,在中華幾千年文化中處處留下他的烙印,使得 孔子門 經得起千年歲月的沖刷,依然濤聲依舊。其實孔子是被歷代帝王捧紅的,也就是說,孔子是中國被炒作最厲害的人物...