Android通信機(jī)制

在剛學(xué)習(xí)Android網(wǎng)絡(luò)請求時(shí)壶笼,肯定會接觸到UI阻塞,然后認(rèn)識了Handler,Handler是我們最常用的解決方案之一逝段。
簡單點(diǎn)說Handler其實(shí)就是解決多線程通信的一個(gè)東西,那它是怎么具體工作的呢?Looper和MessageQueue又是什么青团,和Handler又和什么關(guān)聯(lián)呢?讓我們來慢慢分析咖楣。

首先我們先來看看主線程是怎么工作的督笆,我們知道在Android啟動時(shí),會默認(rèn)有一個(gè)主線程(UI線程)诱贿,在主線程中會關(guān)聯(lián)一個(gè)MessageQueue,所有操作都會封裝成一個(gè)消息來由主線程處理娃肿。為了保證主線程不會主動退出,主線程會把所有獲得消息的操作放在一個(gè)死循環(huán)中珠十,這樣主線程就會一直處理消息料扰,整個(gè)系統(tǒng)通信也就跑起來了,如下圖:

Handler.png

主線程的Looper是怎么自動創(chuàng)建的焙蹭?

主線程是在ActivityThread.main方法中創(chuàng)建的晒杈,ActivityThread.main是應(yīng)用程序的入口。
部分源代碼:

…
Process.setArgV0("<pre-initialized>");

//創(chuàng)建Looper對象
        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"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//執(zhí)行消息循環(huán)壳嚎,讓MessageQueue動起來
        Looper.loop();

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

執(zhí)行完ActivityThread.main方法桐智,應(yīng)用程序就啟動起來了,并且會一直從消息隊(duì)形中存消息烟馅、取消息然后處理消息说庭,這樣整個(gè)系統(tǒng)就轉(zhuǎn)起來了。

Handler郑趁、Looper刊驴、MessageQueue之間的對應(yīng)關(guān)系?
由于主線程阻塞超過5秒會ANR,所以一些耗時(shí)任務(wù)都是交給子線程來處理的捆憎,子線程處理好后想要交給主線程更新UI,我們知道子線程是不可以更新UI的舅柜,一般我們最常用的手段就是Handler,Handler從子線程拿到的數(shù)據(jù)封裝成消息Post到主線程,然后Handler再在HandleMessage方法中處理更新UI躲惰,但是Handle必須在主線程中創(chuàng)建致份!為什么Handle必須在主線中創(chuàng)建呢?因?yàn)橐粋€(gè)Handler對應(yīng)一個(gè)MessageQueue,而MessageQueue被封裝在Looper中础拨,所以說一個(gè)Handler對應(yīng)一個(gè)Looper,而Looper又被ThreadLocal封裝在Thread中氮块,最終每個(gè)消息隊(duì)列會關(guān)聯(lián)一個(gè)線程。默認(rèn)情況下诡宗,消息隊(duì)列只有一個(gè)就是在主線程消息隊(duì)列滔蝉,即在ActivityThread.main方法中創(chuàng)建的,最后啟用Looper.loop來執(zhí)行循環(huán)塔沃。

Handler是如何關(guān)聯(lián)MessageQueue和線程的呢蝠引?

我們來看看Handler部分源碼:

public Handler()
{
…
   mLooper = Looper.myLooper();   //獲取Looper對象
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;   //獲取MessageQueue對象
        mCallback = callback;
        mAsynchronous = async;

…
}

在Handler的構(gòu)造方法中,用Looper.myLooper獲得Looper對象蛀柴,又從Looper對象中獲得MessageQueue對象螃概,那么Looper.myLooper()又是怎么工作的呢?
Hoopler類部分源代碼:

//為當(dāng)前線程設(shè)置一個(gè)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));
    }

//創(chuàng)建主線程Looper
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
}

myLooper方法通過sThreadLocal.get()來獲取的名扛,prepare()方法中sThreadLocal設(shè)置了Looper對象谅年,這樣Looper就和Handler關(guān)聯(lián)起來了,即Handler和線程也就關(guān)聯(lián)起來了肮韧。
看到這里也就解釋了為什么Handler只能在主線程中創(chuàng)建了融蹂,因?yàn)镠andler要與主線程關(guān)聯(lián),這樣才能在HandlerMessage方法中更新UI弄企,此時(shí)超燃,更新UI才是安全的。

