Android Handler消息機(jī)制

導(dǎo)讀

由于我更新優(yōu)化本篇文章中的筆誤之處,導(dǎo)致文章莫名被刪除,故此重新發(fā)布!

Android Handler消息機(jī)制

為了避免ANR,我們通常會把耗時操作放在子線程里面去執(zhí)行,因?yàn)樽泳€程不能更新UI,所以當(dāng)子線程需要更新UI的時候就需要借助到Andriod的消息機(jī)制,也就是Handler機(jī)制了。那么其原理是什么呢?下面我們根據(jù)平時的使用一步一步來解讀源碼。
以上內(nèi)容可能會產(chǎn)生的問題:

  • 什么是ANR?

    在Android上,如果你的應(yīng)用程序有一段時間無法響應(yīng),系統(tǒng)會向用戶顯示一個對話框,這個對話框稱作應(yīng)用程序無響應(yīng)(ANR:Application Not Responding)對話框。用戶可以選擇讓程序繼續(xù)運(yùn)行,但是,他們在使用你的應(yīng)用程序時,并不希望每次都要處理這個對話框。因此,在程序里對響應(yīng)性能的設(shè)計(jì)很重要,這樣,系統(tǒng)不會顯示ANR給用戶。

  • 耗時操作為什么不能再主線程?
  • 為什么子線程不能更新UI?
  • Handler機(jī)制是什么?

一. Handler的常見用法。

  1. 創(chuàng)建handler對象并重寫handleMessage方法,以便于處理自己需要的邏輯。
private Handler handler = new Handler(){
  @Override
  public void handleMessage(Message msg){
    ...
  }
}
  1. 使用handler 對象發(fā)送消息(對象、載體)。
private void sendMsg(){
  Mssages msg = handler.obtainMessage();
  msg.what=what;
  msg.age1=age1;
  handler.sendMessageDelayed(msg,1000);
}
...

二. 代碼分析

  1. 以上代碼可以看出,在主線程創(chuàng)建了一個handler對象,并重構(gòu)了handleMessage方法,然后通過handler發(fā)送消息對象,中間經(jīng)過一系列處理后,handleMessage方法接收到傳遞的數(shù)據(jù),就可以處理具體的邏輯了(如更新UI)。
  2. 以上代碼可以看出,發(fā)送消息、最后處理數(shù)據(jù)時都使用的是Message對象,這是在源碼設(shè)計(jì)層面時約定的媒介(載體、中間件、快遞),這里先不進(jìn)行展開,只做一個簡單的介紹:

what字段作為標(biāo)識,
arg1/arg2字段作為簡單類型的參數(shù),
obj字段作為復(fù)雜對象,以及Bundle常見參數(shù)
target字段作為handler對象標(biāo)識

  1. 再來看發(fā)送消息 sendMessageDelayed,通過源碼可以發(fā)現(xiàn)所有的sendMessage方法執(zhí)行后,最終都會走sendMessageAtTime()方法(這里就不貼上具體源碼了)。
   public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        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);
    }

在這個sendMessageAtTime方法里會拿到一個MessageQueue的實(shí)例對象,并觸發(fā)enqueueMessage(queue, msg, uptimeMillis)方法。那我們就先看看enqueueMessage方法,然后在去看mQueue怎么來的。

      boolean enqueueMessage(Message msg, long when) {
        ...
        Message p = mMessages;
        synchronized (this) {
         if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
            ...  
            Message p = mMessages;
            ...
            msg.next = p; // invariant: p == prev.next
            ...
        }
      }
        return true;
    }

首先這是一個被synchronized修飾的同步代碼塊,確保了并發(fā)問題;核心邏輯是對Message的實(shí)例對象p進(jìn)行非空判斷,最終兩個兩個邏輯里都會把msg放到Message實(shí)例對象p的next方法中進(jìn)行排隊(duì)。

4.現(xiàn)在回來研究這個mQueue對象是怎么來的呢,通過command+f(ctrl+f)搜索,最終發(fā)現(xiàn)是Handler構(gòu)造方法中獲取的(通過代碼可以看出mQueue的獲取是通過looper.mQueue,而我們前面在創(chuàng)建handler的時候使用的是無參構(gòu)造的,那么這個Looper對象的實(shí)例額mLooper是怎么來的?

 @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

4.1 觀察Handler的其他重載的構(gòu)造方法發(fā)現(xiàn),我們的無參構(gòu)造最后會走如下構(gòu)造,而這個mLooper實(shí)例對象是通過Looper.myLooper()方法獲取的而且還不能為空!由此可以看出Looper、MessageQueue都是非常重要的對象。

   public Handler(@Nullable Callback callback, boolean async) {
       ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
    }

4.2 繼續(xù)跟蹤源碼,進(jìn)入Looper類發(fā)現(xiàn)myLooper()的內(nèi)部是通過sThreadLocal.get()獲取的,那么有g(shù)et()自然是有set()的,所以我們command+f(ctrl+f)搜索找到了prepare()方法,而這個方法會被prepareMainLooper的方法執(zhí)行,prepare翻譯過來是準(zhǔn)備的意思,prepareMainLooper就是準(zhǔn)備主Looper的意思了,這里要注意有synchronized修飾噢。

   ...
   public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
   ...
   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));
    }
   ...
   public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

4.3 到這里,我們知道了Handler無參構(gòu)造的時候其實(shí)是使用了一個叫MainLooper及MessageQueue的對象,那這個MainLooper具體又是在哪里創(chuàng)建的呢,那就查查這個prepareMainLooper()方法在哪里被調(diào)用的使用command+shift+f進(jìn)行全局搜索:


WX20200201-115959@2x.png

搜索出來4個結(jié)果,除了Looper.java外,SystemServer.java是系統(tǒng)級別的這里不做深入,后續(xù)新增該類的解讀; Bridge.java是橋梁類,注釋中寫著“Main entry point of the LayoutLib Bridge.”,這里也不做深入;最后是ActivityThread.java類,也是這里需要重點(diǎn)關(guān)注的類:

public final class ActivityThread extends ClientTransactionHandler {
  ...
   public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        Looper.loop();
        ...
    }
  ...
}

有兩個重大發(fā)現(xiàn):
1、Looper.prepareMainLooper()是在一個叫ActivityThread 的 final 類型的Java類,的main(String[] args) 方法中執(zhí)行的。
2、還執(zhí)行了Looper.loop();
main是Java的入口方法,說明什么呢,在整個(應(yīng)用)代碼里L(fēng)ooper.prepareMainLooper()是最先執(zhí)行的代碼之一,也就是說Handler所使用的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;
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            msg.target.dispatchMessage(msg);
            ...
            msg.recycleUnchecked();
        }
    }

這里可以看出幾個重要消息
1、Looper、MessageQueue是不可缺少的對象
2、使用到了for (;;) ,就是說無限循環(huán),一直去MessageQueue的next()去查有沒有Message對象,
3、有Message對象時會執(zhí)行msg.target.dispatchMessage(msg);方法
當(dāng)然也會有2個困惑的問題
3.1、for (;;) 這個無限循環(huán)不會讓APP卡死嗎?
3.2、 有一行代碼if (msg == null) return 其注釋是的意思是是阻塞,return之后不是繼續(xù)for循環(huán)嗎?

4.4 繼續(xù)跟蹤 msg.target.dispatchMessage(msg)方法,前面有提到target其實(shí)就是Handler(為什么呢,大家有興趣可以看看源碼)也就是說dispatchMessage方法是Handler類下的

   public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

發(fā)現(xiàn)這里調(diào)用了一個很眼熟的方法名handleMessage 沒錯,這就是一開始我們重寫的的handleMessage方法,至此,腦海中是否已經(jīng)有了一個Handle整體輪廓圖?是否還有一個疑問,Handler啥時候把線程給切換了?

總結(jié)

1.APP在啟動的時候,最先啟動ActivityThread類的main方法,其中Looper的初始化工作和準(zhǔn)備工作就是這時候執(zhí)行的(獲得Looper對象、MessageQueue)
2.Looper開啟loop()方法(永動機(jī))去死循環(huán)遍歷MessageQueue的消息隊(duì)列(至于為什么沒有卡死前面有提到?有提到嗎,為什么我修改的時候發(fā)現(xiàn)沒有,捂臉)
3.在通常用法中創(chuàng)建Handler對象時,構(gòu)造方法會拿到looper、mQueue對象
4.使用Handler對象發(fā)送消息(sendXxx())
5.把消息加入隊(duì)列(mQueue.enqueueMessage)
6.第2步的永動機(jī)讀取MessageQueue的消息
7.執(zhí)行dispatchMessage方法
8.執(zhí)行代碼中創(chuàng)建Handler方法時重寫的handleMessage方法完成Handler機(jī)制的整個過程。

最后,讓我們帶著文章中的問題,進(jìn)行繼續(xù)深入:Android中為什么主線程不會因?yàn)長ooper.loop里的無限循環(huán)ANR?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容