Handler,Looper,MessageQueue,ThreadLocal的關聯

一.前言

本以爲這些東西是老生常談,並且作爲Android開發的基礎入門知識,是必須熟練掌握的,但是發現每每提起Handler及Looper,ThreadLocal的原理,總是要去翻閱資料,或者查看源碼再捋一遍,乾脆花一點時間整理一下,等下次再忘記拿出來稍稍翻閱一下就ok了。

二.原理淺析

分析一件事情往往需要帶着問題去分析,下面我們將從以下幾個問題進行分析:

  1. 爲什麼主線程直接new Handler就可以,子線程直接new Handler會報異常?報什麼異常?子線程創建handler應該需要什麼樣的步驟?
  2. 消息是怎麼進行跨線程傳遞的,最後是怎麼又傳回給了handler?
  3. Looper是怎麼做到每個線程都存在一個實例的?並且在不同的線程中取到的是對應線程的實例?ThreadLocal的原理是什麼樣的?

相信你如果能夠很詳盡的回答這三個問題,那麼本篇文章可能對你來說只是一個查漏補缺的過程,不過如果你對這三個問題回答模棱兩可甚至回答不出來,那麼請務必好好閱讀此篇文章。

首先我們先看下怎麼在主線程中創建Handler

 private void initMainThreadHandler() {
        mMainThreadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.d(TAG, "mainThread \n"   msg.toString());
            }
        };
    }

如果同樣按照這種方式在子線程中創建Handler如下

 private void initOtherThreadHandler() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                mOtherThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.d(TAG, "otherThread \n"   msg.toString());
                    }
                };
            }
        }).start();
    }

那麼此時,調用initOtherThreadHandler一定會報異常,報的異常爲:

   java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)

那爲什麼會報這個異常呢,我們可以追溯到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的賦值,是Handler的成員變量,後面會用到
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到Looper.myLooper()得到的對象是null,所以就拋出了一個異常,那麼我們此時就要保證這個方法得到的對象不能爲空,至於爲什麼爲空呢?後面將ThreadLocal源碼會講到,優化後的子線程初始化handler如下:

private void initOtherThreadHandler() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                            //準備該線程對應的looper
                Looper.prepare();
                mOtherThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.d(TAG, "otherThread \n"   msg.toString());
                    }
                };
                                //循環遍歷messageQueue來讀取消息隊列中的消息,從而能夠處理消息並保證線程在一直執行
                Looper.loop();
            }
        }).start();
    }

那有的同學可能會問了,爲什麼主線程不會拋異常? 那是因爲在應用App啓動的時候,會在執行程序的入口ActivityThread.class類中主函數public static void main(String[] args)裏面創建一個Looper對象:Looper.prepareMainLooper(),然後調用Looper.loop();完成Looper對象的創建。實際上Looper.prepareMainLooper()方法還是調用了Looper的prepare()方法完成Looper對象的創建。因此在主線程中通過關鍵字new創建的Handler對象之前,Looper對象已經存在並始終存在。

至此,我們解決了第一個問題,下面解決第二個問題。

上面我們創建了handler,那麼怎麼發消息呢:

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.test1:
                //給主線程發消息
                Message obtain = Message.obtain();
                obtain.obj = "主線程收到消息";
                mMainThreadHandler.sendMessage(obtain);
                break;
            case R.id.test2:
                //給子線程發消息
                Message obtain2 = Message.obtain();
                obtain2.obj = "子線程收到消息";
                mOtherThreadHandler.sendMessage(obtain2);
                break;
        }
    }

分別點擊兩個按鈕,打印日誌:

11-20 13:52:04.196 31131-31131/com.huli.hulitestapplication D/HandlerActivity: mainThread 
    { when=-1ms what=0 obj=主線程收到消息 target=com.huli.hulitestapplication.activitys.HandlerActivity$1 }
11-20 13:52:14.166 31131-31163/com.huli.hulitestapplication D/HandlerActivity: otherThread 
    { when=-1ms what=0 obj=子線程收到消息 target=com.huli.hulitestapplication.activitys.HandlerActivity$2$1 }

我們可以看到,給對應線程發消息,就是使用對應線程裏面創建的handler發消息,給主線程發消息,就是使用mMainThreadHandler,給子線程發消息,就是使用mOtherThreadHandler,那這個消息怎麼進到對應線程並處理,我們來看發消息的源碼:

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
             //這個mQueue是Handler的成員變量,是Looper的成員變量賦值過來的
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this   " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
        
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
           //這裏將消息的target屬性賦值爲handler,後續會用這個target來處理消息
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
                //然後將消息塞進隊列中
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到,對應的發消息,其實就是將message的target指向當前發消息的handler,然後將message塞入消息隊列中,這個消息隊列是通過Looper.loop()來進行遍歷的,我們繼續看源碼:

public static void loop() {
        final Looper me = myLooper();
      
            //這個隊列跟發消息的Handler裏面的隊列是一個對象,Handler裏面隊列添加了消息,這裏就能取到消息
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
        
            try {
                         //關鍵代碼 使用調用msg.target.dispatchMessage方法處理message
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        }
    }

由上面代碼,我們可以看到,最終就轉到了發消息的Handler來處理

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
                        //由於上面都爲空,所以走到這裏,調用下面的方法
            handleMessage(msg);
        }
    }
        //這個方法就是我們重寫的方法,最終打印出了日誌
         public void handleMessage(Message msg) { }

所以我們瞭解到,消息是由handler進行發出,塞進對應線程的messageQueue中,對應線程的Looper又進行了遍歷,取出消息,交由對應發消息的handler來處理,進行了消息的發送和處理,那麼新的問題又來了,這個對應線程是怎麼來的?怎麼根據線程來獲得對應的Looper和messageQueue?這就是接下來我們要分析的第三個問題了。

我們會發現我們是通過Looper.prepare()來進行對應線程Looper的初始化的,後面又通過Looper.myLooper()進行對應線程的Looper的獲取的,這裏面肯定有貓膩,這就涉及到了ThreadLocal的原理了,接下來我們看源碼:

//Looper類

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

    private static void prepare(boolean quitAllowed) {
           //從這也可以看出,每個線程Looper.prepare()只能調用一次,否則會拋異常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

從上面就看到ThreadLocal的身影了,我們先解釋下ThreadLocal:

由於內容比較多,這裏貼一篇詳細的文章,我下面做簡單的總結:

ThreadLocal原理分析與使用場景

其實每個線程Thread類裏面都有一個成員變量ThreadLocal.ThreadLocalMap threadLocals,該map的key爲ThreadLocal,value爲要創建的副本,當我們第一次調用Looper.prepare()的時候,首先會調用sThreadLocal.get方法,進到get方法裏面得到當前線程的map,然後獲取key爲sThreadLocal的value值,得到的爲null, 然後調用 sThreadLocal.set(new Looper(quitAllowed));, 進到set方法,同樣的獲取當前線程的map,然後給當前map設置key爲sThreadLocal,value爲新創建的Looper,此時當前線程就存在Looper了

接下來我們創建Handler的時候,就會調用Looper.myLooer()獲取當前線程的Looper,就是通過Looper的sThreadLocal.get得到的

後面進行消息遍歷的時候,也是通過Looper.myLooer()獲取當前線程的Looper,即通過Looper的sThreadLocal.get得到的

所以這樣我們就回答了第3個問題,Looper能夠做到每個線程都有一個Looper副本,就是通過Looper的靜態成員變量sThreadLocal做到的,它是通過獲取當前線程的ThreadLocalMap來判斷對應線程是否有副本,有就能取到當前線程的Looper副本,所以我們並沒有手動給Handler賦值Looper,它就能正常取到其值,就是通過ThreadLocal來做的。

如果在開發過程中你也有不同線程都存在一個副本的需求,就可以嘗試使用ThreadLocal來實現。

至此,大功告成。

本文由博客一文多發平臺 OpenWrite 發佈!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章