Android消息機制系列(1)——Handler Looper Message源碼解析

一、基本概念:

在談到Android消息機制的時候,我們不可避免的要涉及要一下幾個概念:
1、Message 消息
2庸娱、MessageQueue 消息隊列
3、Handler 處理者谐算?它的實際作用包括發(fā)送消息(sendMessage),處理消息(handleMessage)
4归露、Looper 消息循環(huán)洲脂,或者輪詢器

從字面上來看,前兩者都比較好理解剧包。剩下的Handler恐锦、Looper,讓我們先重點理解一下Looper:

二疆液、Looper

Looper類的作用:
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

簡單翻譯過來就是:Looper用于管理一個線程的消息循環(huán)一铅。 默認(rèn)情況下,線程是沒有相關(guān)聯(lián)的Looper的堕油,需要調(diào)用Looper.prepare()來創(chuàng)建潘飘,創(chuàng)建完成之后肮之,Looper.loop()會不斷地循環(huán)來處理消息,直到循環(huán)停止卜录。一般情況下是和Handler來配合使用的戈擒。

使用Looper+Handler典型代碼例如:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare(); //為本線程創(chuàng)建一個Looper
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
            // process incoming messages here
            }
        };
        Looper.loop(); //消息循環(huán)
    }
}

Looper重要屬性:

// sThreadLocal.get() will return null unless you've called prepare()
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper;  // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;

其中,sThreadLocal是一個ThreadLocal對象艰毒,可以在一個線程中存儲變量筐高。

Looper的prepare()方法:

public static void prepare() {
    prepare(true);
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) { //一個線程只能對應(yīng)一個Looper實例
    throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); //new一個Looper的實例保存在ThreadLocal中
}

可以看到,prepare()方法將new一個Looper的實例丑瞧,同時將該Looper實例放入了ThreadLocal柑土。并且當(dāng)?shù)诙握{(diào)用時報錯,說明prepare()方法不能被調(diào)用兩次绊汹,同時也保證了一個線程中只有一個Looper實例稽屏。

Looper的構(gòu)造函數(shù):

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

Looper構(gòu)造函數(shù)中創(chuàng)建一個消息隊列MessageQueue。同時也可以看到灸促,一個Looper實例對應(yīng)一個線程诫欠、一個MessageQueue

Looper的loop()方法:

public static void loop() {
    // myLooper() 返回的是sThreadLocal.get(),也就是sThreadLocal存儲的Looper實例
    final Looper me = myLooper(); 
    if (me == null) {
        // 可以看到這里浴栽,如果在線程中沒有調(diào)用Looper.prepare()的話Looper實例是空的
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on    this thread.");
    }
    final MessageQueue queue = me.mQueue; //拿到Looper實例對應(yīng)的消息隊列
    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) { //不斷地從消息隊列中取消息來進(jìn)行處理
        Message msg = queue.next(); // might block
        if (msg == null) { // No message indicates that the message queue is quitting.
            return;
    }
    ... ...
    msg.target.dispatchMessage(msg); //真正處理消息的地方
    ... ...
    // Make sure that during the course of dispatching the
    // identity of the thread wasn't corrupted.
    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();
    }
}

可以看到荒叼,方法中無限循環(huán)體里面,不斷地通過queue.next()來取出準(zhǔn)備處理的消息典鸡。而真正處理消息的地方是在這一句:msg.target.dispatchMessage(msg)被廓。target是什么?我們可以從Message源碼中可以看到萝玷,target是一個Handler嫁乘,也就是說,真正處理消息是該消息對應(yīng)的Handler實例的dispatchMessage(msg)方法中球碉。

小結(jié):
1蜓斧、Looper用于管理一個線程的消息循環(huán)。
2睁冬、由于默認(rèn)情況下挎春,線程是沒有相關(guān)聯(lián)的Looper的,因此必須調(diào)用Looper.prepare()來創(chuàng)建豆拨。
3直奋、Looper.prepare()方法,會使得線程綁定唯一一個Looper實例
4施禾、Looper創(chuàng)建時也會創(chuàng)建一個消息隊列MessageQueue脚线。
2、Looper.loop()方法弥搞,不斷從MessageQueue.next()中去取出準(zhǔn)備處理的消息邮绿,交給消息的對應(yīng)的handler的dispatchMessage去處理渠旁。

