亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android Handler機制的工作原理詳析

2019-10-21 21:25:55
字體:
來源:轉載
供稿:網友

寫在前面

上一次寫完Binder學習筆記之后,再去看一遍Activity的啟動流程,因為了解了Binder的基本原理,這次看印象會更深一點,學習效果也比以前好很多。本來打算直接來寫Activity的啟動流程的,但總覺得Handler也需要寫一下,知道Handler和Binder的原理后,再去看Activity的啟動流程,應該也沒什么問題了。雖然網上已經有很多Handler相關的文章了,而且Handler機制的上層原理也并不難,還是決定寫一下,因為我想構建自己的知識體系。也希望給看我博客的朋友們一個無縫銜接的閱讀體驗。

Handler機制涉及到的類主要有Handler、Message、Looper、MessageQueue、ThreadLocal等。雖然我們最熟悉的是Handler和Message這兩個類,但是在我們開始可以使用Handler之前,Looper是為我們做了一些事情的。

本文的源碼是基于android-28的

Looper

在使用Handler之前,我們必須得初始化Looper,并讓Looper跑起來。

Looper.prepare();...Looper.loop();

執行上面兩條語句之后,Looper就可以跑起來了。先來看看對應的源碼:

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));}private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}

必須保證一個線程中有且只有一個Looper對象,所以在初始化Looper的時候,會檢查當前線程有沒有Looper對象。Looper的初始化會創建一個MessageQueue。創建完Looper后會放到ThreadLocal中去,關于ThreadLocal,后面會說到。

public static void loop() { // 判斷當前線程有沒有初始化Looper final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }  final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }  ... final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { // target指的是Handler msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } }  ... msg.recycleUnchecked(); }}

方法比較長,所以只把最核心的代碼放了出來。省略掉的代碼中有一個比較有意思的:我們可以指定一個閾值比如說200,當Message的處理超過200ms時,就會輸出Log。這可以在開發中幫助我們發現一些潛在的性能問題??上У氖?,設置閾值的方法是隱藏的,無法直接調用,所以這里就不放出代碼了,感興趣的朋友自己翻一下源碼吧。

簡化后的代碼可以看出邏輯十分簡單,可以說Looper在當中扮演著搬磚工的角色,從MessageQueue中取出Message,然后交給Handler去分發,再去MessageQueue中取出Message...無窮無盡,就像愚公移山一樣。

看到這里,應該多多少少會覺得有點不對勁,因為這里是一個死循環,按道理來說會一直占著CPU資源的,并且消息也總有處理完的時候,難道處理完就從消息隊列返回Null,然后Looper結束嗎?顯然不是,注意看注釋might block。

MessageQueue

答案就在MessageQueue里面,直接來看一下next():

Message next() { ... int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; }}

代碼有點長,這次不打算省略掉一些了,因為這里面還有一個小彩蛋。

方法中最重要的應該就是這一行了

nativePollOnce(ptr, nextPollTimeoutMillis);

簡單來說,當nextPollTimeoutMillis == -1時,掛起當前線程,釋放CPU資源,當nextPollTimeoutMillis >= 0時會延時指定的時間激活一次線程,讓代碼繼續執行下去。這里涉及到了底層的pipe管道和epoll機制,就不再講下去了(其實是因為講不下去了)。這也就可以回答上面的問題了,當沒有消息的時候只需要讓線程掛起就行了,這樣可以保證不占用CPU資源的同時保住Looper的死循環。

然后我們來看消息是如何取出來的。MessageQueue中有一個Message,Message類中又有一個Message成員next,可以看出Message是一個單鏈表結構。消息的順序是根據時間先后順序排列的。一般來說,我們要取的Message就是第一個(這里先不考慮異步消息,關于異步消息以后會講到的,又成功給自己挖了一個坑哈哈),如果當前時間大于等于Message中指定的時間,那么將消息取出來,返回給Looper。由于此時nextPollTimeoutMillis的值為0,所以當前面的消息處理完之后,Looper就又來取消息了。

如果當前的時間小于Message中指定的時間,那么設置nextPollTimeoutMillis值以便下次喚醒。還有另外一種當前已經沒有消息了,nextPollTimeoutMillis會被設置為-1,也就是掛起線程。別急,還沒那么快呢,接著往下看。

