手撕Handler

前言

在平常開發中,咱們勢必會使用到子線程和UI線程的通訊,而起着橋樑做用的就是咱們經常使用的Handler。可是他的內部是怎麼運做的?運做的過程當中存在什麼問題?須要咱們注意,本文將會詳細講解。java

解析Handler

從圖中咱們就能夠知道了,整個Handler工做組成的包括了HandlerLooperMessageQueueMessage這四個部分。git

MessageQueue和Message分別只是一個隊列和消息實體類,天然再也不多說。 而Handler和Looper的具體是怎樣的呢?github

在個人模擬Handler項目中,已經比較清晰的闡述了整個框架的工做流程,接下里就是結合SDK代碼的一份解析了。編程

整個Handler往簡單了來講其實就幹了兩件事情:安全

  • 發送消息
  • 處理消息

發送消息

涉及到的三個函數sendMessage()enqueueMessage()Looper.prepareMainLooper()框架

全部事情的起源要從Looper.prepareMainLooper()開始講起。 這個函數處於ActivityThread中,沒有了解過這個類的讀者們須要知道,java編程必定是有一個主入口的,可是咱們在整個Android編程中,歷來沒有涉及過main()這個函數,是由於它已經包含在了ActivityThread這個類中,而它已經通過了複雜的封裝。ide

接下來看下這個Looper.prepareMainLooper ()函數。函數

public static void prepareMainLooper() {
        prepare(false); // 1
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper(); // 2
        }
    }
// 上述註釋1對應的函數
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));
    }
// 上述註釋2對應的函數
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製代碼

兩小段代碼,裏面用到了一個變量sThreadLocal,這個變量是使用static final修飾的,意味這全局惟一。從他主動拋出的異常咱們也能夠看出Looper這個對象也是一個惟一的變量。這是咱們須要掌握的第一個知識點。oop

接下來是關於sendMessage()函數 這個函數實際上是一個泛稱,他並不僅僅指sendMessage(),他還能夠是sendMessageAtTime()sendMessageDelayed(),他們都幹了一件事情——傳遞消息。經過一直向下探索,你就能知道他們最後調用的都是enqueueMessage()這個函數,也就是把消息放進了消息隊列中。post

沒有不少的操做,就是咱們熟悉的鏈表操做。這裏沒有作展現,有興趣的朋友進到源碼往下翻一點,立刻就能看到了。

就這樣很簡單,而且很成功的讓咱們的消息進入了消息隊列。

處理消息

接收完消息,咱們要幹嗎?咱們爲何要發消息,由於咱們要處理啊。

這裏咱們要遇到的函數有:Looper.loop()dispatchMessage()handleMessage()。 用過Handler的讀者們都應該知道咱們是須要重寫handleMessage()這個函數的,用於對不一樣的消息做出響應,因此就再也不多介紹。 因此第一個講的就是Looper.loop()這個函數。

Looper源碼截圖1

Looper源碼截圖2

一共兩段代碼,也是相當重要的一部分。 在這個代碼中兩個相當重要的點: (1)首先是問題是這麼一個死循環的函數,怎麼就沒引起ANR呢???? (2)經過dispatchMessage()如何分發這個消息的?target是什麼?

先是第一個問題的解答。 網上的解答多種多樣。可是最關鍵的點實際上是這樣的,ANR是圍繞loop()這個函數展開的,而ANR的出現也就是loop()的消息沒有獲得及時的消費。

第二個問題。 先說target這個爆紅的變量是什麼。msg.target也就是說這是Message的一個元素,搜索Message就能找到以下圖示。

原來target就是一個Handler,而這個Handler就是咱們對應的主動建立Handler。 而後就是dispatchMessage()函數了。

/** * Handle system messages here. */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

想來這就很清楚了,他也就是將事件分發給了handleMessage()處理,而handleMessage()又是咱們本身來專門寫的。msg.callback是一個Runnable對象。

害,原來就是這樣啊。。

思考

  1. Handler的內存泄漏實例。
  2. 爲何Handler不能在子線程建立?
  3. 爲何Handler構造方法裏面的Looper不是new出來的?

問題1:Handler的內存泄漏實例。

Handler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

         handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                startActivity(new Intent(MainActivity.this, HandlerTestActivity.class));
                return false;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 中斷3秒
                SystemClock.sleep(3000);
                handler.sendEmptyMessage(0);
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("onDestroy", "已銷燬");
        handler.removeCallbacksAndMessages(0); // 2
        handler = null; // 1
    }
複製代碼

若是不加註釋1和註釋2,這就是一段會內存泄露的代碼,看了代碼,應該也清楚邏輯十分簡單,就是一個跳轉。推出程序後,你仍然會看到跳轉,這就是Handler的內存泄漏。

問題2: 爲何Handler不能在子線程建立?

這個問題其實有點問題,對於修改過底層的華爲的操做系統並不存在這樣的問題,可是正常的Android原生系統就不行了。 代碼以下

new Thread(new Runnable() {
            @Override
            public void run() {

                handler = new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        return false;
                    }
                });
            }
        }).start();
複製代碼

問題2報錯

經過對報錯溯源,咱們就能發現這樣一個問題。

他拿不到 Looper,由於他不是UI線程。 其實這就是問題所在,咱們上文講過 sThreadLocal這個變量,他經過一個 get()函數獲取了 Looper。可是這裏存在一個問題,這個 get(),他獲取的是什麼。 因此咱們也就進去看看好了。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

原來,他的取法就是從一個Map中進行獲取的,而key,就是當前線程,因此當咱們在子線程中建立Handler的時候,咱們也是照樣拿不到Looper的,這個時候咱們一樣明白了Looper也是一個惟一的,由於他不會爲咱們建立出來的一個子線程再添加一個Looper,而是共用。

就這個問題,Google其實有給出解決方案,詳細請看 》》HandlerThread那些事兒

問題3:爲何Handler構造方法裏面的Looper不是new出來的?

這個問題的性質和問題2有點相似了,惟一性。Looper做爲一個事件處理的重要組成部分,想來咱們已經看到了,就像多道程序設計技術同樣,這是一個不受控制的過程,咱們須要瘋狂的思考安全性,同步性等問題。這也是惟一性的好處,因此事件統一處理,處理起來也就有序。至少在咱們的平時使用中已經證實了這是一個可取的方法。

以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。


相關文章推薦:

手撕OkHttp

手撕ButterKnife

HandlerThread那些事兒

手撕AsyncTask

相關文章
相關標籤/搜索