簡言之,Looper提供了存儲消息的隊列(MessageQueue)斯碌,同時不斷循環(huán)取出準(zhǔn)備處理的消息一死。那么誰來發(fā)送消息和真正處理消息呢?——當(dāng)然是我們熟悉的Handler了傻唾。

三投慈、Handler:

從第一部分我們可以推測出,Handler的兩個重要作用:
1冠骄、發(fā)消息到MessageQueue中
2伪煤、接受Looper分發(fā)的消息處理任務(wù),真正處理消息

那么Handler是怎么和一個線程的MessageQueue凛辣、Looper實例關(guān)聯(lián)上的呢抱既?

這個要看一下Handler是怎樣被new出來的倦微。具體我會另開文分析辛辨,這里以開篇的例子來分析,當(dāng)使用new Handler()來創(chuàng)建一個Handler的情況(意思是還有其他情況):

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

最后是調(diào)用了這個:

  public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() 
            || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
            klass.getCanonicalName());
        }
    }
    mLooper = Looper.myLooper();  //這里闽烙,關(guān)聯(lián)上了當(dāng)前線程的Looper實例
    if (mLooper == null) {
        throw new RuntimeException( 
        // 在一個線程里面蝗敢,創(chuàng)建handler之前必須調(diào)用Looper.prepare()
        "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue; // 這里捷泞,取得當(dāng)前線程的Looper實例的消息隊列
    mCallback = callback;
    mAsynchronous = async;
}

可以看到,在這種情況下寿谴,在構(gòu)造函數(shù)里面锁右,通過Looper.myLooper()獲取了當(dāng)前線程保存的Looper實例,然后又獲取了這個Looper實例中保存的消息隊列MessageQueue讶泰。Handler實例就是這樣和一個線程的MessageQueue咏瑟、Looper實例一一關(guān)聯(lián)上的。

這里的當(dāng)前線程痪署,是指創(chuàng)建Handler實例所使用的線程码泞。

接下來就簡單了,用Handler發(fā)送一條消息狼犯,不論使用何種方法余寥,最終都會調(diào)到這個函數(shù):

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue; //當(dāng)前線程對應(yīng)Looper實例的消息隊列
    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);
}

然后調(diào)用了這個函數(shù):

private boolean enqueueMessage(MessageQueue queue, Message msg,long     uptimeMillis) {
    msg.target =this; //將消息的target設(shè)為當(dāng)前的Handler實例
    if(mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis); //將消息插入到消息隊列中去
}

重要的是這句:msg.target = this; //將消息的target設(shè)為當(dāng)前的Handler實例

還記得第一部分結(jié)尾處,loop()方法中取出一條要處理的消息辜王,然后調(diào)用msg.target.dispatchMessage(msg)嘛?那么現(xiàn)在我們知道了罐孝,msg的target就是在發(fā)這條消息的時候設(shè)置上的呐馆。以保證后續(xù)處理消息的時候,能找到處理這條消息的Handler莲兢。

queue.enqueueMessage(msg, uptimeMillis); //這句將設(shè)置好target等屬性的消息放到消息隊列中去

終于汹来,讓我們看一下msg.target.dispatchMessage(msg)的dispatchMessage()函數(shù):

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

public void handleMessage(Message msg) {
}

這個方法最終調(diào)用我們在創(chuàng)建Handler時重寫的handleMessage()方法或者callback來進(jìn)行消息處理续膳;

而handleMessage()我們就很熟悉了,每個創(chuàng)建Handler的都要重寫handleMessage()方法收班,根據(jù)msg.what來處理消息坟岔,例如:

private Handler mHandler = newHandler() {
    public void handleMessage(android.os.Message msg) {
        switch(msg.what) {
            case:
            break;
            default:
            break;
        }
     };
};

三、總結(jié)

1摔桦、默認(rèn)情況下線程是沒有Looper實例的社付,Looper.prepare()為線程關(guān)聯(lián)到唯一的Looper實例,以及Looper實例的MessageQueue邻耕。

2鸥咖、Handler被創(chuàng)建時,會關(guān)聯(lián)到“某個線程”的唯一的Looper實例和MessageQueue兄世;Handler的主要作用是將處理消息切換到“這個線程”來處理啼辣。

3、當(dāng)通過Handler發(fā)送一條Message時御滩,該消息的target就被設(shè)為這個Handler實例(this)

4鸥拧、Handler發(fā)送一條消息,實際上是將這條消息插入到對應(yīng)的消息隊列MessageQueue中

5削解、線程對應(yīng)的Looper實例loop()方法來處理消息時富弦,會根據(jù)這條消息的target(也就是Handler實例),回調(diào)Handler的dispatchMessage()方法進(jìn)行處理钠绍。dispatchMessage()方法最終調(diào)用我們在創(chuàng)建Handler時重寫的handleMessage()方法或者callback舆声。

這里面,比較重要的是柳爽,明確Handler被創(chuàng)建時媳握,和哪個線程或者和哪個線程的Looper相關(guān)聯(lián),這將決定了任務(wù)最后在哪個線程執(zhí)行磷脯;

這里面有個特殊情況蛾找,就是在主線程創(chuàng)建Handler時不需要調(diào)用prepare()和loop(),因為這部分工作Android已經(jīng)幫我們做過了赵誓,具體可以看一下ActivityThread的代碼打毛。

簡單的說,一條消息從發(fā)出到處理經(jīng)歷了:

[Handler發(fā)送消息] -> [消息進(jìn)入Looper的MessageQueue隊列] -> [loop循環(huán)從MessageQueue取出要處理的消息] -> [Handler處理消息]


Reference:

Handler源代碼

Looper源代碼

Android 異步消息處理機制 讓你深入理解 Looper俩功、Handler幻枉、Message三者關(guān)系

Android異步消息處理機制完全解析,帶你從源碼的角度徹底理解

Android的消息機制之ThreadLocal的工作原理

Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死诡蜓?

Android異步消息處理機制詳解及源碼分析

Handler常見用法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末熬甫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔓罚,更是在濱河造成了極大的恐慌椿肩,老刑警劉巖瞻颂,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異郑象,居然都是意外死亡贡这,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門厂榛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盖矫,“玉大人,你說我怎么就攤上這事噪沙×侗耄” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵正歼,是天一觀的道長辐马。 經(jīng)常有香客問我,道長局义,這世上最難降的妖魔是什么喜爷? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮萄唇,結(jié)果婚禮上檩帐,老公的妹妹穿的比我還像新娘。我一直安慰自己另萤,他們只是感情好湃密,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著四敞,像睡著了一般泛源。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忿危,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天达箍,我揣著相機與錄音,去河邊找鬼铺厨。 笑死缎玫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的解滓。 我是一名探鬼主播赃磨,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洼裤!你這毒婦竟也來了邻辉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恩沛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缕减,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡雷客,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桥狡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搅裙。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖裹芝,靈堂內(nèi)的尸體忽然破棺而出部逮,到底是詐尸還是另有隱情,我是刑警寧澤嫂易,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布兄朋,位于F島的核電站,受9級特大地震影響怜械,放射性物質(zhì)發(fā)生泄漏颅和。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一缕允、第九天 我趴在偏房一處隱蔽的房頂上張望峡扩。 院中可真熱鬧,春花似錦障本、人聲如沸教届。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽案训。三九已至,卻和暖如春寄悯,著一層夾襖步出監(jiān)牢的瞬間萤衰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工猜旬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脆栋,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓洒擦,卻偏偏與公主長得像椿争,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子熟嫩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

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