手撕Handler

前言

在日常開發(fā)中,我們勢(shì)必會(huì)使用到子線程和UI線程的通信,而起著橋梁作用的就是我們常用的Handler。但是他的內(nèi)部是怎么運(yùn)作的?運(yùn)作的過程中存在什么問題?需要我們注意,本文將會(huì)詳細(xì)講解。

解析Handler

從圖中我們就可以知道了,整個(gè)Handler工作組成的包括了HandlerLooper、MessageQueue、Message這四個(gè)部分。

MessageQueue和Message分別只是一個(gè)隊(duì)列和消息實(shí)體類,自然不再多說。
而Handler和Looper的具體是怎樣的呢?

在我的模擬Handler項(xiàng)目中,已經(jīng)比較清晰的闡述了整個(gè)框架的工作流程,接下里就是結(jié)合SDK代碼的一份解析了。

整個(gè)Handler往簡(jiǎn)單了來說其實(shí)就干了兩件事情:

  • 發(fā)送消息
  • 處理消息

發(fā)送消息

涉及到的三個(gè)函數(shù)sendMessage()、enqueueMessage()Looper.prepareMainLooper()。

所有事情的起源要從Looper.prepareMainLooper()開始講起。
這個(gè)函數(shù)處于ActivityThread中,沒有了解過這個(gè)類的讀者們需要知道,java編程一定是有一個(gè)主入口的,但是我們?cè)谡麄€(gè)Android編程中,從來沒有涉及過main()這個(gè)函數(shù),是因?yàn)樗呀?jīng)包含在了ActivityThread這個(gè)類中,而它已經(jīng)經(jīng)過了復(fù)雜的封裝。

接下來看下這個(gè)Looper.prepareMainLooper ()函數(shù)。

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對(duì)應(yīng)的函數(shù)
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對(duì)應(yīng)的函數(shù)
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

兩小段代碼,里面用到了一個(gè)變量sThreadLocal,這個(gè)變量是使用static final修飾的,意味這全局唯一。從他主動(dòng)拋出的異常我們也可以看出Looper這個(gè)對(duì)象也是一個(gè)唯一的變量。這是我們需要掌握的第一個(gè)知識(shí)點(diǎn)。

接下來是關(guān)于sendMessage()函數(shù)
這個(gè)函數(shù)其實(shí)是一個(gè)泛稱,他并不單單指sendMessage(),他還可以是sendMessageAtTime()、sendMessageDelayed(),他們都干了一件事情——傳遞消息。通過一直向下探索,你就能知道他們最后調(diào)用的都是enqueueMessage()這個(gè)函數(shù),也就是把消息放進(jìn)了消息隊(duì)列中。

沒有很多的操作,就是我們熟悉的鏈表操作。這里沒有做展示,有興趣的朋友進(jìn)到源碼往下翻一點(diǎn),馬上就能看到了。

就這樣很簡(jiǎn)單,并且很成功的讓我們的消息進(jìn)入了消息隊(duì)列。

處理消息

接收完消息,我們要干嘛?我們?yōu)槭裁匆l(fā)消息,因?yàn)槲覀円幚戆 ?/p>

這里我們要遇到的函數(shù)有:Looper.loop()、dispatchMessage()、handleMessage()
用過Handler的讀者們都應(yīng)該知道我們是需要重寫handleMessage()這個(gè)函數(shù)的,用于對(duì)不同的消息作出響應(yīng),所以就不再多介紹。
所以第一個(gè)講的就是Looper.loop()這個(gè)函數(shù)。

Looper源碼截圖1

Looper源碼截圖2

一共兩段代碼,也是至關(guān)重要的一部分。
在這個(gè)代碼中兩個(gè)至關(guān)重要的點(diǎn):
(1)首先是問題是這么一個(gè)死循環(huán)的函數(shù),怎么就沒引發(fā)ANR呢????
(2)通過dispatchMessage()如何分發(fā)這個(gè)消息的?target是什么?

