消息機(jī)制Handler

<h3><b>基本概述</b></h3>

在Android開發(fā)中提到消息機(jī)制應(yīng)該所有人都不陌生,但是估計(jì)也很少有人能把消息機(jī)制詳細(xì)地說出個(gè)所以然來,我們?cè)陂_發(fā)過程中,有很多技術(shù)痛點(diǎn),就是明明很多時(shí)候我們?cè)谟玫臇|西,我們卻不知道是為什么,它具體是個(gè)什么東西,很多時(shí)候我們只在乎它如何被拿來用,而不去關(guān)心更深層次的東西,也許這就是初中級(jí)向上進(jìn)階的阻礙吧。還好,我意識(shí)到了這一點(diǎn),盡力去了解更多事實(shí)的真相...一不小心說了這么多廢話,言歸正傳吧。

在Android中,我們提到的消息機(jī)制常常指的就是Handler機(jī)制,Handler是Android中的上層接口,在開發(fā)的過程中,我們可以利用Handler將一件事情切換到Handler所在的線程中去執(zhí)行(好好理解這句話)。為什么會(huì)有這樣的機(jī)制出現(xiàn)?我們來考慮一下,在實(shí)際的操作中,我們發(fā)現(xiàn),Handler機(jī)制的運(yùn)用主要是在UI更新上,當(dāng)我們需要做一個(gè)耗時(shí)操作時(shí),我們不可能在mainThread中做這個(gè)事情,因?yàn)锳NR的存在,這時(shí)我們需要開啟一個(gè)新的線程去做這個(gè)事情,當(dāng)事件完成后再通過Handler去告訴mainThread現(xiàn)在可以更新UI了。這就是對(duì)消息機(jī)制的一個(gè)很寬泛的描述。這時(shí)候,我們應(yīng)該考慮一個(gè)問題?為什么我們不干脆在開啟的新線程中更新UI呢,應(yīng)該很多人會(huì)說是因?yàn)锳ndroid不允許這樣做?那么, 為什么不允許呢?

如果不限制UI的更新規(guī)則(即可在任何線程中操作UI)會(huì)帶來什么樣的壞處?如果所有線程都可以改變UI,那我們?nèi)绾伪WCUI朝著我們預(yù)期的方向發(fā)展?這就像是共享內(nèi)存一樣,同時(shí)的操作會(huì)讓UI的狀態(tài)變得不可預(yù)期。那么,如果加上鎖機(jī)制呢?這樣必定會(huì)大大降低UI的訪問效率,因?yàn)殒i機(jī)制會(huì)阻塞線程,就會(huì)讓我們的應(yīng)用在很多情況下看起來像失了智一樣,卡頓嚴(yán)重,所以Handler機(jī)制油然而生了。


<h3><b>工作原理</b></h3>

Android的Handler機(jī)制它的工作原理是怎樣的呢?我們?cè)诰€程中創(chuàng)建一個(gè)Handler對(duì)象,當(dāng)使用它的時(shí)候會(huì)發(fā)現(xiàn)系統(tǒng)會(huì)報(bào)這樣的錯(cuò):

can not create handler inside thread that has not called Looper.prepare();

它描述了在一個(gè)線程中我們不能在沒有Looper的前提下去使用Handler,也就是說Handler機(jī)制的實(shí)現(xiàn)需要Looper這個(gè)東西,那么Looper是啥呢?我們可以通過Looper.prepare()在當(dāng)前線程中創(chuàng)建一個(gè)Looper對(duì)象,當(dāng)我們查閱源碼的時(shí)候可以發(fā)現(xiàn),Looper.prepare()做了什么,實(shí)際上它創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)。Looper相關(guān)源碼如下:

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

