"一篇就夠"系列: Handler消息機(jī)制完全解析

前言

Handler系列文章共兩篇:

第一篇:"一篇就夠"系列: Handler消息機(jī)制完全解析

第二篇: "一篇就夠"系列: Handler擴(kuò)展篇

關(guān)于Handler,想必大家都已經(jīng)非常熟悉了,它是Android中非常基礎(chǔ),但同時(shí)也極其重要的消息機(jī)制,說(shuō)它基礎(chǔ),是因?yàn)樗褂煤?jiǎn)單,在我們一開始學(xué)習(xí)Android時(shí),就會(huì)接觸到Handler,用它來(lái)進(jìn)行線程間的通信。說(shuō)它極其重要,是因?yàn)樗贏ndroid系統(tǒng)中扮演了一個(gè)極其核心的角色,可以說(shuō)只要有異步通信的地方就一定會(huì)有Handler,正是因?yàn)樗拇嬖冢沟梦覀傾ndroid系統(tǒng)中的很多組件能夠正常的運(yùn)行

注意:本文所展示的系統(tǒng)源碼都是基于Android-29 ,并提取核心部分進(jìn)行分析

問(wèn)題

下面我在這里拋出一些問(wèn)題,如果你都知道,那么恭喜你,你對(duì)Handler機(jī)制掌握的很透徹,如果你對(duì)下面這些問(wèn)題有一些疑惑,那么你就可以接著往下看,我會(huì)由淺入深的給你講解Handler機(jī)制,看完之后,這些問(wèn)題你就都會(huì)非常的明了,同時(shí)在最后我也會(huì)對(duì)這些問(wèn)題給出自己的回答

  1. Handler有哪些作用?
  2. 為什么我們能在主線程直接使用Handler,而不需要?jiǎng)?chuàng)建Looper?
  3. 如果想要在子線程創(chuàng)建Handler,需要做什么準(zhǔn)備?
  4. 一個(gè)線程有幾個(gè)Handler?
  5. 一個(gè)線程有幾個(gè)Looper?如何保證?
  6. 為什么Lopper死循環(huán),卻不會(huì)導(dǎo)致應(yīng)用卡死?
  7. Handler內(nèi)存泄露原因? 如何解決?
  8. 線程維護(hù)的Looper,在消息隊(duì)列無(wú)消息時(shí)的處理方案是什么?有什么用?
  9. 我們可以使用多個(gè)Handler往消息隊(duì)列中添加數(shù)據(jù),那么可能存在發(fā)消息的Handler存在不同的線程,那么Handler是如何保證MessageQueue并發(fā)訪問(wèn)安全的呢?
  10. Handler是如何進(jìn)行線程切換的呢?
  11. 我們?cè)谑褂肕essage的時(shí)候,應(yīng)該如何去創(chuàng)建它?
  12. Handler里面藏著的CallBack能做什么?
  13. Handler阻塞喚醒機(jī)制是怎么一回事?
  14. 什么是Handler的同步屏障?
  15. 能不能讓一個(gè)Message被加急處理?

什么是Handler?

我們通常所說(shuō)的Handler,他其實(shí)是Handler機(jī)制中的一個(gè)角色,只不過(guò)我們對(duì)Handler接觸比較多,因此用Handler來(lái)代稱

Handler機(jī)制是Android中基于單線消息隊(duì)列模式的一套線程消息機(jī)制。

Handler基本用法

//在主線程創(chuàng)建Handler實(shí)例
private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            //處理接收的消息
        }
    };

//在適當(dāng)?shù)臅r(shí)機(jī)使用Handler實(shí)例發(fā)送消息
mHandler.sendMessage(message);
mHandler.post(runnable);//Runnable會(huì)被封裝進(jìn)一個(gè)Message,所以它本質(zhì)上還是一個(gè)Message

看上面這段代碼,創(chuàng)建了一個(gè)Handler實(shí)例并重寫了 handleMessage 方法 ,然后在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用它的 send 或者 post 系列方法就可以了,使用就是這么簡(jiǎn)單

那么問(wèn)題來(lái)了,它們是如何進(jìn)行線程間的通信的呢? 下面我們就需要對(duì)源碼進(jìn)行分析

Handler機(jī)制源碼分析

在分析源碼之前,我先講下Handler機(jī)制涉及的幾大角色: Handler,Looper,MessageQueue,Message

先提前介紹下這幾個(gè)角色的作用,便于后續(xù)分析源碼的一個(gè)理解

Handler: 發(fā)送消息和處理消息

Looper: 從MessageQueue中獲取Message,然后交給Handler處理

MessageQueue: 消息隊(duì)列,存放Handler發(fā)送過(guò)來(lái)的消息

Message: 消息的載體

下面我們開始進(jìn)行源碼分析,在我們一開始使用的時(shí)候,創(chuàng)建了一個(gè)Handler實(shí)例,那我們看下它實(shí)例化的這個(gè)構(gòu)造方法:

