深入理解異步加載--Handler和Looper源碼解析(2)

上一章介紹了一些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,就可以一目瞭然了

轉載請註明出處,尊重別人的勞動成果

發佈了37 篇原創文章 · 獲贊 77 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章