先是第一個(gè)問題的解答。
網(wǎng)上的解答多種多樣。但是最關(guān)鍵的點(diǎn)其實(shí)是這樣的,ANR是圍繞loop()這個(gè)函數(shù)展開的,而ANR的出現(xiàn)也就是loop()的消息沒有得到及時(shí)的消費(fèi)。

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

原來target就是一個(gè)Handler,而這個(gè)Handler就是我們對(duì)應(yīng)的主動(dòng)創(chuàng)建Handler。
然后就是dispatchMessage()函數(shù)了。

    /**
     * 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);
        }
    }

想來這就很清楚了,他也就是將事件分發(fā)給了handleMessage()處理,而handleMessage()又是我們自己來專門寫的。msg.callback是一個(gè)Runnable對(duì)象。

害,原來就是這樣啊。。

思考

  1. Handler的內(nèi)存泄漏實(shí)例。
  2. 為什么Handler不能在子線程創(chuàng)建?
  3. 為什么Handler構(gòu)造方法里面的Looper不是new出來的?

問題1:Handler的內(nèi)存泄漏實(shí)例。

    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,這就是一段會(huì)內(nèi)存泄露的代碼,看了代碼,應(yīng)該也清楚邏輯十分簡(jiǎn)單,就是一個(gè)跳轉(zhuǎn)。推出程序后,你仍然會(huì)看到跳轉(zhuǎn),這就是Handler的內(nèi)存泄漏。

問題2: 為什么Handler不能在子線程創(chuàng)建?
這個(gè)問題其實(shí)有點(diǎn)問題,對(duì)于修改過底層的華為的操作系統(tǒng)并不存在這樣的問題,但是正常的Android原生系統(tǒng)就不行了。
代碼如下

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報(bào)錯(cuò)

通過對(duì)報(bào)錯(cuò)溯源,我們就能發(fā)現(xiàn)這樣一個(gè)問題。


他拿不到Looper,因?yàn)樗皇荱I線程。
其實(shí)這就是問題所在,我們上文講過sThreadLocal這個(gè)變量,他通過一個(gè)get()函數(shù)獲取了Looper。但是這里存在一個(gè)問題,這個(gè)get(),他獲取的是什么。
所以我們也就進(jìn)去看看好了。

    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();
    }

原來,他的取法就是從一個(gè)Map中進(jìn)行獲取的,而key,就是當(dāng)前線程,所以當(dāng)我們?cè)谧泳€程中創(chuàng)建Handler的時(shí)候,我們也是照樣拿不到Looper的,這個(gè)時(shí)候我們同樣明白了Looper也是一個(gè)唯一的,因?yàn)樗粫?huì)為我們創(chuàng)建出來的一個(gè)子線程再添加一個(gè)Looper,而是共用。

就這個(gè)問題,Google其實(shí)有給出解決方案,詳細(xì)請(qǐng)看 》》HandlerThread那些事兒

問題3:為什么Handler構(gòu)造方法里面的Looper不是new出來的?
這個(gè)問題的性質(zhì)和問題2有點(diǎn)類似了,唯一性。Looper作為一個(gè)事件處理的重要組成部分,想來我們已經(jīng)看到了,就像多道程序設(shè)計(jì)技術(shù)一樣,這是一個(gè)不受控制的過程,我們需要瘋狂的思考安全性,同步性等問題。這也是唯一性的好處,所以事件統(tǒng)一處理,處理起來也就有序。至少在我們的平時(shí)使用中已經(jīng)證明了這是一個(gè)可取的方法。

以上就是我的學(xué)習(xí)成果,如果有什么我沒有思考到的地方或是文章內(nèi)存在錯(cuò)誤,歡迎與我分享。


相關(guān)文章推薦:
手撕OkHttp
手撕AsyncTask
手撕ButterKnife
HandlerThread那些事兒

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容