public Handler() {
     this(null, false);
}

它其實(shí)是調(diào)用了它的一個(gè)重載的方法,接著看它的重載方法

注意:

  1. Handler的構(gòu)造方法中還可以傳入Looper,通過(guò)傳入Looper的構(gòu)造方法可以實(shí)現(xiàn)一些特殊的功能
  2. Handler的構(gòu)造方法中還可以傳入Callback,這種方式創(chuàng)建一個(gè)Handler的實(shí)例,它并不需要派生出一個(gè)子類,后面我也會(huì)介紹到
  3. 有些構(gòu)造方法使用了@UnsupportedAppUsage注解,表示不支持外部應(yīng)用調(diào)用
public Handler(@Nullable Callback callback, boolean async) {
    //...
    //獲取當(dāng)前線程的Lopper
    mLooper = Looper.myLooper();
    //如果當(dāng)前Looper為空,則拋出異常
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    //將當(dāng)前Lopper中的MessageQueue賦值給Handler中的MessageQueue
    mQueue = mLooper.mQueue;
    //...
 }

//---------------------以下為額外擴(kuò)展內(nèi)容--------------------------
//傳入Looper的構(gòu)造方法
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

//傳入Callback的構(gòu)造方法
public Handler(@Nullable Callback callback) {
    this(callback, false);
}

//使用了@UnsupportedAppUsage注解的構(gòu)造方法
@UnsupportedAppUsage
public Handler(boolean async) {
    this(null, async);
}

上面這段代碼注釋寫的很清楚,那我們是不是就可以得出一個(gè)結(jié)論: 我們?cè)趧?chuàng)建Handler實(shí)例的時(shí)候,一定要先創(chuàng)建一個(gè)Lopper,并開啟循環(huán)讀取消息,那么大家肯定有個(gè)疑問(wèn)? 你上面的使用就沒(méi)有創(chuàng)建Lopper,那是因?yàn)槲覀兊闹骶€程已經(jīng)給我們創(chuàng)建了一個(gè)Lopper

接下來(lái)我們找下主線程的這個(gè)Lopper是在哪里創(chuàng)建的,我們找到ActivityThread的main()方法

public static void main(String[] args) {
  //...
    //創(chuàng)建Lopper
  Looper.prepareMainLooper();
  
  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  //...
  //開啟循環(huán)讀取消息
  Looper.loop();
    //Looper如果因異常原因停止循環(huán)則拋異常
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

注意:通常我們認(rèn)為 ActivityThread 就是主線程。事實(shí)上它并不是一個(gè)線程,而是主線程操作的管理者,所以們把 ActivityThread 認(rèn)為就是主線程無(wú)可厚非,另外主線程也可以說(shuō)成 UI 線程。

我們?cè)?ActivityThread里的main方法里調(diào)用了Looper.prepareMainLooper() 方法創(chuàng)建了主線程的Looper ,并且調(diào)用了loop方法,所以我們就可以直接使用 Handler

繼續(xù)分析,我們知道m(xù)ain()方法是Java程序的入口,同時(shí)也是Android應(yīng)用程序的入口,而在Java中,我們執(zhí)行完main()方法,馬上就退出了,而在Android中,為啥沒(méi)有退出呢?這里我們做個(gè)假設(shè),如果在Android中也退出了,那么是不是Android就沒(méi)得玩了,所以Google肯定是不能讓他退出的,之所以在Android中沒(méi)有退出,正是因?yàn)槲覀冊(cè)谶@里創(chuàng)建并開啟了Looper死循環(huán),他會(huì)循環(huán)執(zhí)行各種事物。Looper死循環(huán)說(shuō)明線程沒(méi)有死亡,如果Looper停止循環(huán),線程則結(jié)束退出了

那么大家是不是又會(huì)有個(gè)疑問(wèn)?既然是一個(gè)死循環(huán),那為啥不會(huì)造成ANR?

其實(shí)Lopper死循環(huán)和程序ANR沒(méi)有任何關(guān)系,這里感覺(jué)就是在進(jìn)行一個(gè)概念的混淆,這里我解釋一下這兩個(gè)概念

ANR: 全稱Applicationn Not Responding,中文意思是應(yīng)用無(wú)響應(yīng),當(dāng)我發(fā)送一個(gè)消息到主線程,經(jīng)過(guò)一定時(shí)間沒(méi)有被執(zhí)行,那么這個(gè)時(shí)候就會(huì)拋出ANR異常

Lopper死循環(huán): 循環(huán)執(zhí)行各種事務(wù),當(dāng)Looper處理完所有消息的時(shí)候會(huì)進(jìn)入阻塞狀態(tài),當(dāng)有新的Message進(jìn)來(lái)的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行

到了這里,相信大家對(duì)于創(chuàng)建Handler已經(jīng)很明了了,下面我們來(lái)實(shí)際應(yīng)用一下,在子線程創(chuàng)建Handler,直接上代碼:

public static class MyThread extends Thread {
    public Handler mHandler;
 
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(@NonNull Message msg) {
                                        //處理接收的消息
                }
            };
            Looper.loop();
        }
}

好,到了這里,我們應(yīng)該對(duì)創(chuàng)建Handler實(shí)例的時(shí)候,一定要先創(chuàng)建一個(gè)Lopper,并開啟循環(huán)讀取消息,有了深刻的理解,我們繼續(xù)分析源碼

上面說(shuō)了創(chuàng)建Handler實(shí)例之前要先創(chuàng)建Looper并開啟循環(huán),那我們分析下創(chuàng)建Lopper并開啟循環(huán)這個(gè)過(guò)程,先看下ActivityThread里的main方法里調(diào)用的Looper.prepareMainLooper()

public static void prepareMainLooper() {
    //創(chuàng)建Looper,參數(shù)false表示該Looper不能退出
    prepare(false);
    //添加同步鎖
    synchronized (Looper.class) {
        //如果當(dāng)前sMainLooper已經(jīng)存在,則拋異常
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //將當(dāng)前線程的Looper實(shí)例賦值給sMainLooper
        sMainLooper = myLooper();
    }
}

實(shí)際上主要是調(diào)用了另外兩個(gè)方法,我們?cè)诳聪聀repare(false)和myLooper()方法的內(nèi)部實(shí)現(xiàn)

private static void prepare(boolean quitAllowed) {
     //通過(guò)sThreadLocal獲取當(dāng)前Looper實(shí)例,如果當(dāng)前Lopper實(shí)例不為空則拋出異常
     if (sThreadLocal.get() != null) {
         throw new RuntimeException("Only one Looper may be created per thread");
     }
     //將new出來(lái)的Looper實(shí)例設(shè)置給sThreadLocal
     sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
     //通過(guò)sThreadLocal獲取Looper實(shí)例對(duì)象
     return sThreadLocal.get();
}

prepare方法: new一個(gè)Looper設(shè)置給sThreadLocal. myLooper方法: 通過(guò)sThreadLocal獲取Looper. 上面兩個(gè)方法,大家是不是會(huì)對(duì)這個(gè)sThreadLocal很好奇,這個(gè)東西有啥作用,我們根據(jù)上面兩個(gè)方法可以推斷出: sThreadLocal是用來(lái)存放Looper的

ThreadLocal介紹

ThreadLocal是Java中一個(gè)用于線程內(nèi)部存儲(chǔ)數(shù)據(jù)的工具類

看下面這一段代碼

 public static void main(String[] args) {
     ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
     threadLocal.set(true);
     Boolean aBoolean = threadLocal.get();
     System.out.println("Current Thread " + Thread.currentThread().getName() + ": " + aBoolean);
         //創(chuàng)建一個(gè)新的線程命名為a
     new Thread("a"){
         @Override
          public void run() {
              threadLocal.set(false);
              Boolean bBoolean = threadLocal.get();
              System.out.println("Current Thread " + Thread.currentThread().getName() + ": " + bBoolean);
          }
     }.start();
         //創(chuàng)建一個(gè)新的線程命名為b
     new Thread("b"){
         @Override
         public void run() { ;
             Boolean cBoolean = threadLocal.get();
             System.out.println("Current Thread " + Thread.currentThread().getName() + ": " + cBoolean);
         }
     }.start();
 }

//打印結(jié)果:
Current Thread main: true
Current Thread a: false
Current Thread b: null

上面這段代碼:

  1. 在主線程創(chuàng)建了一個(gè)threadLocal變量,并調(diào)用set方法設(shè)置為true,然后獲取該值并打印
  2. 創(chuàng)建一個(gè)新的線程,并調(diào)用set方法設(shè)置值為false,獲取獲取該值并打印
  3. 創(chuàng)建一個(gè)新的線程,獲取該值并打印

從上面的日志可以看出,雖然在不同的線程中訪問(wèn)同一個(gè)threadLocal對(duì)象,但是它們通過(guò)ThreadLocal獲取的值卻是不一樣的,這就是ThreadLocal的奇妙之處,這里我又想問(wèn)一句,為什么? 凡事多問(wèn)幾個(gè)為什么,知識(shí)原理就學(xué)到手了,哈哈??,我們點(diǎn)進(jìn)去ThreadLocal的set方法看一下

public void set(T value) {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //獲取當(dāng)前線程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //如果map不為空,則將當(dāng)前的ThreadLocal變量作為key,傳進(jìn)來(lái)的泛型作為value進(jìn)行存儲(chǔ)
        map.set(this, value);
    else
        //如果map為空,則會(huì)創(chuàng)建map,將當(dāng)前的ThreadLocal變量作為key,傳進(jìn)來(lái)的泛型作為value進(jìn)行存儲(chǔ)
        createMap(t, value);
}

通過(guò)上面代碼我們知道,通過(guò)獲取當(dāng)前線程的ThreadLocalMap,在把ThreadLocal變量作為key,傳進(jìn)來(lái)的泛型作為value進(jìn)行存儲(chǔ)

ThreadLocalMap它是ThreadLocal里面的一個(gè)靜態(tài)內(nèi)部類,它類似于一個(gè)改版的HashMap,內(nèi)部也是使用數(shù)組和Hash算法來(lái)存儲(chǔ)數(shù)據(jù),使得存儲(chǔ)和讀取的速度非???,因此這里我們使用HashMap的思想去理解ThreadLocalMap就好了,如果對(duì)ThreadLocalMap工作原理感興趣的,可以閱讀這篇文章傳送門

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

獲取當(dāng)前線程的ThreadLocalMap,前面講到ThreadLocalMap其實(shí)非常像一個(gè)HashMap,他的get方法也是一樣的,使用ThreadLocal作為key獲取到對(duì)應(yīng)的Entry,再把value返回即可,如果map尚未初始化則會(huì)執(zhí)行初始化操作

因此我們是否可以得到結(jié)論:

image-20210201082013061

ThreadLocal會(huì)從各自的線程,取出自己維護(hù)的ThreadLocalMap,其key為ThreadLocal,value為ThreadLocal對(duì)應(yīng)的泛型對(duì)象,這樣每個(gè)ThreadLocal就可以把自己作為key把不同的value存儲(chǔ)在不同的ThreadLocalMap,當(dāng)獲取數(shù)據(jù)的時(shí)候,同個(gè)ThreadLocal就可以從不同線程的ThreadLocalMap中得到不同的數(shù)據(jù)。因此當(dāng)我們以線程作為作用域,并且不同線程需要具有不同數(shù)據(jù)副本的時(shí)候,我們就可以考慮使用ThreadLocal。而Looper正好適用于這種場(chǎng)景

Looper介紹

上面我們分析到Looper使用ThreadLocal來(lái)保證每個(gè)線程有且只有一個(gè)相同的副本,因此我們可以得出結(jié)論: 一個(gè)線程對(duì)應(yīng)一個(gè)Looper,這個(gè)結(jié)論非常的重要,Handler機(jī)制之所以能夠?qū)崿F(xiàn)線程之間的通信,就是因?yàn)槭褂昧瞬煌€程的Looper處理消息,舉個(gè)例子: 我在線程A創(chuàng)建了幾個(gè)Hanlder實(shí)例處理消息,那我首先就要?jiǎng)?chuàng)建A線程的Looper并開啟消息循環(huán),那么我不管你這些Hanlder的實(shí)例從那個(gè)線程發(fā)送消息過(guò)來(lái),最終都會(huì)回到我A線程的MessageQueue中,然后通過(guò)A線程Looper不斷讀取消息,在交給當(dāng)前A線程的Handler來(lái)處理

Looper可以說(shuō)是Handler機(jī)制中的一個(gè)非常重要的核心。Looper相當(dāng)于線程消息機(jī)制的引擎,驅(qū)動(dòng)整個(gè)消息機(jī)制運(yùn)行。Looper負(fù)責(zé)從隊(duì)列中取出消息,然后交給對(duì)應(yīng)的Handler去處理。如果隊(duì)列中沒(méi)有消息,則MessageQueue的next方法會(huì)阻塞線程,等待新的消息的到來(lái)。每個(gè)線程有且只能有一個(gè)“引擎”,也就是Looper,如果沒(méi)有Looper,那么消息機(jī)制就運(yùn)行不起來(lái),而如果有多個(gè)Looper,則會(huì)違背單線操作的概念,造成并發(fā)操作。

Looper創(chuàng)建

在上面創(chuàng)建Looper的時(shí)候我們分析到:

  1. 主線程ActivityThread創(chuàng)建Looper,使用的是prepareMainLooper方法,它是為主線程量身定做的,由于主線程的Looper比較特殊,所以Looper提供了一個(gè)getMainLooper方法,通過(guò)這個(gè)方法我們可以在任何地方獲取到主線程的Looper,且主線程的Looper不能退出

  2. 我們自己創(chuàng)建的Looper,使用的是prepare方法,實(shí)質(zhì)上它們最終都會(huì)調(diào)到prepare(boolean quitAllowed)這個(gè)方法,這個(gè)方法是私有的,外部不能直接調(diào)用,區(qū)別就是主線程創(chuàng)建的Looper不能退出,而我們自己創(chuàng)建的可以退出

//主線程
public static void prepareMainLooper() {
    prepare(false);
    //...
}

//獲取主線程Looper
public static Looper getMainLooper() {
     synchronized (Looper.class) {
     return sMainLooper;
     }
 }

//我們自己創(chuàng)建Looper
public static void prepare() {
    prepare(true);
}

//參數(shù)quitAllowed true: 可退出 false: 不可退出
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));
}

到這里我又有個(gè)疑問(wèn),為啥Looper不能直接在外部給New出來(lái)呢?我們點(diǎn)擊去Looper的構(gòu)造方法看一下:

private Looper(boolean quitAllowed) {
     //創(chuàng)建一個(gè)MessageQueue,賦值給當(dāng)前Looper的mQueue
     mQueue = new MessageQueue(quitAllowed);
     //獲取當(dāng)前線程賦值給Looper的mThread
     mThread = Thread.currentThread();
}

我們發(fā)現(xiàn),他的構(gòu)造方法是私有的,原來(lái)如此。而且我們還會(huì)發(fā)現(xiàn):Looper的內(nèi)部維護(hù)了一個(gè)MessageQueue,當(dāng)初始化Looper的時(shí)候會(huì)順帶初始化這個(gè)MessageQueue

Looper開啟消息循環(huán)

當(dāng)我們的Looper創(chuàng)建好后,他是不會(huì)自己?jiǎn)?dòng)的,需要我們手動(dòng)去啟動(dòng)Looper,調(diào)用Looper的loop()方法即可,所以前面創(chuàng)建Looper的時(shí)候我總是會(huì)說(shuō),創(chuàng)建Looper并開啟消息循環(huán),Looper的prepareloop方法是配套使用的,兩者必須成對(duì)存在?,F(xiàn)在我們來(lái)重點(diǎn)分析一下Looper的loop方法,上源碼:

public static void loop() {
    // 獲取當(dāng)前線程的Looper
    final Looper me = myLooper();
    //當(dāng)前線程的Looper,直接拋異常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲取當(dāng)前Looper中的MessageQueue
    final MessageQueue queue = me.mQueue;
    //...
    //開啟死循環(huán)讀取消息
    for (;;) {
        // 獲取消息隊(duì)列中的消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            // 返回null說(shuō)明MessageQueue退出了
            return;
        }
        //...
        try {
            // 調(diào)用Message對(duì)應(yīng)的Handler處理消息
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        }
        //...
            // 回收Message
        msg.recycleUnchecked();
    }
}

loop方法就是Looper這個(gè)“引擎”的核心所在,他就像是一個(gè)開關(guān)

分析下這段代碼,首先獲取當(dāng)前線程的Looper對(duì)象,沒(méi)有則拋異常,然后進(jìn)入一個(gè)死循環(huán): 不斷調(diào)用MessageQueue的next方法來(lái)獲取消息,然后調(diào)用message的目標(biāo)handler的dispatchMessage方法來(lái)處理Message。

Looper退出

Looper提供了quitquitSafely方法來(lái)退出一個(gè)Looper,二者的區(qū)別是: quit會(huì)直接退出Looper,而quitSafely只是設(shè)定一個(gè)標(biāo)記,然后把消息隊(duì)列中的已有消息處理完畢后才安全退出.在我們手動(dòng)創(chuàng)建Looper的情況下,如果所有的消息都被處理完成后,我們應(yīng)該調(diào)用quit方法來(lái)終止消息循環(huán),否則子線程就會(huì)一直處于等待狀態(tài),而如果退出Looper,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper。

public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}

// 最終都是調(diào)用到了這個(gè)方法
void quit(boolean safe) {
    // 如果不能退出則拋出異常。這個(gè)值在初始化Looper的時(shí)候被賦值
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        // 退出一次之后就無(wú)法再次運(yùn)行了
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        // 執(zhí)行不同的方法
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        // 喚醒MessageQueue
        nativeWake(mPtr);
    }
}

quitquitSafely方法最終都調(diào)用了quit(boolean safe)這個(gè)方法,這個(gè)方法先判斷是否能退出,然后再執(zhí)行退出邏輯。如果mQuitting==true,那么這里會(huì)直接return掉,我們會(huì)發(fā)現(xiàn)mQuitting這個(gè)變量只有在這里被執(zhí)行了賦值,所以一旦looper退出,則無(wú)法再次運(yùn)行了。之后執(zhí)行不同的退出邏輯,然后喚醒MessageQueue,之后MessageQueue的next方法會(huì)退出,Looper的loop方法也會(huì)跟著退出,那么線程也就停止了。

Looper總結(jié)

Looper作為Handler消息機(jī)制的“動(dòng)力引擎”,不斷從MessageQueue中獲取消息,然后交給Handler去處理。Looper的使用前需要先初始化當(dāng)前線程的Looper對(duì)象,再調(diào)用loop方法來(lái)啟動(dòng)它。

同時(shí)Handler也是實(shí)現(xiàn)切換的核心,因?yàn)椴煌腖ooper運(yùn)行在不同的線程,他所調(diào)用的dispatchMessage方法則運(yùn)行在不同的線程,所以Message的處理就被切換到Looper所在的線程了。當(dāng)looper不再使用時(shí),可調(diào)用不同的退出方法來(lái)退出他,注意Looper一旦退出,線程則會(huì)直接結(jié)束。

