[Android 知識點剖析] Looper Handler MessageQueue

快速了解消息循環(huán)場景

首先一句話總結(jié)一下上面這些概念:

Looper是為一個Thread添加一個事件循環(huán)(Message Loop)
MessageQueue是Looper中管理Message的隊列
Message是事件循環(huán)中的事件對象
Handler是用來創(chuàng)建Message并且管理發(fā)送Message的

接下來用通俗的語言來描述一下這些對象運行的場景

Looper是一個死循環(huán),它里面持有一個MessageQueue,然后這個循環(huán)不斷的從MessageQueue里拿Message出來執(zhí)行。如果MessageQueue里沒有東西執(zhí)行線程就會等待,那誰往MessageQueue里塞Message呢?沒錯,就是Handler。

如果你找這個文章,只是為了某種原因救急,那么到這里我覺得就結(jié)束了。但如果你還需要完全掌握這些概念并且能講給別人聽,那么下面部分應(yīng)該不會讓你失望呦 :)

這些對象之間的關(guān)系

大多數(shù)場景下,我們可能并不需要自己創(chuàng)建Looper,我們關(guān)注這些源自于對Handler的使用有疑惑。我們從一個常見的場景來進入主題。

從工作線程讓一段代碼在主線程中執(zhí)行

完成這個任務(wù)一般我們有兩種做法:

  • Handler.post
    直接上實現(xiàn)代碼:
Handler mainHandler = new Handler(Looper.getMainLooper()); //等同于context.getMainLooper()
Runnable myRunnable = new Runnable() {
    @Override 
    public void run() {....} // This is your code
};
mainHandler.post(myRunnable);

代碼創(chuàng)建了一個使用主線程Looper的Handler對象,并把Runnable post到主線程Looper的MessageQueue里。如上面介紹所說,等Looper拿到這個runnable就會執(zhí)行。

  • Activity.runOnUiThread
    在你可以方便獲得Activity對象的時候,可以直接調(diào)用這個方法來實現(xiàn)代碼在主線程調(diào)用。我們來看看這個方法代碼實現(xiàn):
public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

代碼實現(xiàn)里判斷了當(dāng)前線程是否是主線程,如果是主線程,直接執(zhí)行Runnable的run方法(action.run());如果不是,則使用綁定在主線程的handler把Runnable post到主線程Looper的MessageQueue里。
從這段代碼能思考到下面幾點:

  1. 調(diào)用runOnUiThread的時候,你可以從任何線程,只要你能獲得activity實例,不需要做任何線程判斷,因為系統(tǒng)幫你做了。
  2. 這個方法實現(xiàn)其實是封裝了一下方法一的Handler實現(xiàn)方式。
對象關(guān)系解析

先看類圖:


類圖

如上圖,Handler和Looper,Looper和MessageQueue,Thread之間都是組合關(guān)系。從Looper的官方文檔的推薦代碼我們看到Looper是如何給一個Thread添加消息隊列能力的:

class LooperThread extends Thread {
      public Handler mHandler;
      public void run() {
          Looper.prepare();
          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };
          Looper.loop();
      }
  }

調(diào)用了Looper.prepare()和Looper.loop(); 其中Looper.prepare是構(gòu)造Looper對象,并且創(chuàng)建MessageQueue和獲得Looper所在線程;Looper.loop()就是啟動死循環(huán),并從MessageQueue中讀取Message執(zhí)行(大家可以直接參考源碼來看,非常簡單)
Handler對象的創(chuàng)建成功的必要條件是Handler所在的線程擁有一個調(diào)用過prepare()的Looper,不然會拋出異常。然后Handler.post等方法調(diào)用,實際上是調(diào)用Handler擁有的Looper的MessageQueue.enqueue方法把要執(zhí)行的Runnable或者Message加入隊列中等待執(zhí)行。
它們之間的關(guān)系就是這么簡單,no big deal :D

為什么會這么設(shè)計?

根據(jù)上面的解析可以看出,沒有Looper的設(shè)計,Handler也沒有必要存在,那為什么需要Looper?
其實任何牽扯GUI的系統(tǒng),都會有這樣的需求,而且基本上是一個模式。Looper就做了兩件事:

  1. 把一個run()方法執(zhí)行完就結(jié)束的普通線程,轉(zhuǎn)化為一個可以持續(xù)執(zhí)行的Android app。
  2. 提供一個MessageQueue,GUI需要任務(wù)隊列來完成操作。
    我們知道,當(dāng)程序開始運行,會執(zhí)行main方法,Android程序一般來說執(zhí)行在main方法所在的線程中——“主線程”,主線程也不是什么特別的線程,也是使用new Thread()方法來創(chuàng)建的,看下面的代碼:
public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }
    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

不過,這跟我們所知道的Android主線程不同的是,run()方法執(zhí)行完,程序就退出了。在Android中,這顯然不是我們需要的,我們希望主程序初始化完成后,等待用戶的操作,反饋用戶的操作并執(zhí)行接下來的任務(wù)。并且用戶可能進行一系列連續(xù)的操作,我們需要一個先進先出的隊列來緩沖用戶操作并交給程序執(zhí)行,這就是Looper的作用,在任何GUI系統(tǒng)里,都有一樣的機制來完成這個目的。
而且Looper的官網(wǎng)說:

"Threads by default do not have a message loop associated with them", and Looper is a class "used to run a message loop for a thread".

這樣就更好理解Looper了。

為什么證明我們的描述,我們?nèi)?a target="_blank" rel="nofollow">ActivityThread class看它的main方法,把一個普通Thread變成了主線程:

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");

好了,大概就是這樣了先,有問題一起探討~ 終于把這個寫了,居然寫了一天...

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

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

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