Android 消息機(jī)制之 Looper 深入源碼分析 [ 三 ]

Android 消息機(jī)制深入源碼分析 [ 一 ]
Android 消息機(jī)制之 ThreadLocal 深入源碼分析 [ 二 ]
Android 消息機(jī)制之 Looper 深入源碼分析 [ 三 ]
Android 消息機(jī)制之 Message 與消息對(duì)象池的深入源碼分析 [ 四 ]
Android 消息機(jī)制之 MessageQueue 深入源碼分析 [ 五 ]
Android 消息機(jī)制之初識(shí)Handler [ 六 ]
Android 消息機(jī)制之 Handler 發(fā)送消息的深入源碼分析 [ 七 ]
Android 消息機(jī)制之 MessageQueue.next() 消息取出的深入源碼分析 [ 八 ]
Android 消息機(jī)制之消息的其他處理深入源碼分析 [ 九 ]
Android 消息機(jī)制總結(jié) [ 十 ]

上一章學(xué)習(xí)了消息機(jī)制中的 ThreadLocal, 本章接著來學(xué)習(xí)消息機(jī)制中的 Looper. 開篇也是先拋出幾個(gè)問題.

1. 問題

    1. 可以在一個(gè)線程多次執(zhí)行 Looper. prepare() 嗎? 為什么 ?
    1. Looper.prepareMainLooper 是用來做什么的. 為什么我們?cè)谥骶€程可以直接使用 Handler, 而不需要調(diào)用 Looper. prepare() ?
    1. Looper.quit 與 Looper.quitSafely() 有什么區(qū)別.

2. 例

先來一個(gè)典型的關(guān)于 Looper 的例子. (因?yàn)楸菊轮环治?Looper, Handler 會(huì)放到后面章節(jié)分析.)

class LooperThread extends Thread{
  public Handler mHandler;
  public void run(){
    //分析1
    Looper.prepare();
    mHandler = new Handler(){
      public void HhandleMessage(Message msg){
        Message msg = Message.obtain();
      }
    };
    //分析 2
    Looper.loop();
  }
}

3. 分析 1

進(jìn)入 Looper.prepare() 方法. 代碼在 Looper.java 97行.

public static void prepare() {
    prepare(true);
}
  • 官方注釋:

初始化當(dāng)前線程和 Looper, 這樣可以在實(shí)際開始啟動(dòng)循環(huán)(Loop()) 之前創(chuàng)建一個(gè) Handler, 并且關(guān)聯(lián)一個(gè) Looper. 要確保最先調(diào)用這個(gè)方法, 然后才調(diào)用 loop(), 并且通過調(diào)用 quit 結(jié)束.

這也正對(duì)應(yīng)了我們例子中寫的, 要先調(diào)用 Looper.prepare() , 然后創(chuàng)建一個(gè) Handler, 最后才調(diào)用 Looper.loop() 啟動(dòng)循環(huán).

繼續(xù)回到 public static void prepare(), 在方法內(nèi)部又調(diào)用了一個(gè) private static void prepare(boolean quitAllowed), 注意, 這兩個(gè)方法名是相同的, 不過一個(gè)是 public 無參的, 一個(gè)是 private 有參的. 而我們調(diào)用的是 public 無參的, 在這個(gè)方法內(nèi)部, Android 系統(tǒng)又調(diào)用了 private 有參的, 并且傳入的參數(shù)為 true, 實(shí)際上這個(gè)參數(shù)一直向下傳遞到了 MessageQueue 的構(gòu)造函數(shù)中, 表示允許退出消息隊(duì)列. 也就是說我們?cè)谕獠空{(diào)用 Looper.prepare() 后, 系統(tǒng)為我們創(chuàng)建的消息隊(duì)列是允許退出的.
?

進(jìn)入到 private static void Looper.prepare(true)
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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));
}
  • 解析:

又看到了 ThreadLocal, 所以我們需要先學(xué)習(xí)了第二章, 再來看這個(gè)就會(huì)明白是什么意思了. 這個(gè) sThreadLocal 中存儲(chǔ)的是 Looper 對(duì)象.
調(diào)用 ThreadLocal.get() 看獲取到的值是否為null, 如果為不為 null 說明當(dāng)前線程已經(jīng)創(chuàng)建過 Looper 對(duì)象了. 那么就會(huì)直接拋出異常. [每個(gè)線程只能執(zhí)行一次 Looper], 那么這就是問題1 的答案,
如果獲取到值為 null, 說明當(dāng)前線程還未創(chuàng)建 Looper, 就會(huì)創(chuàng)建一個(gè) Looper 對(duì)象, 并放到 ThreadLocal 中.

接著我們進(jìn)入 Looper 的構(gòu)造函數(shù), 看看都做了些什么.
?

進(jìn)入到 Looper(boolean quitAllowed)
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}
  • 解析

看到構(gòu)造函數(shù)內(nèi)部創(chuàng)建了一個(gè) MessageQueue, 傳入 boolean quitAllowed 設(shè)置是否允許退出, 然后賦值給本地變量 mQueue, 然后獲取當(dāng)前線程, 也賦值給本地變量 mThread.

這樣這個(gè)創(chuàng)建的 Looper 內(nèi)就持有 MessageQueue 消息隊(duì)列 與 Thread 當(dāng)前線程的引用. 也就實(shí)現(xiàn)了 Looper 與 MessageQueue 和 Thread 的關(guān)聯(lián) ()

通過 Looper 的構(gòu)造函數(shù)可以知道, Looper 是無法被直接創(chuàng)建的, (構(gòu)造函數(shù)是 private 類型的). 我們必須通過 Looper 的兩個(gè)靜態(tài)方法, prepare()/prepareMainLooper() 來間接的創(chuàng)建. 那么 prepareMainLooper 是用來做什么的呢, 一起來看一下
?

進(jìn)入到 prepareMainLooper()

Looper.java 114 行

public static void prepareMainLooper() {
  //設(shè)置不允許退出的 Looper
  prepare(false);
  synchronized (Looper.class) {
      //主線程有且只能調(diào)用一次 prepareMainLooper()
      if (sMainLooper != null) {
          throw new IllegalStateException("The main Looper has already been prepared.");
      }
      //賦值給 sMainlooper
      sMainLooper = myLooper();
  }
}
  • 官方注釋

初始化當(dāng)前線程的 looper. 并且標(biāo)記為一個(gè)程序的主 Looper, 由 Android 環(huán)境來創(chuàng)建應(yīng)用程序的主 Looper, 因此這個(gè)方法不能由自己調(diào)用,只能是 Android 自己調(diào)用

什么意思呢, 簡(jiǎn)單來說就是初始化主線程的 Looper, 而且只能由 Android 系統(tǒng)調(diào)用.

  • 解析

一開始也調(diào)用了 private static void prepare(boolean quitAllowed) 傳入了 false, 結(jié)合上面的分析, 是創(chuàng)建了一個(gè) Looper 對(duì)象, 然后 set 到當(dāng)前線程的 ThreadLocal中, 并且在 Looper 構(gòu)造函數(shù)中創(chuàng)建的 MessageQueue 是無法退出的.
接著做了 sMainLooper 的非空判斷, sMainLooper 看名字就知道這是什么了, 就是主線程的 Looper, 如果非空, 就直接拋出異常, 所以這個(gè) sMainLooper 一開始必須是空, 因?yàn)橹骶€程有且只能調(diào)用一次 prepareMainLooper(), 如果 sMainLooper 非空, 說明 prepareMainLooper() 已經(jīng)被系統(tǒng)調(diào)用過了. 為空, 就調(diào)用 myLooper() 方法, 將返回的 Looper 對(duì)象賦值給 sMainLooper. 那么 prepareMainLooper() 是在什么時(shí)候被調(diào)用的呢, 答案是在 ActivityThread,java 中的 main 方法.

那么問題 2 的答案也有了: prepareMainLooper() 是用來為主線程創(chuàng)建 Looper 的, 系統(tǒng)默認(rèn)為我們創(chuàng)建好了, 所以我們可以在主線程中直接使用 Handler
?

進(jìn)入到 myLooper()

Looper.java 的 254行

public static @Nullable Looper myLooper() {
  return sThreadLocal.get();
}
  • 解析

從當(dāng)前線程的 ThreadLocal 中取出 Looper 對(duì)象并返回. 這里的 sThreadLocal.get();prepare(boolean) 方法里面的 sThreadLocal.set(new Looper( quitAllowed))對(duì)應(yīng)的, 一個(gè)設(shè)置值一個(gè)取值.

現(xiàn)在完整的分析 1, 已經(jīng)分析完了, 主要就是為當(dāng)前線程創(chuàng)建一個(gè)Looper 對(duì)象放入到當(dāng)前線程的 ThreadLocal中, 并同時(shí)持有 MessageQueue 對(duì)象與當(dāng)前線程對(duì)象. 其中創(chuàng)建的消息隊(duì)列, 是可以退出的. 也知道了我們?yōu)槭裁纯梢栽谥骶€程直接使用 Handler, 就是因?yàn)樵?code>ActivityThread.main 方法中調(diào)用了 Looper.prepareMainLooper() 方法, 系統(tǒng)已經(jīng)為主線程創(chuàng)建好了 Looper.
Looper 已經(jīng)創(chuàng)建好了, 那么接下來就是開始循環(huán)了. 接下來開始看分析 2 Looper.loop().

?

4. 分析 2

Looper.java 137 行, 進(jìn)入 Looper.loop() 方法

public static void loop() {
    //獲取當(dāng)前線程 ThreadLocal 存儲(chǔ)的 Looper
    final Looper me = myLooper();
    if (me == null) {
        //沒有 Looper 直接拋出異常
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲取當(dāng)前 Looper 持有的消息隊(duì)列
    final MessageQueue queue = me.mQueue;
    //確保權(quán)限檢查基于本地進(jìn)程, 而不是基于最初調(diào)用進(jìn)程  
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //進(jìn)入 loop 主循環(huán)方法
    //一個(gè)死循環(huán), 不聽的處理消息隊(duì)列中的消息,消息的獲取是通過 MessageQueue 的 next 方法
    for (;;) {
        //可能會(huì)阻塞
        Message msg = queue.next();
        //注: 這里的 msg = null, 并不是說沒消息了, 而是如果消息隊(duì)列正在關(guān)閉的情況下, 會(huì)返回 null.
        if (msg == null) {
            return;
        }
        ...        
        //用于分發(fā)消息,調(diào)用 Message 的 target 變量(也就是 Handler)的 dispatchMessage方法處理消息
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        ...
        //將 Message 回收標(biāo)記后放入消息池.
        msg.recycleUnchecked();
    }
}
  • 解析

簡(jiǎn)單來說就是 Loop() 進(jìn)入循環(huán)模式, 不斷的重復(fù)下面的操作, 直到?jīng)]有消息時(shí), 退出.

  1. 讀取 MessageQueue 的下一條數(shù)據(jù). 并賦值給 Message.
  2. 調(diào)用把 Handler.dispatchMessage() 方法把 Message 分發(fā)給相對(duì)應(yīng)的 target 也就是 Handler.
  3. 把分發(fā)后的 Message 打上標(biāo)記后放到消息池回收. 以便重復(fù)使用.

這里面有幾個(gè)重要的方法, 后面文章會(huì)解釋到.

  1. MessageQueue.next 方法,讀取下一條 Message, 有阻塞
  2. Message.Handler.dispatchMessage(Message msg) 方法:消息分發(fā)
  3. Message.recycleUnchecked() 方法:消息放入消息池

5. Looper.quit 與 Looper.quitSafely 問題 3 的答案.

上面只是開啟了循環(huán)方法, Looper 也提供了兩個(gè)退出循環(huán)的方法, 分別是 quitquitSafely
我們調(diào)用的 Looper 的兩個(gè)退出循環(huán)的方法.

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

MessageQueue.quit(boolean safe)

void quit(boolean safe) {
  //當(dāng) mQuitAllowed 為 false,表示不允許退出,強(qiáng)行調(diào)用 quit 會(huì)有異常
  //mQuitAllowed 是在 Looper 構(gòu)造函數(shù)里面構(gòu)造 MessageQueue() 以參數(shù)傳入的.
  if (!mQuitAllowed) {
      throw new IllegalStateException("Main thread not allowed to quit.");
  }
    //同步代碼塊
  synchronized (this) {
      //防止多次執(zhí)行退出操作
      if (mQuitting) {
          return;
      }
      mQuitting = true;
      if (safe) {
          //移除所有尚未觸發(fā)的所有消息
          removeAllFutureMessagesLocked();
      } else {
          //移除所有消息
          removeAllMessagesLocked();
      }

      // We can assume mPtr != 0 because mQuitting was previously false.
      nativeWake(mPtr);
  }
}
  • quit: 會(huì)將消息隊(duì)列中的所有消息移除 (延遲消息和非延遲消息)
    • Looperquit() 方法內(nèi)部的本質(zhì)是調(diào)用 MessageQueuequit(boolean safe) 方法. 傳入?yún)?shù)是 false.
  • quitSafely: 會(huì)將消息隊(duì)列所有延遲消息移除, 非延遲消息則派發(fā)出去讓 Handler 處理.
    • LooperquitSafely() 方法內(nèi)部調(diào)用的本質(zhì)也是 MessageQueuequit(boolean safe) 方法. 只不過傳入?yún)?shù)是 true.
  • quitSafely 相比于 quit 方法安全支出在于清空消息之前會(huì)派發(fā)出去所有的非延遲消息.

到這里. 相信對(duì) Looper 都有了自己的認(rèn)識(shí), 比如為什么要先調(diào)用 Looper.prepare(), 再調(diào)用 Looper.loop(), 以及為什么主線程可以直接使用Hander, 而不需要調(diào)用 Looper.prepare(). 還有 loop() 循環(huán)中是怎么發(fā)送消息的. 下一章接著分析消息機(jī)制中的 Message.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子设褐,更是在濱河造成了極大的恐慌,老刑警劉巖垦沉,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異劣像,居然都是意外死亡乡话,警方通過查閱死者的電腦和手機(jī)摧玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門耳奕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诬像,你說我怎么就攤上這事屋群。” “怎么了坏挠?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵芍躏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我降狠,道長(zhǎng)对竣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任榜配,我火速辦了婚禮否纬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛋褥。我一直安慰自己临燃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布烙心。 她就那樣靜靜地躺著膜廊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淫茵。 梳的紋絲不亂的頭發(fā)上爪瓜,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音匙瘪,去河邊找鬼铆铆。 笑死炬转,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的算灸。 我是一名探鬼主播扼劈,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼菲驴!你這毒婦竟也來了荐吵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤赊瞬,失蹤者是張志新(化名)和其女友劉穎先煎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巧涧,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薯蝎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谤绳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片占锯。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缩筛,靈堂內(nèi)的尸體忽然破棺而出消略,到底是詐尸還是另有隱情,我是刑警寧澤瞎抛,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布艺演,位于F島的核電站,受9級(jí)特大地震影響桐臊,放射性物質(zhì)發(fā)生泄漏胎撤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一断凶、第九天 我趴在偏房一處隱蔽的房頂上張望伤提。 院中可真熱鬧,春花似錦懒浮、人聲如沸飘弧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽次伶。三九已至,卻和暖如春稽穆,著一層夾襖步出監(jiān)牢的瞬間冠王,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工舌镶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柱彻,地道東北人豪娜。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哟楷,于是被迫代替她去往敵國和親瘤载。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351