Handler 之 ThreadLocal 相關

Kendrick784 8年前發布 | 14K 次閱讀 安卓開發 Android開發 移動開發

來自: http://gudong.name/technology/2016/03/11/handler_analysis_three.html

在上一篇文章Handler 之 源碼解析中介紹 Handler 與 Looper 的關系時,半路出現了 ThreadLocal 這個類,他是什么呢,本想在 Handler 源碼解析一文一起闡述了, 但是覺得這樣篇幅太長,不好,況且他又是一個相對獨立的概念,這里就把它單獨拿出來,結合任玉剛的 Android的消息機制之ThreadLocal的工作原理 博文,為自己總結歸納下 ThreadLocal 的用處,以及他在 Handler 和 Looper 中的巧妙用法。

ThreadLocal

ThreadLocal并不是線程,它的作用是可以在每個線程中存儲數據。

其實在Handler 之 源碼分析一文中,關于 Handler 有一點我一直沒說到。

Handler創建的時候必須使用當前線程的 Looper 來構造消息循環系統,而自己手動創建的子線程默認是沒有 Looper 的, 如果在一個子線程中創建 Handler ,就必須為這個子線程創建 Looper,否則我們就會看到一個常見的異常

Can't create handler inside thread that has not called Looper.prepare()

那我們如何在子線程中創建一個 Looper 呢?

其實從上面的異常信息,我們已經知道了,在子線程中調用 Looper.prepare() 方法就可以為這個子線程創建一個 Looper 對象。

是該拋出一個觀點的時候了。

我們在主線程創建一個 Handler,這個 Handler 就需要一個和主線程綁定的 Looper,如果實在一個子線程創建一個 Handler, 那么我們就需要為這個 Handler 綁定一個與子線程綁定的 Lopper。

至于原因,應該是這樣的,主線程的 handler 發送消息后,應該是主線程的 Looper 去輪訓與主線程相關的那個 MessageQueue,并且處理消息,子線程中創建的 Handler 對象在發送消息時,(不論他在什么地方發送),應該是該子線程對應的 Looper 去輪詢相應的 MessageQueue,然后處理消息。

上面這樣做的好處就是有條理,不亂套。

但是現在有一個問題,如何控制 Looper 的 myLooper() 方法返回的對象是當前線程對應的 Looper 呢?

public static @Nullable Looper myLooper() {
   return sThreadLocal.get();
}

這就是 ThreadLocal 妙用之所在,ThreadLocal 可以在不同的線程之中互不干擾地存儲并提供數據,這句話的意思很清楚。

就是說,在不同的線程中會存儲不同的數據,他們互不干擾。

下面這行,摘抄自任玉剛的博客

ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以后,只有在指定線程中可以獲取到存儲的數據,對于其它線程來說無法獲取到數據。

這個特性正好滿足 Handler、Looper 在線程方法的特性,可以確保在主線程中調用 sThreadLocal.get(); 得到的是主線程對用的 Looper 對象,在子線程中調用得到的是子線程中對應的Looper。

上面也說了,如果我們在子線程中去實例化 Handler,必須先調用 Looper.prepare() 方法。通過這個方法,為所在的子線程綁定一個 Looper,這個 Lopper 將會在子線程中 new Handler() 時自動綁定到這個 Handler,解析來我們看看,這一切在子線程中是怎么發生的。

首先分析出錯的情形,也就是直接在子線程中 new Handler() 時報錯,是怎么發生的。現在子線程中創建一個 Handler

new Thread(new Runnable() { @Override public void run() { Handler innerHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; } }).start();

接著看 Handler 的構造方法。

public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( “Can’t create handler inside thread that has not called Looper.prepare()”); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }

當執行到 myLooper()

public static @Nullable Looper myLooper() { return sThreadLocal.get(); }

此時的 sThreadLocal 是一個線程數據存儲類,但是因為是在子線程中訪問,所以它對應者子線程中的資源。

由于我們在這個子線程中沒有事先對 sThreadLocal 做任何處理,沒有設置任何 Looper 對象,所以此時的 myLooper() 返回值一定是 null。

接下來,我們就會看到那個一開始學 Handler 時經常看到的錯誤日志了。

Can't create handler inside thread that has not called Looper.prepare()"

然后,在以前,我直接在 handler 之前根據錯誤提示加一個 Looper.prepare() 方法,然后錯誤就沒有了,我自己也就不管了,但是這次,我一定要看看,為什么調用 Looper.prepare() 就不報錯了。

prepare() 源碼

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

此時在方法中,首先判斷 sThreadLocal.get() != null ,因為現在在子線程,而且沒有為 sThreadLocal 設置過 Looper 對象,所以此時 sThreadLocal.get() 一定是 null 的,所以會直接執行

sThreadLocal.set(new Looper(quitAllowed));

我們看到,最終通過 prepare() 方法設置了在子線程中 sThreadLocal 的值為一個新的 Looper 對象。

那么只要設置過了 Looper 對象,下一次在 sThreadLocal 執行 get 操作時就會得到已經設置好的 Looper,這個 Looper 最終會被綁定到子線程中創建的 Handler 上面。

這里需要特別注意一點,僅僅在創建 handler 之前調用了 Looper.prepare() 并不能完事大吉,如果你想通過這個 handler 接受消息,你就一定需要在創建完畢 Handler ,立即執行 Looper.loop() 方法,讓剛才創建的 Looper 立即工作起來。

否則,后續你在子線程發送消息了,但是你卻不能在子線程的 handler 中接受到消息,就出出現這樣的問題。

所以補全上面的代碼,如下所示。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler innerHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i("msg","content is "+msg.what);
            }
        };
        Looper.loop();
    }
}).start();

這里不禁就要問了,在子線程中是這樣創建 Handler 的,需要手動調用 Looper.prepare(); 以及 Looper.loop();那為什么在主線程中定義 Handler 不需要這些操作呢?

因為主線程,也就是我們經常提到的主線程,也叫UI線程,它其實就是ActivityThread,ActivityThread被創建時就會初始化Looper,代碼如下

public static void main(String[] args) {

  //............. 無關代碼...............

  Looper.prepareMainLooper();

  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");   }

所以在主線程中創建 handler 時,是不需要手動去創建 Looper 的,因為他們早已創建好了。

</article>

 本文由用戶 Kendrick784 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!