您好,登錄后才能下訂單哦!
Handler機制這個話題,算是爛大街的內容。但是為什么偏偏重拿出來“炒一波冷飯”呢?因為自己發現這“冷飯”好像吃的不是很明白。最近在思考幾個問題,發現以之前對Handler機制的了解是在過于淺顯。什么問題?
如果透徹的了解Handler,以及線程的知識。是肯定不會有這些疑問的,因為以上問題本身就存在問題。
就這倆個小問題,就發現自己在學習道路上的不扎實,所以這段時間重新理解了一下Handler。先預告一小下下,關于Handler的內容將是一個系列文章,今天這一篇內容重點在于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機制。
這種單線程 + 消息隊列的模型其實應用很廣。比如在Web前端之中,對于JavaScript來說,被設計時就決定了單線程模型。假設如果 Javascript 被設計為多線程的程序,那么操作 DOM 必然會涉及到資源的競爭。此時只能加鎖,那么在 Client 端中跑這么一門語言的程序,資源消耗和性能都將是不樂觀的。但是如果設計成單線程,并輔以完善的異步隊列來實現,那么運行成本就會比多線程的設計要小很多了。
所以我們可以看到,Handler機制的思路可以說是一個頗為常見的設計。
既然本質是消息隊列,是不是我們自己也可以寫一套消息隊列來感受一下Handler的設計思路呢?沒錯,接下來讓我們一起實現一套簡單的消息隊列:
我們先來捋一捋思路:
Looper中創建了MessageQueue,Handler之中又通過ThreadLocal拿到主線程new出來的Looper,因此Handler就持有了MessageQueue,又因此線程間是內存共享的,所以子線程可以通過Handler去往MessageQueue之中發送Message。
Looper.loop()死循環輪詢MessageQueue,拿到Message就回調其對應的方法。
這樣整個Handler機制就運轉起來了。接下來我們就依靠這個思路,實現自己的消息隊列,為了代碼更簡潔,以及和Handler機制產生區別,我這里省略一些操作比如ThreadLocal之類的。
代碼結束后有解釋
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。效果很簡單,我猜很多小伙伴已經猜到了:
![](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;
}
}
細心的小伙伴,可能有留意到loop()方法執行后有這么一行代碼,然后效果圖中并沒有被打印:
System.out.println("死循環了,我永遠也不被執行~");
當然這是必然的,畢竟我們的loop()是一個死循環,后邊的代碼是不可能被執行的。其實我們ActivityThread中調用了Looper.loop()之后,也沒有任何代碼了。
這里可能有小伙伴有疑問了。loop()死循環了,那么我們在主線程中的生命周期回調怎么辦?豈不也不被執行了?其實不然,通過上述的消息隊列,我們就能看出:我們在手寫的這個demo中,loop啟動前start了一個子線程,由子線程發送Message交由loop去執行。保證了消息的流暢性。
那是不是我們Android中的loop也是這種思路?沒錯,main中的loop啟動前,的確會起一個子線程......
不要著急,關于這個問題,讓我們下篇文章再展開~
今天這篇文章是全面理解Handler機制的第一篇,內容大多并沒有直切到Handler機制本身,而是從外部去思考Handler的設計。而接下來的內容則是對Handler內部源碼進行剖析了。
希望可以對小伙伴們有所幫助,如果感覺有收獲,歡迎點贊,收藏,關注呦~
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。