Adnroid源碼學(xué)習(xí)筆記:Handler 線程間通訊

常見(jiàn)的使用Handler線程間通訊:

//主線程:
Handler handler = new Handler() {
@Override
    public void handleMessage(Message msg) {
        ...
    }
};

//子線程:
Message message = new Message();  
message.arg1 = 1;  
Bundle bundle = new Bundle();  
bundle.putString("test", "test");  
message.setData(bundle);  
handler.sendMessage(message); 

這類操作一般用于在子線程更新UI劈彪。在主線程創(chuàng)建一個(gè)handler竣蹦,重寫handlermessage方法,然后在子線程里發(fā)送消息沧奴,主線程里就會(huì)接受到消息痘括。這就是簡(jiǎn)單的線程間通訊。

如果在子線程創(chuàng)建handler對(duì)象則會(huì)報(bào)錯(cuò)滔吠。根據(jù)Log提示纲菌,子線程創(chuàng)建handler需要調(diào)用Looper.prepare() (在main函數(shù)中已經(jīng)調(diào)用了Looper.prepareMainLooper(),該方法內(nèi)會(huì)調(diào)起Looper.prepare())疮绷,Looper.loop()方法 翰舌。但是即使子線程調(diào)用Looper.prepare()創(chuàng)建Looper對(duì)象,這個(gè)Looper也是子線程的冬骚,不可以用于更新UI操作椅贱。

那到底Handler、Looper這幾個(gè)類之間是如何工作的呢只冻?我們從源頭看起庇麦,以下是Looper類的prepare()方法:

public final class Looper { 
    ...
    final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //threadLocal是線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,該類存儲(chǔ)了線程的所有數(shù)據(jù)信息喜德。

    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)); //創(chuàng)建一個(gè)Looper(Looper的構(gòu)造器里也創(chuàng)建了一個(gè)MessageQueue山橄,),將Looper與線程關(guān)聯(lián)起來(lái)
    }

    public static @Nullable Looper myLooper() { //下面會(huì)看到的舍悯,設(shè)置Handler類里的Looper時(shí)會(huì)調(diào)用該方法
        return sThreadLocal.get(); //獲得Looper對(duì)象
    }
    ...
}

當(dāng)handler傳輸message時(shí)航棱,不論是調(diào)用sendMessage(Message msg)還是sendMessageDelayed(),最后都會(huì)指向sendMessageAtTime()方法:

public class Handler {    
    ...
    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);
    }
    ...
}

mQueue即消息隊(duì)列睡雇,用于將收到的消息以隊(duì)列形式排列,提供出隊(duì)和入隊(duì)方法饮醇,該變量是Looper的成員變量入桂,在Handler創(chuàng)建時(shí)賦值給handler

public class Handler {
    ...
    final Looper mLooper;
    final MessageQueue mQueue;
    mLooper = Looper.myLooper();  //創(chuàng)建Handler前調(diào)用Looper.prepare()時(shí)定義并設(shè)置了Looper,這里調(diào)用Looper.myLooper()來(lái)獲得該Looper
    mQueue = mLooper.mQueue;
    ...
}

上面調(diào)用的enqueueMessage(queue, msg, uptimeMillis)方法作用是消息入隊(duì)

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; //把handler本身賦值給要入隊(duì)的消息驳阎,用來(lái)待會(huì)兒出隊(duì)使用
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到最后是由消息隊(duì)列queue調(diào)用自身MessageQueue類的入隊(duì)方法enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

mMessages是MessageQueue類的一個(gè)成員變量,用以記錄排在最前面的消息馁蒂,msg是我們傳入的message呵晚,msg.next是Message類的成員變量,可以理解成下一條消息沫屡。入列方法重點(diǎn)看這幾句:

Message p = mMessages;
msg.next = p;  //把mMessages賦值給新入隊(duì)的這條消息的next
mMessages = msg;  //把新入隊(duì)的消息賦值給mMessages

就像排隊(duì)一樣饵隙,msg是來(lái)插隊(duì)的,排第一的mMessages自愿排到msg的后面沮脖,并讓msg站到自己原來(lái)的位置上金矛,這樣就完成的msg的入隊(duì)操作,整個(gè)消息入隊(duì)操作是按照時(shí)間來(lái)排序的。至于出隊(duì)操作勺届,就在一開(kāi)始所提到的ActivityTread中的main方法里調(diào)用的Looper.loop()方法里:

public static void loop() {
    ...

    for (;;) {
        ...
        Message msg = queue.next(); //獲取下一條消息
        ...
        msg.target.dispatchMessage(msg); //傳遞消息
        ...
        msg.recycleUnchecked(); //清空狀態(tài)驶俊,循環(huán)往復(fù)
        }
    }
    ...
}

提煉出來(lái)就是在loop方法里一直死循環(huán),從MessageQueue消息隊(duì)列里使用next()方法獲得下一條消息免姿,next方法簡(jiǎn)單看就是:

Message msg = mMessages;
mMessages = msg.next;
msg.next = null;
return msg;

這就是簡(jiǎn)單的解釋消息出列饼酿,把排第一的消息作為方法的返回值,然后讓排第二的排到第一去胚膊。獲得消息后使用msg.target(上面入隊(duì)時(shí)賦值的handler)來(lái)傳遞消息:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg); //如果有callback參數(shù)則調(diào)用處理回調(diào)的方法
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); //將消息作為參數(shù)傳遞出去
    }
}

至此故俐,handler傳遞消息的整個(gè)流程走完。另外還有一個(gè)我們經(jīng)常用到handler的方法post:

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

private static Message getPostMessage(Runnable r) { //將runnable變成message自身的callback變量
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

可以看到紊婉,post的runnable參數(shù)經(jīng)過(guò)getPostMessage()方法最后被賦值給要傳遞下去的消息的callback這個(gè)變量药版,等到消息出列時(shí),如果消息帶有callback參數(shù)則調(diào)用處理回調(diào)的方法handleCallback(msg)

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

可以看到喻犁,不論是從sendMessage里發(fā)出的消息槽片,還是在post傳遞的runnable里執(zhí)行的代碼,最后都是殊途同歸株汉,都是在UI線程運(yùn)行的筐乳。最后總結(jié)一下吧,線程間通訊原理大概就是:

  1. Looper.prepare()創(chuàng)建Looper和MessageQueue乔妈,并與所在線程關(guān)聯(lián)
  2. Looper.loop()通過(guò)一個(gè)for死循環(huán)不斷對(duì)MessageQueue進(jìn)行輪詢
  3. 創(chuàng)建handler時(shí)蝙云,會(huì)把Looper和MessageQueue賦值給handler,將三者關(guān)聯(lián)起來(lái)路召。當(dāng)handler調(diào)用sendMessage傳遞消息勃刨,消息會(huì)被發(fā)送到Looper的消息隊(duì)列MessageQueue里
  4. 一旦loop()方法接收到消息波材,則將消息通過(guò)該消息攜帶的handler(msg.target)的handleMessage方法處理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市身隐,隨后出現(xiàn)的幾起案子廷区,更是在濱河造成了極大的恐慌,老刑警劉巖贾铝,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隙轻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡垢揩,警方通過(guò)查閱死者的電腦和手機(jī)玖绿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)叁巨,“玉大人斑匪,你說(shuō)我怎么就攤上這事》嫔祝” “怎么了蚀瘸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)庶橱。 經(jīng)常有香客問(wèn)我贮勃,道長(zhǎng),這世上最難降的妖魔是什么悬包? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任衙猪,我火速辦了婚禮,結(jié)果婚禮上布近,老公的妹妹穿的比我還像新娘垫释。我一直安慰自己,他們只是感情好撑瞧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布棵譬。 她就那樣靜靜地躺著,像睡著了一般预伺。 火紅的嫁衣襯著肌膚如雪订咸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天酬诀,我揣著相機(jī)與錄音脏嚷,去河邊找鬼。 笑死瞒御,一個(gè)胖子當(dāng)著我的面吹牛父叙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼趾唱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼涌乳!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起甜癞,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夕晓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后悠咱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蒸辆,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年析既,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吁朦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渡贾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雄右,到底是詐尸還是另有隱情空骚,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布擂仍,位于F島的核電站囤屹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逢渔。R本人自食惡果不足惜肋坚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肃廓。 院中可真熱鬧智厌,春花似錦、人聲如沸盲赊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哀蘑。三九已至诚卸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绘迁,已是汗流浹背合溺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缀台,地道東北人棠赛。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親恭朗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屏镊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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