上一章介紹了一些Handler類和Looper類,其實這些內容網上有一大把,我只不過是做了個筆記,便於以後回憶
在這章,會放出一點乾貨,講講別人沒講過的東西。
看看這個圖和我們的Handler,Looper和MessageQueue模型像不像
其實我們的異步加載模型就是從多生產者,單消費者模型裏借鑑出來的。
我們再看下生產者/消費者 模型的定義:
在實際的軟件開發過程中,經常會碰到如下場景:某個模塊負責產生數據,這些數據由另一個模塊來負責處理(此處的模塊是廣義的,可以是類、函數、線程、進程等)。產生數據的模塊,就形象地稱爲生產者;而處理數據的模塊,就稱爲消費者。
單單抽象出生產者和消費者,還夠不上是生產者/消費者模式。該模式還需要有一個緩衝區處於生產者和消費者之間,作爲一箇中介。生產者把數據放入緩衝區,而消費者從緩衝區取出數據
我們在子線程發送Message不就是生產出數據,放入MessageQueue的緩衝區,然後用Looper在主線程中取出來,用於消費。
只不過我們是使用自旋鎖的形式用Looper死循環,不斷的去取數據。
我們再來看下上張講的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.");
}
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.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
.
.
.省略下面取消息的部分
.
我們可以看到
final Looper me = myLooper();這句代碼就是用來取出Looper對象
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
}
而myLooper()的實質就是通過ThreadLocal取出當前線程中的Looper。
這裏稍微介紹一下ThreadLocal
這個對象作用於是線程,根據不同線程號,在每個線程中會維護一個values數據,用於存放數據。這樣的好處是不用維護一個Hash表去區分不同線程數據。每個線程有獨立的副本對象。
用於我們一般使用場景都是子線程把數據交給主線程去處理的,使用的都是MainLooper,所以我們傳進去的ThreadId都是UI線程的ID,取到的都是MainLooper。由於MainLooper是在U線程掛起的(自旋),所以取出來後的Message調用getTarget得到Handler對象後去處理消息內容也是在UI線程當中了。
這裏有一個很重要的概念,我曾經問了很多人都似是而非,不是很理解。對象和線程是沒有關係的,我們使用Handler進行異步加載其實是沒有切換線程,準確的說代碼也無法切換線程。我們只不過是子線程準備好了數據,放進了一個內存中共享的緩衝區(堆),然後在UI線程把數據取出來,進行了處理。也正因爲這樣,MessageQueue是線程不安全的,可以看到在源代碼裏在進插入單鏈表和取數據的時候都加了synchronized鎖,防止數據髒讀。
基於上述理論,我們完全可以實現自己的異步加載
調用
A Thread:
Looper.prepare();
Handler handler = new Handler(Looper.myLooper());
Looper.loop();
然後我們在別的線程就可以把數據交給A Thread去處理了。
順便再提一下,串聯整個異步加載過程的是MessageQueue對象。
MessageQueue是Looper的一個成員變量,Looper成員變量裏取消息,然後再new Handler對象的時候,又會把Looper對象傳給Handler對象,Handler就把Message都存在了Looper對應的MessageQueue裏了。這樣存和取就對上了。
如果對上述內容有什麼疑問,可以在關鍵代碼處打印下線程ID,就可以一目瞭然了
轉載請註明出處,尊重別人的勞動成果