緊接著的邏輯是判斷當前有沒有IdleHandler,沒有的話就continue,該掛起就掛起,該延時就延時,有IdleHandler的話會執行它的queueIdle()方法。這個IdleHandler是干什么的呢?從名字應該也能猜出個一二來,這里就不再展開講了。關于它的一些妙用可以看我之前寫的Android 啟動優化之延時加載。執行完queueIdle()方法后,會將nextPollTimeoutMillis置為0,重新看一下消息隊列中有沒有新的消息。

Handler

上面將取消息的流程都講清楚了,萬事俱備,就差往消息隊列中添加消息了,該我們最熟悉的Handler出場了。Handler往隊列中添加消息,主要有兩種方式:

Handler.sendXXX();Handler.postXXX();

第一種主要是發送Message,第二種是Runnable。無論是哪種方式,最終都會進入到MessageQueue的enqueueMessage()方法。

boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { ... 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;}

一般情況下,我們通過Handler發送消息的時候,會通過SystemClock.uptimeMillis()獲取一個開機時間,然后MessageQueue就會根據這個時間來對Message進行排序。所以enqueueMessage()方法中就分了兩種情況,一種是直接可以在隊頭插入的。一種是排在中間,需要遍歷一下,然后尋一個合適的坑插入。when == 0對應的是Handler的sendMessageAtFrontOfQueue()和postAtFrontOfQueue()方法。needWake的作用是根據情況喚醒Looper線程。

上面有一點還沒有講,就是Looper從MessageQueue中取出Message后,會交由Handler進行消息的分發。

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

優先級順序是Message自帶的callback,接著是Handler自帶的callback,最后才是handleMessage()這個回調。

ThreadLocal

還記得Looper中有一個ThreadLocal吧,把它放到最后來講是因為它可以單獨拿出來講,不想在上面干擾到整個流程。

ThreadLocal是一個數據存儲類,它最神奇的地方就是明明是同一個ThreadLocal對象,但是在不同線程中可以存儲不同的對象,比如說在線程A中存儲了"Hello",而在線程B中存儲了"World"。它們之間互相不干擾。

在Handler機制中,由于一個Looper對應著一個線程,所以將Looper存進ThreadLocal最合適不過了。

ThreadLocal比價常用的就set()和get()方法。分別來看看怎么實現的吧。

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}

首先是去獲取ThreadLocalMap,找得到的話直接設置值,找不到就創建一個。

ThreadLocalMap getMap(Thread t) { return t.threadLocals;}

看到這里,大概也能明白了。每個線程Thread中有一個ThreadLocalMap對象。通過ThreadLocal.set()方法,實際上是去獲取當前線程中的ThreadLocalMap,線程不同,獲取到的ThreadLocalMap自然也不同。
再來看看這個ThreadLocalMap是什么來頭??搭惖淖⑨屩杏羞@么一句話:

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.

從注釋中可以知道這就是一個定制的HashMap,并且它的Entry類指定了Key只能為ThreadLocal類型的。所以直接將它看成是一個HashMap就好了。

get()方法也好理解,就是從Map中取出值而已。大概看一下就好了。

public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();}

寫在最后