Looper是如何執(zhí)行消息循環(huán)的拘领?

消息循環(huán)的建立是通過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;

  //死循環(huán)
        for (;;) {
            Message msg = queue.next(); //從隊(duì)列中獲取消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

     //把消息傳給Handler
            msg.target.dispatchMessage(msg);
            //回收消息
            msg.recycleUnchecked();
        }
    }

Loop()方法其實(shí)是一個(gè)死循環(huán),不斷地從中取出消息仍給Handler约素。
我們總結(jié)一下Looper,通過 Looper.prepare()獲得對象届良,然后通過Looper.loop()不斷循環(huán),這兩步是成對出現(xiàn)的圣猎。

消息處理的機(jī)制士葫?

msg.target.dispatchMessage(msg);這句是loop()方法中處理的方法,那它是怎么工作的呢送悔?target是一個(gè)Handler類型慢显,target通過Handler投遞到MessageQueue然后又分發(fā)到Handler處理爪模,整個(gè)流程走一遍,下面再看看dispatchMessage()方法源碼:

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

從中可以看到dispatchMessage()方法只是一個(gè)分發(fā)方法 荚藻,如果Runable類型的callback為空屋灌,則執(zhí)行handlerMessage(),我們會將更新UI的代碼寫在這個(gè)方法中。當(dāng)callback不為空時(shí)应狱,會執(zhí)行handCallback來處理共郭,該方法會調(diào)用callback的run方法,其實(shí)這是Handler分發(fā)的兩種類型侦香,比如post(Runable callback)則callback不會為空落塑,而sendMessage()一般不會設(shè)置callback纽疟,因此執(zhí)行handMessage()這個(gè)分支罐韩,源碼如下:

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

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

在上述程序中看到,post()方法中會把Runable包裝成一個(gè)Message對象然后調(diào)用sendMessageDelayed()方法插入到MessageQueue污朽。

總結(jié)

Handler通過Looper.myLooper()拿到Looper實(shí)例散吵,又通過Looper實(shí)例拿到MessageQuenu實(shí)例,這樣Handler就與線程建立聯(lián)系了蟆肆,Handler把子線程的消息Post到MessageQueue,消息在MessageQueue循環(huán),Looper利用dispatchMessage分發(fā)消息矾睦,handlerMessage或者callback方法拿回消息進(jìn)行更新UI.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市炎功,隨后出現(xiàn)的幾起案子枚冗,更是在濱河造成了極大的恐慌,老刑警劉巖蛇损,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赁温,死亡現(xiàn)場離奇詭異,居然都是意外死亡淤齐,警方通過查閱死者的電腦和手機(jī)股囊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來更啄,“玉大人稚疹,你說我怎么就攤上這事〖牢瘢” “怎么了内狗?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長义锥。 經(jīng)常有香客問我柳沙,道長,這世上最難降的妖魔是什么缨该? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任偎行,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛤袒。我一直安慰自己熄云,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布妙真。 她就那樣靜靜地躺著缴允,像睡著了一般。 火紅的嫁衣襯著肌膚如雪珍德。 梳的紋絲不亂的頭發(fā)上练般,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音锈候,去河邊找鬼薄料。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泵琳,可吹牛的內(nèi)容都是我干的摄职。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼获列,長吁一口氣:“原來是場噩夢啊……” “哼谷市!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起击孩,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤迫悠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后巩梢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體创泄,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年且改,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了验烧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡又跛,死狀恐怖碍拆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慨蓝,我是刑警寧澤感混,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站礼烈,受9級特大地震影響弧满,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜此熬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一庭呜、第九天 我趴在偏房一處隱蔽的房頂上張望滑进。 院中可真熱鬧,春花似錦募谎、人聲如沸扶关。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽节槐。三九已至,卻和暖如春拐纱,著一層夾襖步出監(jiān)牢的瞬間铜异,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工秸架, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揍庄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓咕宿,卻偏偏與公主長得像币绩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子府阀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

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