天天看點

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

上一章介紹了一些Handler類和Looper類,其實這些内容網上有一大把,我隻不過是做了個筆記,便于以後回憶

在這章,會放出一點幹貨,講講别人沒講過的東西。

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

看看這個圖和我們的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,就可以一目了然了

轉載請注明出處,尊重别人的勞動成果