public static void prepare(boolean quitAllowed){
    if( sThreadLocal.get()!=null ){
        ...
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

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

在創(chuàng)建了Looper之后,我們不僅僅得到了looper對(duì)象,還得到了一個(gè)消息隊(duì)列messageQueue,這時(shí)候才構(gòu)建了一個(gè)完整的Handler機(jī)制。它們是如何分工的。

線程內(nèi)部負(fù)責(zé)處理業(yè)務(wù)邏輯,looper的作用負(fù)責(zé)消息的存取查閱,其中messageQueue是消息的存儲(chǔ)結(jié)構(gòu),它是單鏈表形式,looper負(fù)責(zé)消息的輪詢,handler負(fù)責(zé)發(fā)送和處理消息。looper、線程、messageQueue是一一對(duì)應(yīng)的,在不同的線程中它們各自有著對(duì)應(yīng)的模塊。我們還能看到創(chuàng)建的looper對(duì)象被放入了ThreadLocal中去,這樣的作用是,各個(gè)線程中的Looper對(duì)象是相對(duì)獨(dú)立,誰也影響不到誰,如何達(dá)到這個(gè)效果的呢?我們就來看一下ThreadLocal這個(gè)類。

在實(shí)際使用的過程中我們發(fā)現(xiàn),運(yùn)行在主線程的Handler我們不需要事先為它創(chuàng)建一個(gè)Looper來進(jìn)行消息管理,這是為什么?這是因?yàn)橄到y(tǒng)早已經(jīng)默認(rèn)給主線程創(chuàng)建了Looper對(duì)象。


<h3><b>ThreadLocal類</b></h3>

從它的名字來看,它似乎是個(gè)什么線程,其實(shí)不然ThreadLocal不是線程但和線程有關(guān),具體的來講,它是一個(gè)和線程相關(guān)的存儲(chǔ)方式。它是線程內(nèi)部的存儲(chǔ)方式,只有在當(dāng)前線程內(nèi)可以獲得這個(gè)值。它的內(nèi)部是如何實(shí)現(xiàn)的?我們通過查看源碼不難發(fā)現(xiàn),它是一個(gè)類似于數(shù)組且以key-value方式存儲(chǔ)的一種數(shù)據(jù)格式。

ThreadLocal內(nèi)部存儲(chǔ)是數(shù)組結(jié)構(gòu),它以當(dāng)前線程作為Key來區(qū)別各個(gè)線程之間的數(shù)據(jù),查看源碼可以發(fā)現(xiàn),在偶數(shù)位它用來存放當(dāng)前線程,在此的后一位來存放具體的數(shù)據(jù)。所以通過不同的線程可以定位到不同位置的key,然后向后延一位就能得到存儲(chǔ)的數(shù)據(jù),這就是ThreadLocal能做到各個(gè)線程間互不干涉的所在。它最典型的應(yīng)用就是Handler機(jī)制中Looper的構(gòu)建。

我們下面來舉個(gè)很簡單的例子,我們來看偽代碼:

private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>;

// 主線程
mThreadLocal.set(true);
Log.i("threadLocal", mThreadLocal.get());

// 線程1
new Thread("Thread1"){
    @ovrride
    public void run(){
        mThreadLocal.set(false);
        Log.i("threadLocal", mThreadLocal.get());
    }
}

// 線程2
new Thread("Thread2"){
    @ovrride
    public void run(){
        Log("threadLocal", mThreadLocal.get());
    }
}

上面的例子中,我們分別在主線程,線程1,線程2中對(duì)mThreadLocal變量進(jìn)行了操作,并且在這三個(gè)地方分別打印該變量的值,如果是普通的變量,那么我想這些打印的值恐怕只能靠猜了...,而現(xiàn)在,我們了解了ThreadLocal的存儲(chǔ)原理,不難分析出各個(gè)位置打印的值。

因?yàn)樗鼈儾粫?huì)相互影響, 所以在主線程中設(shè)置了true,那么我以主線程去保存數(shù)據(jù),我在打印的時(shí)候還以主線程為key去獲取對(duì)應(yīng)的值,這個(gè)值一定是當(dāng)時(shí)存儲(chǔ)的值true。同樣的判斷,在線程1中打印的值是false。而在線程2中,我們從來沒有在對(duì)應(yīng)的存儲(chǔ)空間上為線程2來開辟存儲(chǔ)空間,所以它的值應(yīng)該是null。


<h3><b>消息隊(duì)列</b></h3>

我們?cè)谏厦娴慕榻B中可以知道,Looper對(duì)象是在其內(nèi)部創(chuàng)建了一個(gè)消息隊(duì)列用來存儲(chǔ)消息的,所以在了解Looper之前,我們有必要知道消息隊(duì)列的一些事情。

消息隊(duì)列在Android中指的就是MessageQueue,從它的名字上來看是個(gè)隊(duì)列,實(shí)際上它的內(nèi)部實(shí)現(xiàn)不是隊(duì)列,而是單鏈表結(jié)構(gòu)的,它提供了消息機(jī)制中消息的插入和讀取。

說點(diǎn)其他的,我們應(yīng)該知道鏈表的概念,鏈表的概念是從C語言中引用而來的,它表示每個(gè)消息是相連的,每一個(gè)消息在鏈表中稱作一個(gè)節(jié)點(diǎn),節(jié)點(diǎn)不僅僅保存數(shù)據(jù),它還保存了一個(gè)指針用來指向下一個(gè)節(jié)點(diǎn)的地址,所以它的優(yōu)點(diǎn)在可以高效率地插入、刪除。因?yàn)橄㈥?duì)列常用的操作就是插入消息和取出消息,而取出消息實(shí)質(zhì)上就是將該節(jié)點(diǎn)從數(shù)據(jù)結(jié)構(gòu)中剔除,所以消息隊(duì)列會(huì)選取鏈表結(jié)構(gòu)來存儲(chǔ)消息。


<h3><b>Looper</b></h3>

我們從上面的分析中可以知道,Handler的工作需要Looper,也知道可以通過Looper.prepare()為Handler創(chuàng)建一個(gè)Looper。我們通過Looper.loop()開啟Looper輪詢,此時(shí)Looper會(huì)不停的輪詢消息隊(duì)列中的數(shù)據(jù),一有消息通過Handler發(fā)出時(shí),Looper就能捕獲這個(gè)消息,并通過這個(gè)函數(shù)來處理:

msg.target.dispatchMessage(msg)

其實(shí),msg.target實(shí)際上就是發(fā)出消息的那個(gè)Handler,調(diào)用Handler的dispatchMessage(msg)函數(shù)來最終處理這個(gè)消息。

每個(gè)線程都有自己的Looper,非主線程我們是不可預(yù)期的,但是主線程永遠(yuǎn)只有一個(gè),所以我們可以通過靜態(tài)方法Looper.getMainLooper()隨時(shí)獲取主線程的Looper。

我們說過,如果消息隊(duì)列中不存在消息時(shí),Looper將會(huì)處于阻塞狀態(tài),這無疑增加了系統(tǒng)的開銷,所以當(dāng)我們確定不再有消息傳遞進(jìn)來時(shí),可以通過Looper.quit()或Looper.quitSately()方法來退出Looper。前者是一調(diào)用立馬退出Looper,后者是需要將現(xiàn)有的消息處理完之后再退出Looper,相對(duì)來講后者更有責(zé)任心一些,但這也不一定就是必要的。


<h3><b>Handler</b></h3>

說了這么多是時(shí)候來認(rèn)識(shí)最后的東西了。從以上種種我們的介紹,我們可以很清晰地知道,在Handler消息機(jī)制中,Handler做扮演的角色的作用就是發(fā)送和接收消息。

我們經(jīng)常性的操作來用語言描述一次。我們?cè)谥骶€程中定了一個(gè)Handler,由于系統(tǒng)已經(jīng)默認(rèn)幫我們把Looper創(chuàng)建好了,所以我們不需要再去創(chuàng)建Looper。這時(shí)候我們需要向服務(wù)器進(jìn)行一個(gè)耗時(shí)的請(qǐng)求,那么我們開啟了線程a去請(qǐng)求數(shù)據(jù),當(dāng)數(shù)據(jù)請(qǐng)求完成后,我們希望數(shù)據(jù)可以在UI上(即主線程)上展示,所以我們利用Handler將線程a取到的數(shù)據(jù)發(fā)送出去。Looper循環(huán)機(jī)制接收并處理了該消息,最終該消息交由Handler所在的線程(主線程)處理。我們通過Handler機(jī)制將UI操作從線程a中搬到了主線程中去。

那么Handler是如何處理消息的?我們看到 msg.target.dispatchMessage(msg) 這個(gè)函數(shù),略過長篇大論的判斷和callback,在日常開發(fā)中,創(chuàng)建Handler最常用的就是派生一個(gè)Handler的子類,并重寫handlerMessage()方法來處理具體的消息。


<h3><b>進(jìn)一步認(rèn)清Handler</b></h3>

當(dāng)我們了解以上Android的消息機(jī)制后,我們不禁沾沾自喜,偶爾看到了這樣的代碼:

new Handler().postDelayed(new Runnable(){
    @Override
    public void run(){
        Toast.makeText(mContext, "hellow", Toast.LENGHT_LONG).show();
    }
}, 1000);

乍一看,咦?在非主線程中更新UI?竟然不報(bào)錯(cuò)...,好吧我們來分析一下吧。首先我們來看Handler的構(gòu)造函數(shù)。

1. Handler的構(gòu)造函數(shù)

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

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

此時(shí)Handler做了一些準(zhǔn)備工作,比如準(zhǔn)備Looper等,此時(shí)一個(gè)變量mCallback==null。此時(shí)我們將Looper中的隊(duì)列和Handler中的隊(duì)列關(guān)聯(lián)起來。

2. 我們?cè)賮砜磒ostDelayed()函數(shù)

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

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

很顯然我們將Runnable這個(gè)線程設(shè)置為了Message的callback屬性。我們經(jīng)過跟蹤,依次調(diào)用了這些方法。

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; 這句話看出msg.target就是Handler,也就是說此時(shí)MessageQueue和Handler發(fā)生了聯(lián)系,這個(gè)enqueueMessage方法很顯然就是我們?cè)贛essageQueue中講的入列方法,即將消息加入到了對(duì)應(yīng)線程的Looper消息隊(duì)列中去了,此時(shí)Looper獲取到了這個(gè)消息,并且我們上面講Looper時(shí)提到了Looper調(diào)用這個(gè)函數(shù)來處理消息msg.target.dispatchMessage(msg),msg.target是什么?就是Handler啊,所以這個(gè)消息由Handler的dispatchMessage(msg)函數(shù)來處理消息。

3. dispatchMessage()方法

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

通過步驟2可以知道,msg.callback!=null,而是runnable。所以執(zhí)行handlerCallback(msg);

4. handlerCallback(msg)方法

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

這樣,就會(huì)執(zhí)行我們?cè)贏ctivity 的Runnable 中的run 方法了,也就是顯示Toast。所以為什么這個(gè)Toast是沒問題的呢?

這是因?yàn)?,我們將Runnable設(shè)置給了Message的屬性callback,這個(gè)callback連同Message被傳遞到Handler所在的線程(即UI線程)中去了,所以它實(shí)際上試運(yùn)行在UI線程上的。


至此,我們應(yīng)該對(duì)Handler機(jī)制有了比較深刻的理解了。

如果有你認(rèn)為不恰當(dāng)?shù)拿枋觯环亮粞愿嬖V我...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容