1. 程式人生 > >全面理解Handler第一步:理解消息隊列,手寫消息隊列

全面理解Handler第一步:理解消息隊列,手寫消息隊列

pro 主線程 思考 -c 常見 pub 為什麽不能 array handler機制

前言

Handler機制這個話題,算是爛大街的內容。但是為什麽偏偏重拿出來“炒一波冷飯”呢?因為自己發現這“冷飯”好像吃的不是很明白。最近在思考幾個問題,發現以之前對Handler機制的了解是在過於淺顯。什麽問題?

  • Handler機制存在的意義是什麽?能否用其他方式替換?
  • Looper.loop();是一個死循環,為什麽沒有阻塞主線程?用什麽樣的方式解決死循環的問題?

如果透徹的了解Handler,以及線程的知識。是肯定不會有這些疑問的,因為以上問題本身就存在問題。

就這倆個小問題,就發現自己在學習道路上的不紮實,所以這段時間重新理解了一下Handler。先預告一小下下,關於Handler的內容將是一個系列文章,今天這一篇內容重點在於Handler的理解,以及對消息隊列的思考。

正文

1、Handler機制為了什麽?

我們都知道,在Android開發中,無法在子線程中更新UI。

我們先思考一個問題?為什麽不能在子線程更新UI。如果看過View繪制的源碼,我們都知道不能在子線程更新UI的原因是:ViewRootImpl中有這麽一個方法:

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

很明顯這是人為限制的一個操作。那我們在思考,為什麽谷歌開發Android系統時要這麽限制?

其實不難推測出來。對於線程來說,我們都知道線程與線程之間是內存共享的。所以如果某一時刻多個子線程同時去更新UI,那麽對於繪制UI來說便成為了一個不安全的操作。為了保證UI繪制的正確性,此時勢必要增加鎖,以同步的方式去控制這個問題。

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

那麽還有沒有其他方案呢?很顯然,最終谷歌選擇了只能在主線程更新UI,應運而生的Handler機制被創造出來了。但是它也不是什麽新概念,說白了就是消息隊列。實現原理也很簡單:只允許一個線程(主線程)去更新UI,子線程將消息放到消息隊列中,由主線程去輪詢消息隊列,拿出消息並執行。

這也就是我們的Handler機制。

2、消息隊列

這種單線程 + 消息隊列的模型其實應用很廣。比如在Web前端之中,對於JavaScript來說,被設計時就決定了單線程模型。假設如果 Javascript 被設計為多線程的程序,那麽操作 DOM 必然會涉及到資源的競爭。此時只能加鎖,那麽在 Client 端中跑這麽一門語言的程序,資源消耗和性能都將是不樂觀的。但是如果設計成單線程,並輔以完善的異步隊列來實現,那麽運行成本就會比多線程的設計要小很多了。

所以我們可以看到,Handler機制的思路可以說是一個頗為常見的設計。

既然本質是消息隊列,是不是我們自己也可以寫一套消息隊列來感受一下Handler的設計思路呢?沒錯,接下來讓我們一起實現一套簡單的消息隊列:

3、手寫消息隊列

我們先來捋一捋思路:

Looper中創建了MessageQueue,Handler之中又通過ThreadLocal拿到主線程new出來的Looper,因此Handler就持有了MessageQueue,又因此線程間是內存共享的,所以子線程可以通過Handler去往MessageQueue之中發送Message。

Looper.loop()死循環輪詢MessageQueue,拿到Message就回調其對應的方法。

這樣整個Handler機制就運轉起來了。接下來我們就依靠這個思路,實現自己的消息隊列,為了代碼更簡潔,以及和Handler機制產生區別,我這裏省略一些操作比如ThreadLocal之類的。

3.1、代碼實現

代碼結束後有解釋


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

public void fun() {
    mMQ = new MyMessageQueue();
    System.out.println("當前線程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("執行此條消息的線程id:" + Thread.currentThread().getId());
                }
            }));
        }
    }).start();

    loop();
    System.out.println("死循環了,我永遠也不被執行~");
}

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

    }
}

}

這裏沒有使用Looper這種思想,因為Looper本質就是使用ThreadLocal創建一個和主線程唯一關聯的Looper實例,並以此保證MessageQueue的唯一性。

知道這個原理之後,這個demo。直接在主線程中new MessageQueue(),是同樣的道理,然後調用loop()方法死循環輪詢MessageQueue中的Message,不為null則執行。

main()方法中start了一個子線程,然後sleep3秒後,往MessageQueue中post Message。效果很簡單,我猜很多小夥伴已經猜到了:

![](http://i2.51cto.com/images/blog/201809/30/d9f45c6c1e12522dd0ad17e54bcb86d7.png?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、思考存在的問題

細心的小夥伴,可能有留意到loop()方法執行後有這麽一行代碼,然後效果圖中並沒有被打印:

System.out.println("死循環了,我永遠也不被執行~");

當然這是必然的,畢竟我們的loop()是一個死循環,後邊的代碼是不可能被執行的。其實我們ActivityThread中調用了Looper.loop()之後,也沒有任何代碼了。

這裏可能有小夥伴有疑問了。loop()死循環了,那麽我們在主線程中的生命周期回調怎麽辦?豈不也不被執行了?其實不然,通過上述的消息隊列,我們就能看出:我們在手寫的這個demo中,loop啟動前start了一個子線程,由子線程發送Message交由loop去執行。保證了消息的流暢性。

那是不是我們Android中的loop也是這種思路?沒錯,main中的loop啟動前,的確會起一個子線程......

不要著急,關於這個問題,讓我們下篇文章再展開~

結尾

今天這篇文章是全面理解Handler機制的第一篇,內容大多並沒有直切到Handler機制本身,而是從外部去思考Handler的設計。而接下來的內容則是對Handler內部源碼進行剖析了。

希望可以對小夥伴們有所幫助,如果感覺有收獲,歡迎點贊,收藏,關註呦~

全面理解Handler第一步:理解消息隊列,手寫消息隊列