Android消息機(jī)制源碼解析

概述

Android 的消息機(jī)制主要是指 Handler 的運(yùn)行機(jī)制逞力。Android 規(guī)定只有主線(xiàn)程可以訪(fǎng)問(wèn) UI ,子線(xiàn)程中無(wú)法訪(fǎng)問(wèn) UI糠爬。但是主線(xiàn)程中不建議進(jìn)行耗時(shí)操作寇荧,因?yàn)檫@會(huì)引起 ANR。
系統(tǒng)為什么不允許子線(xiàn)程中訪(fǎng)問(wèn) UI执隧?
如果多線(xiàn)程并發(fā)訪(fǎng)問(wèn)揩抡,UI 控件處于不可控制的狀態(tài)户侥。如果對(duì) UI 控件的訪(fǎng)問(wèn)上鎖,首先上鎖機(jī)制會(huì)讓 UI 訪(fǎng)問(wèn)的邏輯變得復(fù)雜峦嗤;其次會(huì)降低 UI 的訪(fǎng)問(wèn)效率蕊唐,因?yàn)殒i機(jī)制會(huì)阻塞某些線(xiàn)程的執(zhí)行。所以烁设,采用單線(xiàn)程模型來(lái)處理 UI 操作簡(jiǎn)單高效替梨。

消息機(jī)制解決了我們需要從服務(wù)器獲取數(shù)據(jù)后操作 UI 的矛盾。但是更新 UI 僅僅是 Handler 的一個(gè)使用場(chǎng)景装黑。本質(zhì)上來(lái)說(shuō)副瀑,Handler 并不是專(zhuān)門(mén)用于更新 UI 的,它只是在更新 UI 的場(chǎng)景中用的比較多曹体。Handler 的使用過(guò)程很簡(jiǎn)單俗扇,通過(guò)它可以輕松的將一個(gè)任務(wù)切換到 Handler 所在的線(xiàn)程中去執(zhí)行。Handler 的運(yùn)行需要底層的 MessageQueue 和 Looper 的支撐箕别。

Android消息機(jī)制有哪些核心成員組成的呢铜幽?

  • MessageQueue:
    消息隊(duì)列。 它的內(nèi)部存儲(chǔ)了一組消息串稀,以隊(duì)列的形式對(duì)外提供插入和刪除的操作除抛。但是內(nèi)部存儲(chǔ)結(jié)構(gòu)并不是真正的隊(duì)列,而是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)消息列表母截。
  • Looper:
    消息循環(huán)到忽。以無(wú)限循環(huán)的形式去查詢(xún)是否有新消息,如果有的話(huà)就處理消息清寇,沒(méi)有就一直等待喘漏。
  • Handler:
    消息傳遞的主要操作者。將消息從一個(gè)線(xiàn)程傳遞到另外一個(gè)線(xiàn)程华烟。
  • ThreadLocal:
    不是線(xiàn)程翩迈,它的作用是可以在每個(gè)線(xiàn)程中存儲(chǔ)數(shù)據(jù)。
    Handler 創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線(xiàn)程的 Looper 來(lái)構(gòu)造消息循環(huán)系統(tǒng)盔夜,Handler 內(nèi)部要使用 ThreadLocal 獲取當(dāng)前線(xiàn)程的 Looper负饲。ThreadLocal 可以在不同的線(xiàn)程中互不干擾的存儲(chǔ)并提供數(shù)據(jù),通過(guò) ThreadLocal 可以輕松獲取每個(gè)線(xiàn)程的 Looper喂链。
    注意:線(xiàn)程默認(rèn)是沒(méi)有 Looper 的返十,如果需要使用 Handler 必須為線(xiàn)程創(chuàng)建 Looper。主線(xiàn)程(UI線(xiàn)程 ActivityThread)被創(chuàng)建時(shí)就會(huì)初始化 Looper椭微,這也是主線(xiàn)程中默認(rèn)可以使用 Handler 的原因洞坑。

Handler分析

Handler 是Android消息機(jī)制的上層類(lèi),也是Android消息機(jī)制中我們接觸最多的類(lèi)蝇率。使用消息機(jī)制的時(shí)候迟杂,我們都是通過(guò) Handler 的 post 和 send 系列方法開(kāi)始的匈仗,我們從這里開(kāi)始分析,借助源碼逢慌,探索Android消息機(jī)制的底層原理。我們先看 post 系列方法源碼:

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

public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

我們?cè)倏?send 系列方法的源碼:

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what){
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageAtTime(msg, uptimeMillis);
}

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

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

畫(huà)個(gè)簡(jiǎn)易的圖间狂,理清這些方法之間的關(guān)系攻泼。

image

圖中可以發(fā)現(xiàn):post 系列和 send 系列的方法最終都指向了 sendMessageAtTime()方法。也就是說(shuō)鉴象,send 系列的方法最終都是靠 sendMessageAtTime()方法實(shí)現(xiàn)的忙菠。我們看下 sendMessageAtTime 方法的實(shí)現(xiàn):首先獲取一個(gè) MessageQueue,然后傳給 enqueueMessage 方法纺弊,調(diào)用此方法牛欢。下面是 enqueueMessage 方法源碼:

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

可以看到,最后執(zhí)行的是 MessageQueue 類(lèi)的 enqueueMessage 方法淆游。我們知道 MessageQueue有兩個(gè)很重要的方法:enqueueMessage 和 next傍睹。enqueueMessage 負(fù)責(zé)往消息隊(duì)列里添加數(shù)據(jù),next 負(fù)責(zé)從消息隊(duì)列里取出數(shù)據(jù)犹菱。所以拾稳,handler post 和send 系列的方法做的事情就是往 MessageQueue 里面添加數(shù)據(jù)。

現(xiàn)在我們知道數(shù)據(jù)是怎么添加到消息隊(duì)列里面去的了腊脱,這是消息傳遞的第一步访得,那第二步就是把消息取出來(lái)進(jìn)行處理,雖然處理消息依然是 handler 的事陕凹,但把消息取出來(lái)卻是 Looper 默默一直干的事悍抑。我們分析下 Looper。

Looper

Looper 是讓我們整個(gè)消息機(jī)制循環(huán)起來(lái)的核心類(lèi)杜耙。普通的線(xiàn)程是沒(méi)有消息隊(duì)列的搜骡,也是無(wú)法使用 Handler 的(主線(xiàn)程:ActivityThread 被創(chuàng)建的時(shí)候默認(rèn)初始化 Looper ,這也是我們可以直接在主線(xiàn)程使用 Handler 的原因)泥技。正是借助 Looper 讓線(xiàn)程成為 Looper 線(xiàn)程浆兰,線(xiàn)程和 Looper 綁定后,也就有了消息隊(duì)列珊豹,因?yàn)橄㈥?duì)列 (MessageQueue) 是放在 Looper 類(lèi)里面的簸呈。那么我們?cè)趺醋隹梢宰屍胀ň€(xiàn)程變成可以使用消息機(jī)制的線(xiàn)程呢?很簡(jiǎn)單店茶,使用 Looper 類(lèi)的兩個(gè)靜態(tài)方法:

  • Looper.prepare()
  • Looper.loop()

這兩個(gè)方法具體做了什么呢蜕便?我們看下源碼:

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

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

prepare 方法很簡(jiǎn)單,里面就做了一件事贩幻,就是給變量 sThreadLocal 設(shè)置值轿腺。sThreadLocal 變量是一個(gè) ThreadLocal 類(lèi)型的變量:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

如果對(duì) ThreadLocal 不太了解可以先看下這篇文章 Android消息機(jī)制-ThreadLocal两嘴。我們知道,ThreadLocal 的作用就是保證使用 ThreadLocal 存儲(chǔ)的數(shù)據(jù)只屬于當(dāng)前線(xiàn)程族壳,其他線(xiàn)程無(wú)法獲取憔辫。sThreadLocal 泛型是 Looper,所以這個(gè)變量會(huì)存儲(chǔ)一個(gè) Looper 對(duì)象仿荆。prepare() 方法是調(diào)用了 sThreadLocal 的 set 方法存儲(chǔ)了一個(gè)新建的 Looper 對(duì)象贰您。存儲(chǔ)之前會(huì)判斷是否已經(jīng)有了 Looper 對(duì)象,如果已經(jīng)有了拢操,會(huì)拋異常锦亦。所以,一個(gè)線(xiàn)程中只能有一個(gè) Looper 對(duì)象令境。這樣就保證了這個(gè) Looper 對(duì)象只屬于當(dāng)前線(xiàn)程杠园,而且只有一個(gè) Looper 對(duì)象。我們看看 new Looper(quitAllowed)這步都干了什么:

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

可以看到舔庶,首先抛蚁,構(gòu)造方法是私有的,就是在別的類(lèi)中不能實(shí)例化 Looper 對(duì)象栖茉。方法中就是給兩個(gè)變量賦值篮绿,一個(gè)是我們的消息隊(duì)列 mQueue,一個(gè)是線(xiàn)程對(duì)象 mThread吕漂。此時(shí)亲配,我們的 Looper 對(duì)象有了消息隊(duì)列,而且獲取到了當(dāng)前線(xiàn)程惶凝。然后看下 loop() 方法:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();//1吼虎、獲取looper對(duì)象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//2、獲取消息隊(duì)列

    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // 取出隊(duì)列中的消息
        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
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);//消息不為空?qǐng)?zhí)行此處
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

這里就出現(xiàn)了我們一直想尋找的東西啦苍鲜。首先 1 處通過(guò) myLooper()方法獲取到存儲(chǔ)的 looper 對(duì)象:

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

然后 2 處通過(guò)獲取到的 Looper 對(duì)象獲取到對(duì)象中的消息隊(duì)列思灰。獲取到消息隊(duì)列后,通過(guò)一個(gè)不設(shè)參數(shù)的 for 循環(huán)方法不斷取出消息混滔,如果消息不為空洒疚,就執(zhí)行:

msg.target.dispatchMessage(msg);

msg我們都知道是 Message,那么 msg.target 是什么呢坯屿?哈哈油湖,還記得我們前面分析的 Handler 把消息存入消息隊(duì)列的過(guò)程嗎,Handler 的存儲(chǔ)方法:enqueueMessage 方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//這里把handler對(duì)象賦給了target
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

所以领跛,這個(gè) target 就是我們的 handler 對(duì)象乏德,next 方法取出消息后就調(diào)用了我們 handler 對(duì)象的 dispatchMessage() 方法。這里就是我們能不斷處理消息的關(guān)鍵: Looper 對(duì)象一直在幕后不斷的取出消息給我們的 handler 對(duì)象,然后由 handler 對(duì)象去處理消息喊括。

現(xiàn)在我們已經(jīng)清楚了消息隊(duì)列是什么時(shí)候構(gòu)造的胧瓜,消息是什么時(shí)候存入隊(duì)列的,消息是怎么取出的郑什,還差一步府喳,我們看看 handler 是怎么處理消息的,就是我們上面取出消息后執(zhí)行的方法:dispatchMessage

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

首先判斷 msg 的 callback 是否為空蘑拯,callback 是什么呢劫拢?是我們調(diào)用 post(Runnable r) 方法傳入的 runnable 對(duì)象:

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

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

不為空的時(shí)候用的是 post 系列方法,如果為空則用的是 send 系列方法强胰。第二步,判斷 mCallback 是否為空妹沙,mCallback 又是什么呢偶洋?

final Callback mCallback;
public interface Callback {
    public boolean handleMessage(Message msg);
}

看到這里,大家應(yīng)該明白了吧距糖,終于看到我們熟悉的處理消息的方法了:handleMessage玄窝。mCallback 是否為空對(duì)應(yīng)的是 Handler 類(lèi)的不同構(gòu)造方法。

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

我們?cè)趧?chuàng)建 handler 對(duì)象的時(shí)候悍引,可以直接傳入一個(gè)匿名內(nèi)部類(lèi)去實(shí)現(xiàn) handleMessage 方法恩脂;也可以構(gòu)造方法不傳參,然后去實(shí)現(xiàn) handlerMessage 方法趣斤。所以俩块,在 dispatchMessage 方法中,判斷如果 mCallback 為空的話(huà)浓领,執(zhí)行 handleMessage 方法玉凯。這樣,我們就走到了 handleMessage 方法联贩,就可以按照我們的業(yè)務(wù)邏輯去處理消息了漫仆。

最后畫(huà)一張圖總結(jié)一下整個(gè)流程:


image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市泪幌,隨后出現(xiàn)的幾起案子盲厌,更是在濱河造成了極大的恐慌,老刑警劉巖祸泪,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吗浩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡浴滴,警方通過(guò)查閱死者的電腦和手機(jī)拓萌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)升略,“玉大人微王,你說(shuō)我怎么就攤上這事屡限。” “怎么了炕倘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵钧大,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我罩旋,道長(zhǎng)啊央,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任涨醋,我火速辦了婚禮瓜饥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浴骂。我一直安慰自己乓土,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布溯警。 她就那樣靜靜地躺著趣苏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪梯轻。 梳的紋絲不亂的頭發(fā)上食磕,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音喳挑,去河邊找鬼彬伦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛伊诵,可吹牛的內(nèi)容都是我干的媚朦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼日戈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼询张!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起浙炼,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤份氧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后弯屈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜗帜,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年资厉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厅缺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖湘捎,靈堂內(nèi)的尸體忽然破棺而出诀豁,到底是詐尸還是另有隱情,我是刑警寧澤窥妇,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布舷胜,位于F島的核電站,受9級(jí)特大地震影響活翩,放射性物質(zhì)發(fā)生泄漏烹骨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一材泄、第九天 我趴在偏房一處隱蔽的房頂上張望沮焕。 院中可真熱鬧,春花似錦拉宗、人聲如沸遇汞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至络它,卻和暖如春族檬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背化戳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工单料, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人点楼。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓扫尖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親掠廓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子换怖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

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