溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

全面理解Handler第一步:理解消息隊(duì)列,手寫(xiě)消息隊(duì)列

發(fā)布時(shí)間:2020-07-16 13:50:28 來(lái)源:網(wǎng)絡(luò) 閱讀:1397 作者:MDove 欄目:移動(dòng)開(kāi)發(fā)

前言

Handler機(jī)制這個(gè)話(huà)題,算是爛大街的內(nèi)容。但是為什么偏偏重拿出來(lái)“炒一波冷飯”呢?因?yàn)樽约喊l(fā)現(xiàn)這“冷飯”好像吃的不是很明白。最近在思考幾個(gè)問(wèn)題,發(fā)現(xiàn)以之前對(duì)Handler機(jī)制的了解是在過(guò)于淺顯。什么問(wèn)題?

  • Handler機(jī)制存在的意義是什么?能否用其他方式替換?
  • Looper.loop();是一個(gè)死循環(huán),為什么沒(méi)有阻塞主線(xiàn)程?用什么樣的方式解決死循環(huán)的問(wèn)題?

如果透徹的了解Handler,以及線(xiàn)程的知識(shí)。是肯定不會(huì)有這些疑問(wèn)的,因?yàn)橐陨蠁?wèn)題本身就存在問(wèn)題。

就這倆個(gè)小問(wèn)題,就發(fā)現(xiàn)自己在學(xué)習(xí)道路上的不扎實(shí),所以這段時(shí)間重新理解了一下Handler。先預(yù)告一小下下,關(guān)于Handler的內(nèi)容將是一個(gè)系列文章,今天這一篇內(nèi)容重點(diǎn)在于Handler的理解,以及對(duì)消息隊(duì)列的思考。

正文

1、Handler機(jī)制為了什么?

我們都知道,在A(yíng)ndroid開(kāi)發(fā)中,無(wú)法在子線(xiàn)程中更新UI。

我們先思考一個(gè)問(wèn)題?為什么不能在子線(xiàn)程更新UI。如果看過(guò)View繪制的源碼,我們都知道不能在子線(xiàn)程更新UI的原因是:ViewRootImpl中有這么一個(gè)方法:

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

很明顯這是人為限制的一個(gè)操作。那我們?cè)谒伎迹瑸槭裁垂雀栝_(kāi)發(fā)Android系統(tǒng)時(shí)要這么限制?

其實(shí)不難推測(cè)出來(lái)。對(duì)于線(xiàn)程來(lái)說(shuō),我們都知道線(xiàn)程與線(xiàn)程之間是內(nèi)存共享的。所以如果某一時(shí)刻多個(gè)子線(xiàn)程同時(shí)去更新UI,那么對(duì)于繪制UI來(lái)說(shuō)便成為了一個(gè)不安全的操作。為了保證UI繪制的正確性,此時(shí)勢(shì)必要增加鎖,以同步的方式去控制這個(gè)問(wèn)題。

然而加鎖的方式顯然是一種犧牲性能的方式。

那么還有沒(méi)有其他方案呢?很顯然,最終谷歌選擇了只能在主線(xiàn)程更新UI,應(yīng)運(yùn)而生的Handler機(jī)制被創(chuàng)造出來(lái)了。但是它也不是什么新概念,說(shuō)白了就是消息隊(duì)列。實(shí)現(xiàn)原理也很簡(jiǎn)單:只允許一個(gè)線(xiàn)程(主線(xiàn)程)去更新UI,子線(xiàn)程將消息放到消息隊(duì)列中,由主線(xiàn)程去輪詢(xún)消息隊(duì)列,拿出消息并執(zhí)行。

這也就是我們的Handler機(jī)制。

2、消息隊(duì)列

這種單線(xiàn)程 + 消息隊(duì)列的模型其實(shí)應(yīng)用很廣。比如在Web前端之中,對(duì)于JavaScript來(lái)說(shuō),被設(shè)計(jì)時(shí)就決定了單線(xiàn)程模型。假設(shè)如果 Javascript 被設(shè)計(jì)為多線(xiàn)程的程序,那么操作 DOM 必然會(huì)涉及到資源的競(jìng)爭(zhēng)。此時(shí)只能加鎖,那么在 Client 端中跑這么一門(mén)語(yǔ)言的程序,資源消耗和性能都將是不樂(lè)觀(guān)的。但是如果設(shè)計(jì)成單線(xiàn)程,并輔以完善的異步隊(duì)列來(lái)實(shí)現(xiàn),那么運(yùn)行成本就會(huì)比多線(xiàn)程的設(shè)計(jì)要小很多了。

所以我們可以看到,Handler機(jī)制的思路可以說(shuō)是一個(gè)頗為常見(jiàn)的設(shè)計(jì)。

既然本質(zhì)是消息隊(duì)列,是不是我們自己也可以寫(xiě)一套消息隊(duì)列來(lái)感受一下Handler的設(shè)計(jì)思路呢?沒(méi)錯(cuò),接下來(lái)讓我們一起實(shí)現(xiàn)一套簡(jiǎn)單的消息隊(duì)列:

3、手寫(xiě)消息隊(duì)列

我們先來(lái)捋一捋思路:

Looper中創(chuàng)建了MessageQueue,Handler之中又通過(guò)ThreadLocal拿到主線(xiàn)程new出來(lái)的Looper,因此Handler就持有了MessageQueue,又因此線(xiàn)程間是內(nèi)存共享的,所以子線(xiàn)程可以通過(guò)Handler去往MessageQueue之中發(fā)送Message。

Looper.loop()死循環(huán)輪詢(xún)MessageQueue,拿到Message就回調(diào)其對(duì)應(yīng)的方法。

這樣整個(gè)Handler機(jī)制就運(yùn)轉(zhuǎn)起來(lái)了。接下來(lái)我們就依靠這個(gè)思路,實(shí)現(xiàn)自己的消息隊(duì)列,為了代碼更簡(jiǎn)潔,以及和Handler機(jī)制產(chǎn)生區(qū)別,我這里省略一些操作比如ThreadLocal之類(lèi)的。

3.1、代碼實(shí)現(xiàn)

代碼結(jié)束后有解釋


public class MainMQ {
private MyMessageQueue mMQ;
public static void main(String[] args) {
    new MainMQ().fun();
}

public void fun() {
    mMQ = new MyMessageQueue();
    System.out.println("當(dāng)前線(xiàn)程id:" + Thread.currentThread().getId());
    new Thread(new Runnable() {
        @Override
        public void run() {
            // 省略try-catch
            Thread.sleep(3000);
            mMQ.post(new MyMessage(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執(zhí)行此條消息的線(xiàn)程id:" + Thread.currentThread().getId());
                }
            }));
        }
    }).start();

    loop();
    System.out.println("死循環(huán)了,我永遠(yuǎn)也不被執(zhí)行~");
}

public void loop() {
    while (true) {
        // 省略try-catch
        MyMessage next = mMQ.next();
        if (next == null) {
            continue;
        }
        Runnable runnable = next.getRunnable();
        if (runnable != null) {
            runnable.run();

    }
}

}

這里沒(méi)有使用Looper這種思想,因?yàn)長(zhǎng)ooper本質(zhì)就是使用ThreadLocal創(chuàng)建一個(gè)和主線(xiàn)程唯一關(guān)聯(lián)的Looper實(shí)例,并以此保證MessageQueue的唯一性。

知道這個(gè)原理之后,這個(gè)demo。直接在主線(xiàn)程中new MessageQueue(),是同樣的道理,然后調(diào)用loop()方法死循環(huán)輪詢(xún)MessageQueue中的Message,不為null則執(zhí)行。

main()方法中start了一個(gè)子線(xiàn)程,然后sleep3秒后,往MessageQueue中post Message。效果很簡(jiǎn)單,我猜很多小伙伴已經(jīng)猜到了:

![](https://cache.yisu.com/upload/information/20200311/46/177936.jpg?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)

> 貼一下MessageQueue和Message
```java
public class MyMessageQueue {
    private final Queue<MyMessage> mQueue = new ArrayDeque<>();

    public void post(MyMessage message) {
        synchronized (this) {
            notify();
            mQueue.add(message);
        }
    }

    public MyMessage next() {
        while (true) {
            synchronized (this) {
                // 省略try-catch
                if (!mQueue.isEmpty()) {
                    return mQueue.poll();
                }
                wait(); 
            }
        }
    }
}
public class MyMessage {
    private Runnable mRunnable;

    public MyMessage(Runnable runnable) {
        mRunnable = runnable;
    }

    public Runnable getRunnable() {
        return mRunnable;
    }
}

3.2、思考存在的問(wèn)題

細(xì)心的小伙伴,可能有留意到loop()方法執(zhí)行后有這么一行代碼,然后效果圖中并沒(méi)有被打?。?/p>

System.out.println("死循環(huán)了,我永遠(yuǎn)也不被執(zhí)行~");

當(dāng)然這是必然的,畢竟我們的loop()是一個(gè)死循環(huán),后邊的代碼是不可能被執(zhí)行的。其實(shí)我們ActivityThread中調(diào)用了Looper.loop()之后,也沒(méi)有任何代碼了。

這里可能有小伙伴有疑問(wèn)了。loop()死循環(huán)了,那么我們?cè)谥骶€(xiàn)程中的生命周期回調(diào)怎么辦?豈不也不被執(zhí)行了?其實(shí)不然,通過(guò)上述的消息隊(duì)列,我們就能看出:我們?cè)谑謱?xiě)的這個(gè)demo中,loop啟動(dòng)前start了一個(gè)子線(xiàn)程,由子線(xiàn)程發(fā)送Message交由loop去執(zhí)行。保證了消息的流暢性。

那是不是我們Android中的loop也是這種思路?沒(méi)錯(cuò),main中的loop啟動(dòng)前,的確會(huì)起一個(gè)子線(xiàn)程......

不要著急,關(guān)于這個(gè)問(wèn)題,讓我們下篇文章再展開(kāi)~

結(jié)尾

今天這篇文章是全面理解Handler機(jī)制的第一篇,內(nèi)容大多并沒(méi)有直切到Handler機(jī)制本身,而是從外部去思考Handler的設(shè)計(jì)。而接下來(lái)的內(nèi)容則是對(duì)Handler內(nèi)部源碼進(jìn)行剖析了。

希望可以對(duì)小伙伴們有所幫助,如果感覺(jué)有收獲,歡迎點(diǎn)贊,收藏,關(guān)注呦~

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI