Android異步消息機制初探

Android中,當我們需要在子線程中進行網絡請求等耗時操作后,如果需要更新UI時,通常會考慮使用Handler來處理,一種常用的寫法如下:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1){
                // update ui
            }
        }
    };

new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = Message.obtain();
                message.what = 1;
                mHandler.sendMessage(message);
            }
        });

除了上邊我們用到的Handler類外,Android消息機制要正常工作還需要LooperMessageQueue兩個類的配合,下來我們分別介紹三個類,以及它們之間的工作原理:

1、MessageQueue

顧名思義就是消息隊列,但其實MessageQueue的底層是通過單鏈表來實現(xiàn)的,MessageQueue是用來中轉Handler對象發(fā)送出的消息,主要包括enqueueMessagenext兩個操作,分別用來插入一條消息和取出一條消息,因為鏈表在插入和刪除操作上有較高的效率,這可能就是使用該數(shù)據(jù)結構的原因吧。MessageQueue只負責保存消息,并不會處理消息,MessageQueue對象如何得到呢?繼續(xù)往下看。

2、Looper

如果我們是在UI線程中使用Handler來發(fā)送異步消息,系統(tǒng)已經在當前UI線程通過Looper.prepareMainLooper()幫助我們創(chuàng)建好Looper對象,并調用Looper.loop()開啟無限消息循環(huán),不斷從MessageQueue的實例中讀取消息。如果在子線程中我們不手動創(chuàng)建Looper對象,則會拋出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()的異常,所以在子線程中創(chuàng)建并使用Handler對象,就必須手動調用Looper.prepare()創(chuàng)建Looper對象,并通過Looper.loop()開啟消息循環(huán)。
在子線程中可通過如下方式使用Android消息機制:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//創(chuàng)建Looper對象
                Handler handler = new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //do something
                    }
                });
                Looper.loop();//開啟消息循環(huán)
            }
        });

這樣子線程同樣具有了異步消息循環(huán)處理的能力。
我們先通過源碼分析一下Looper.prepare()方法

public static void prepare() {
        prepare(true);
    }

很簡單,只有一行代碼,繼續(xù)進入到prepare方法中

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

重點看一下new Looper()方法

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

可以看到在創(chuàng)建Looper對象時也創(chuàng)建了MessageQueue對象,此時Looper對象持有了MessageQueue對象的引用。并與當前線程綁定,保證一個線程只會有一個Looper對象,同時一個Looper對象也只有一個MessageQueue對象。

3、Handler

首先看一下Handler的構造方法

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

依然只有一行,繼續(xù)跟進

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

通過mLooper = Looper.myLooper(),我們Handler對象拿到了對應Looper對象的引用,產生了關聯(lián)。
可以看到如果Looper等于null的話會拋出異常,這也驗證了上邊第二點中的說法,創(chuàng)建Handler對象時必須有Looper對象存在。
再看 mQueue = mLooper.mQueue這一行,其實就是將Handler的實例與我們Looper實例中創(chuàng)建的MessageQueue對象關聯(lián)起來。此時MessageQueue對象已經和Looper對象以及Handler對象關聯(lián)了起來,并且他們在同一線程中。

4、工作原理

結合文章開頭的例子來分析 Handler 具體的工作原理。

通過Handler對象發(fā)送消息時可用的方法非常多,例如常用的sendMessage系列方法,post系列方法等,通過源碼可以知道,這兩系列方法都最終都是調用下邊的方法來實現(xiàn)的:

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

繼續(xù)看下sendMessageAtTime方法

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

其核心的的就是最后一行,跟進去

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

首先看第一行msg.target = this,將Handler對象賦值給的Message對象的target屬性,此時Message對象持有了Handler對象的引用。
最后一行通過調用MessageQueue 對象的enqueueMessage方法來保存發(fā)送的消息。

上邊Looper類中還有一個Looper.loop()方法沒分析,調用該方法后,則會一直檢測MessageQueue對象中是否有數(shù)據(jù),有的話則取出,否則阻塞,此時我們來看一下源碼:

public static void loop() {
        ........
        省略
        ........
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);
            ........
            省略
            ........
        }
    }

只看核心的代碼,首先是一個無限的for循環(huán),循環(huán)中調用MessageQueue 的next方法不斷的取出Message對象,沒有消息則進入阻塞狀態(tài)。否則執(zhí)行msg.target.dispatchMessage(msg),這里的msg.target就是上邊enqueueMessage方法中的msg.target,也就是Handler對象的引用,所以此時消息就可以調用Handler的dispatchMessage()方法進行消息處理。此時Handler對象發(fā)出的消息在MessageQueue對象中通過 Looper 完成了中轉,由于Looper是在主線程創(chuàng)建并開啟消息循環(huán)的,所以dispatchMessage()在主線程被調用:

接下來看一下dispatchMessage方法:

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

首先看第一行if判斷,什么時候msg.callback != null成立呢,其實就是當我們通過如下形式發(fā)送消息時

handler.post(new Runnable() {
                    @Override
                    public void run() {
                        // update ui
                    }
                });

繼續(xù)查看post方法

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

進入getPostMessage方法中

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

記住m.callback = r這一行,此時再看dispatchMessage中第一個if條件的執(zhí)行代碼:

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

方法中執(zhí)行的正是callback 中的run方法

if (mCallback != null)什么時候成立呢?當我們通過如下方式使用Handler時成立

private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            return false;
        }
    });

從而下邊的代碼得到執(zhí)行

if (mCallback.handleMessage(msg)) {
                    return;
                }

即就是我們上邊new Handler.Callback()中的handleMessage方法

最后如果我們采用文章最開始的代碼執(zhí)行handler操作則會執(zhí)行dispatchMessage中最后一行handleMessage(msg)方法:

public void handleMessage(Message msg) {}

可以看到是一個空的方法,因為需要我們自己去實現(xiàn)哦!同時 Handler 也是在主線程創(chuàng)建的,所以 handleMessage()方法最終在主線程執(zhí)行,這樣就完成了從子線程到主線程的切換。

最后說一下,創(chuàng)建Message對象,可以通過new方法 ,也可以使用Message.obtain()方法,但建議使用obtain方法,因為Message內部維護了一個Message池用于Message的復用,避免使用new 重新分配內存。

到這里你有沒有發(fā)現(xiàn)LooperMessageQueueHandler之間的關系呢?

  1. 在Looper中創(chuàng)建了MessageQuene,Looper循環(huán)從MessageQuene取出消息,由于消息持有Handler的引用,最終會調用Handler的handleMessage()方法或其它方法,Looper會被保存在ThreadLocal中,一個線程對應唯一Looper。
  2. Handler發(fā)送消息時,會從ThreadLocal中取出對應線程的Looper,再從中得到對應的MessageQuene,發(fā)送的消息會持有Handler的引用,然后將消息保存到MessageQuene。
  3. 關于ThreadLocal在下一篇有講到

到此Android消息機制的基本原理已經分析完了,簡單的概括一下:通過Handler對象的相關方法將Message對象發(fā)送出去進而插入到MessageQueue對象中,然后Looper對象調用loop方法,進一步會執(zhí)行MessageQueue對象的next方法取出消息,交給Handler對象的dispatchMessage方法來處理。這就是Android消息機制一個大致的流程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容