Handler發(fā)送消息

Handler和Looper都創(chuàng)建好了,那么接下來(lái)我們就要使用Handler去發(fā)送消息,我們?cè)谧铋_始介紹Handler使用的時(shí)候,寫了發(fā)送的兩種消息類型,如下:

//在適當(dāng)?shù)臅r(shí)機(jī)使用Handler實(shí)例發(fā)送消息
mHandler.sendMessage(message);
mHandler.post(runnable);//Runnable會(huì)被封裝進(jìn)一個(gè)Message,所以它本質(zhì)上還是一個(gè)Message

使用Handler發(fā)送消息,它有send 或者 post等一系列方法,最終這些發(fā)送的方法會(huì)調(diào)用到Handler中的enqueueMessage()方法,而Handler中的enqueueMessage方法最終會(huì)調(diào)用到MessageQueue的enqueueMessage方法,我們通過(guò)一個(gè)發(fā)送消息方法的源碼看下,以我們最常用的sendMessage()這個(gè)方法為例:

注意post系列方法,發(fā)送的是一個(gè)Runnable,Runnable會(huì)被封裝進(jìn)一個(gè)Message,所以它本質(zhì)上還是一個(gè)Message

//1
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

//2
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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

//4
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
     //將當(dāng)前的Handler賦值給Message的target屬性
     msg.target = this;
     msg.workSourceUid = ThreadLocalWorkSource.getUid();

     if (mAsynchronous) {
         msg.setAsynchronous(true);
     }
     return queue.enqueueMessage(msg, uptimeMillis);
}

以上代碼的調(diào)用順序就是1->2->3->4

這里我給一張圖來(lái)總結(jié)一下,send 或者 post等一系列方法的調(diào)用及最終的走向:

image-20210131222442908

MessageQueue enqueueMessage方法介紹

到了這里,我們就來(lái)重點(diǎn)分析一下MessageQueue的enqueueMessage()方法,enqueueMessage中文意思是入隊(duì)消息,見(jiàn)名知意,這個(gè)方法就是把Handler發(fā)送的消息,放到消息隊(duì)列中

boolean enqueueMessage(Message msg, long when) {
    // Hanlder為空則拋異常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //當(dāng)前消息如果已經(jīng)已經(jīng)被執(zhí)行則拋異常
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    // 對(duì)MessageQueue進(jìn)行加鎖
    synchronized (this) {
        // 判斷目標(biāo)thread是否已經(jīng)死亡
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
        // 標(biāo)記Message正在被執(zhí)行,以及需要被執(zhí)行的時(shí)間
        //這里的when的值需要分情況:1,可能為0 2, 如果不為0,則是系統(tǒng)開機(jī)到現(xiàn)在的一個(gè)毫秒數(shù) + 延遲執(zhí)行的時(shí)間
        //這兩種情況主要看你調(diào)用的是Handler哪個(gè)發(fā)送Message的方法
        msg.markInUse();
        msg.when = when;
        // p是MessageQueue的鏈表頭
        Message p = mMessages;
        // 判斷是否需要喚醒MessageQueue
        boolean needWake;
        // 如果有新的隊(duì)頭,同時(shí)MessageQueue處于阻塞狀態(tài)則需要喚醒隊(duì)列
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //...
            // 根據(jù)時(shí)間找到插入的位置
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                //...
            }
            msg.next = p; 
            prev.next = msg;
        }

        // 如果需要?jiǎng)t喚醒隊(duì)列
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

上述代碼我們來(lái)總結(jié)一下:

  1. 首先判斷Message中的Handler不能不空,且不能為在使用中,否則拋異常

  2. 對(duì)MessageQueue進(jìn)行加鎖,判斷當(dāng)前線程是否dead,如果dead則打印一個(gè)異常,并返回false

  3. 初始化Message的執(zhí)行時(shí)間以并且標(biāo)記為正在執(zhí)行中

  4. 當(dāng)新插入的Message在鏈表頭時(shí),如果messageQueue是空的或者正在等待下個(gè)延遲消息,則需要喚醒MessageQueue

  5. 根據(jù)Message的執(zhí)行時(shí)間,找到在鏈表中的插入位置進(jìn)行插入,這里我們可以理解MessageQueue中維護(hù)了一個(gè)優(yōu)先級(jí)隊(duì)列,

    優(yōu)先級(jí)隊(duì)列就是鏈表根據(jù)時(shí)間進(jìn)行排序并加入隊(duì)列的數(shù)據(jù)結(jié)構(gòu)形成的,例如我們發(fā)送的幾個(gè)消息攜帶的時(shí)間分別為:1s,20ms,3s,那么這個(gè)時(shí)候就會(huì)根據(jù)時(shí)間進(jìn)行排序?yàn)椋?0ms,1s,3s, 那么如果我新加入的一個(gè)消息的時(shí)間為2s,那么他就會(huì)插入1s和3s的中間,此時(shí)這個(gè)優(yōu)先級(jí)隊(duì)列就有了4個(gè)元素: 20ms,1s,2s,3s