雖然在開始寫之前,覺得Handler機制比較簡單,好像沒啥必要寫,但真正要寫起來的時候還是得去深入了解代碼的細節,然后才發現有些地方以前理解得也不夠好。能理解和能寫出來讓別人理解,其實是不同的層次了。

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人精品久久av网站| 国产午夜精品全部视频在线播放| 日韩中文字幕网| 欧美成在线视频| 亚洲精品电影久久久| 久久99国产精品久久久久久久久| 一区二区三区精品99久久| 日本精品久久久| 国产丝袜精品第一页| 国产在线观看91精品一区| 91在线视频成人| 国内免费久久久久久久久久久| 亚洲男人天堂2023| 亚洲精品国产精品国自产在线| 精品欧美一区二区三区| 狠狠综合久久av一区二区小说| 日韩精品中文字幕在线| 精品久久香蕉国产线看观看gif| 一区二区三区日韩在线| 久久香蕉国产线看观看网| 日本久久久久久久久久久| 欧美高跟鞋交xxxxhd| 中文字幕av一区二区三区谷原希美| 日韩在线观看av| 高清欧美一区二区三区| 2020国产精品视频| 中文字幕成人精品久久不卡| 欧美理论电影在线播放| 九九九久久国产免费| 亚洲第一色中文字幕| 久久躁日日躁aaaaxxxx| 在线播放日韩专区| 中文国产亚洲喷潮| 国产精品视频一区二区三区四| 国产精品久久国产精品99gif| 欧美综合在线观看| 2020国产精品视频| 日韩亚洲综合在线| 久久久久久久久电影| 8x海外华人永久免费日韩内陆视频| 久久久精品久久| 亚洲国产精品成人精品| 97热在线精品视频在线观看| 亚洲片av在线| 日本国产一区二区三区| 81精品国产乱码久久久久久| 欧美极品少妇与黑人| 亚洲aa在线观看| 中文字幕在线成人| 欧美亚州一区二区三区| 亚洲激情在线观看| 日韩精品在线视频美女| 日韩欧美国产一区二区| 日韩av快播网址| 亚洲精品久久久久久久久久久| 免费不卡欧美自拍视频| 欧美成人h版在线观看| 日韩高清不卡av| 亚洲欧美一区二区三区在线| 成人福利在线观看| 久久久黄色av| 成人黄色免费片| 欧美日韩免费区域视频在线观看| 亚洲福利在线播放| 久久久久久国产精品美女| 在线精品91av| 亚洲精品第一国产综合精品| 国产精品视频区1| 欧美丰满少妇xxxxx| 欧美乱大交xxxxx另类电影| 米奇精品一区二区三区在线观看| 日本久久精品视频| 欧美综合激情网| 欧美电影免费观看| 久久99青青精品免费观看| 中文字幕亚洲综合久久筱田步美| 欧美一级大片视频| 久久久久国产精品免费网站| 亚洲精品视频免费在线观看| 九色91av视频| 欧美放荡办公室videos4k| 91精品国产自产在线观看永久| 国产精品久久久久久搜索| 91国产美女在线观看| 伊人久久综合97精品| 欧美黄色三级网站| 最近2019年中文视频免费在线观看| 欧美日韩精品在线播放| 国产精品中文字幕在线观看| 九色91av视频| 国产91精品青草社区| 国产精品久久久久久久久久东京| 成人福利网站在线观看| 亚洲成人网在线观看| 一区二区欧美在线| 久久免费高清视频| 日韩电影网在线| 欧美性xxxx极品hd满灌| 91豆花精品一区| 日韩欧美黄色动漫| 国产综合久久久久| 国产v综合v亚洲欧美久久| 黄色91在线观看| 日韩风俗一区 二区| 青青草国产精品一区二区| 国产精品91在线观看| 2024亚洲男人天堂| 欧美日韩另类字幕中文| 在线视频欧美日韩精品| 美女视频黄免费的亚洲男人天堂| 成人欧美一区二区三区黑人孕妇| 欧美资源在线观看| 97在线看免费观看视频在线观看| 亚洲欧美在线免费观看| 欧美成人免费小视频| 国产亚洲精品久久| 亚洲精品久久久久中文字幕欢迎你| 亚洲永久免费观看| 亚洲性猛交xxxxwww| 欧美天天综合色影久久精品| 欧美日韩在线视频观看| 国产一区二区三区在线观看视频| 日韩毛片在线观看| 欧美日韩亚洲国产一区| 欧美性猛交xxxx黑人| 亚洲精品国产电影| 国产中文字幕日韩| 国产欧美日韩免费看aⅴ视频| 国产精品视频久| 69av在线播放| 欧美激情一区二区久久久| 欧美成人免费在线观看| www.亚洲天堂| 91久久精品久久国产性色也91| 亚洲美女性生活视频| 国自产精品手机在线观看视频| 欧美裸体xxxx极品少妇软件| 亚洲精品视频免费在线观看| 久久久久久久av| 欧美肥老太性生活视频| 亚洲iv一区二区三区| 亚洲第一页中文字幕| 亚洲天堂第二页| 日韩欧美国产黄色| 国产亚洲精品美女久久久| 亚洲天堂av在线免费| 久色乳综合思思在线视频| 伦伦影院午夜日韩欧美限制| 91麻豆国产语对白在线观看| 亚洲欧美日韩精品久久奇米色影视| 九九热这里只有精品6| 欧美日韩亚洲视频| 欧美极品少妇xxxxⅹ免费视频| 欧美精品18videos性欧| 日韩av在线导航| 精品国产美女在线| 久久国产精彩视频| 日韩美女主播视频| 韩国一区二区电影| 欧美激情在线播放| 欧美日本在线视频中文字字幕| 精品久久久久久亚洲国产300| 成人精品一区二区三区电影黑人| 日韩www在线|