MessageQueue next方法介紹

到這里,Handler發(fā)送的消息已經(jīng)放到了MessageQueue中,那接著肯定就要進(jìn)行消息的讀取,我們剛講到Looper的Loop方法會(huì)從MessageQueue中循環(huán)讀取消息,loop方法中調(diào)用queue.next()的地方有句源碼注釋:might block,中文意思是可能被阻塞,如下:

public static void loop() {
    //獲取當(dāng)前Looper中的MessageQueue
    final MessageQueue queue = me.mQueue;
    //...
    //開啟死循環(huán)讀取消息
    for (;;) {
      // 獲取消息隊(duì)列中的消息
        Message msg = queue.next(); // might block
    }
    //...
}

我們就看下MessageQueue的next方法到底做了什么:

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // 源碼中的注釋表示:如果looper已經(jīng)退出了,這里就返回null
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    //...
    // 定義阻塞時(shí)間賦值為0
    int nextPollTimeoutMillis = 0;
    //死循環(huán)
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // 阻塞對(duì)應(yīng)時(shí)間 這個(gè)方法最終會(huì)調(diào)用到linux的epoll機(jī)制
        nativePollOnce(ptr, nextPollTimeoutMillis);
            // 對(duì)MessageQueue進(jìn)行加鎖,保證線程安全
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //...
            if (msg != null) {
                if (now < msg.when) {
                    // 下一個(gè)消息還沒(méi)開始,等待兩者的時(shí)間差
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 獲得消息且現(xiàn)在要執(zhí)行,標(biāo)記MessageQueue為非阻塞
                    mBlocked = false;
                    // 鏈表操作
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 沒(méi)有消息,進(jìn)入阻塞狀態(tài)
                nextPollTimeoutMillis = -1;
            }
            //退出
            if (mQuitting) {
                 dispose();
                 return null;
             }
            //...涉及了同步屏障和IdleHandler,后續(xù)在分析
    }
}

從上面代碼我們發(fā)現(xiàn)next方法目的是獲取MessageQueue中的一個(gè)Message,它里面有一個(gè)死循環(huán),如果消息隊(duì)列中沒(méi)有消息,那么next方法會(huì)一直阻塞在這里,當(dāng)有新消息到來(lái)時(shí),就會(huì)將它喚醒,next方法會(huì)返回這條消息并將其從優(yōu)先級(jí)隊(duì)列中給移除

步驟如下:

  1. 如果Looper已經(jīng)退出了,直接返回null
  2. 進(jìn)入死循環(huán),直到獲取到Message或者退出
  3. 循環(huán)中先判斷是否需要進(jìn)行阻塞,阻塞最終會(huì)調(diào)用到linux的epoll機(jī)制,阻塞結(jié)束后,對(duì)MessageQueue進(jìn)行加鎖,獲取Message
  4. 如果MessageQueue中沒(méi)有消息,則直接把線程無(wú)限阻塞等待喚醒
  5. 如果MessageQueue中有消息,則判斷是否需要等待,否則則直接返回對(duì)應(yīng)的message

可以看到邏輯就是判斷當(dāng)前時(shí)間Message中是否需要等待.其中nextPollTimeoutMillis表示阻塞的時(shí)間,-1表示無(wú)限時(shí)間,直到有事件發(fā)生為止,0表示不阻塞

Handler接收消息

在我們對(duì)Looper進(jìn)行總結(jié)時(shí)我們說(shuō)了: Handler也是實(shí)現(xiàn)線程切換的核心,因?yàn)椴煌腖ooper運(yùn)行在不同的線程,他所調(diào)用的dispatchMessage方法則會(huì)運(yùn)行在不同的線程,所以Message的處理就會(huì)被切換到Looper所在的線程

public static void loop() {
    for (;;) {
        //...
        try {
            // 調(diào)用Message對(duì)應(yīng)的Handler處理消息
            msg.target.dispatchMessage(msg);
        }
                //...
    }
}

上面代碼調(diào)用了 msg.target.dispatchMessage(msg) 方法,msg.target 就是發(fā)送該消息的 Handler,這樣消息最終會(huì)回調(diào)到Handler的dispatchMessage方法中,看下這個(gè)方法

public void dispatchMessage(@NonNull Message msg) {
    //消息的callback不為空,則回調(diào)handleCallback方法
    if (msg.callback != null) {
        handleCallback(msg);
     } else {
            //當(dāng)前mCallback不為空,回調(diào)mCallback.handleMessage方法
                if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
            //回調(diào)handleMessage
          handleMessage(msg);
     }
}

上述代碼步驟:

1、首先,檢查Message的callback是否為null,不為null就通過(guò)handleCallBack來(lái)處理消息,Message的callback是一個(gè)Runnable對(duì)象,實(shí)際上就是Handler的post系列方法所傳遞的Runnable參數(shù),handleCallBack方法處理邏輯也很簡(jiǎn)單,如下:

private static void handleCallback(Message message) {
     message.callback.run();
}

2、其次,檢查mCallback是否為null,不為null就調(diào)用mCallback的handleMessage方法來(lái)處理消息。Callback是個(gè)接口,如下:

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    boolean handleMessage(@NonNull Message msg);
}

通過(guò)Callback可以采用如下方式來(lái)創(chuàng)建Handlere對(duì)象:

Handler handler = new Handler(callback);

那Callback的意義是什么呢?源碼里注釋做了說(shuō)明:可以用來(lái)創(chuàng)建一個(gè)Handler的實(shí)例但并不需要派生的子類。在日常開發(fā)中,創(chuàng)建Handler最常見(jiàn)的就是派生一個(gè)Handler的子類并重寫其handleMessage方法來(lái)處理具體的消息,而Callback給我們提供了另外一種使用Handler的方式,當(dāng)我們不想派生子類時(shí),就可以通過(guò)Callback來(lái)實(shí)現(xiàn)。

3、最后,調(diào)用Handler的handleMessage方法來(lái)處理消息

Handler處理消息的過(guò)程我畫了一張圖,如下:

image-20210131222054614

Message介紹

Message是負(fù)責(zé)承載消息的類,主要是關(guān)注他的內(nèi)部屬性:

// 用戶自定義,主要用于辨別Message的類型
public int what;
// 用于存儲(chǔ)一些整型數(shù)據(jù)
public int arg1;
public int arg2;
// 可放入一個(gè)可序列化對(duì)象
public Object obj;
// Bundle數(shù)據(jù)
Bundle data;
// Message處理的時(shí)間。相對(duì)于1970.1.1而言的時(shí)間
// 對(duì)用戶不可見(jiàn)
public long when;
// 處理這個(gè)Message的Handler
// 對(duì)用戶不可見(jiàn)
Handler target;
// 當(dāng)我們使用Handler的post方法時(shí)候就是把runnable對(duì)象封裝成Message
// 對(duì)用戶不可見(jiàn)
Runnable callback;
// MessageQueue是一個(gè)鏈表,next表示下一個(gè)
// 對(duì)用戶不可見(jiàn)
Message next;

循環(huán)利用Message

當(dāng)我們獲取Message的時(shí)候,官方建議是通過(guò)Message.obtain()方法來(lái)獲取,當(dāng)使用完之后使用recycle()方法來(lái)回收循環(huán)利用。而不是直接new一個(gè)新的對(duì)象:

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; 
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

Message維護(hù)了一個(gè)靜態(tài)鏈表,鏈表頭是sPool,Message有一個(gè)next屬性,Message本身就是鏈表結(jié)構(gòu)。sPoolSync是一個(gè)object對(duì)象,僅作為解決并發(fā)訪問(wèn)安全設(shè)計(jì)。當(dāng)我們調(diào)用obtain來(lái)獲取一個(gè)新的Message的時(shí)候,首先會(huì)檢查鏈表中是否有空閑的Message,如果沒(méi)有則新建一個(gè)返回。

當(dāng)我們使用完成之后,可以調(diào)用Message的recycle方法進(jìn)行回收,如果這個(gè)Message正在使用則會(huì)拋出異常,否則則調(diào)用recycleUnchecked進(jìn)行回收,把Message中的內(nèi)容清空,然后判斷鏈表是否達(dá)到最大值(50),然后插入鏈表中

Handler消息機(jī)制原理總結(jié)

通過(guò)上面的源碼分析,我們可以得出結(jié)論:

  1. 實(shí)例化Handler之前,需先構(gòu)建當(dāng)前線程的Looper并開啟消息循環(huán)
  2. 通過(guò)Handler的send和post方法發(fā)送消息
  3. 發(fā)送的消息會(huì)加入到MessageQueue中,等待Looper獲取處理
  4. Looper會(huì)不斷地從MessageQueue中獲取Message然后交付給對(duì)應(yīng)的Handler處理

如果到這里你還不是特別清楚Handler消息機(jī)制的原理,那么繼續(xù)看下面這張圖:

image

好了,到了這里,關(guān)于Handler消息機(jī)制的主體部分就講完了。

限于篇幅,本篇文章就到這里了,后續(xù)我會(huì)在寫一篇關(guān)于Handler的文章,介紹Hanlder的一些擴(kuò)展知識(shí)學(xué)習(xí),并回答前面我所列出來(lái)的一系列問(wèn)題

全文到此,原創(chuàng)不易,歡迎點(diǎn)贊,收藏,評(píng)論和轉(zhuǎn)發(fā),你的認(rèn)可是我創(chuàng)作的動(dòng)力

最后編輯于
?著作權(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)容