面試被問懵?帶你一步一步深入Handler原始碼,不信還拿不下面試官?
Handler機制是Android中相當經典的非同步訊息機制,在Android發展的歷史長河中扮演著很重要的角色,無論是我們直接面對的應用層還是FrameWork層,使用的場景還是相當的多。
很多朋友面試時問到了這裡,一時被問懵。從哪裡跌倒就從哪裡爬起來,帶大家一步一步深入Handler原始碼,就不信還拿不下面試官!
BATJ、位元組跳動面試專題,演算法專題,高階技術專題,混合開發專題,java面試專題,Android,Java小知識,到效能最佳化.執行緒.View.OpenCV.NDK等已經上傳到了的我的GitHub
大家點選我的GitHub地址: 點下star一起學習
分析原始碼一探究竟。
從一個常見的用法說起:
private Button mBtnTest;private Handler mTestHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: mBtnTest.setText("收到訊息1"); } } };@Overrideprotected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnTest = (Button) findViewById(R.id.btn_test); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); mTestHandler.sendEmptyMessage(1); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
在對某件實物進一步瞭解之前,我們要先對該事物的價值意義有一個理解,即他是做什麼的,再明白事物產生或發生時做了什麼,結束時又會有什麼樣的結果。
我們要討論研究的是這個過程到底經歷了什麼,是發生什麼因,再經歷什麼產生這個果。
當呼叫Handler傳送訊息相關方法時,會把這個訊息傳送到哪兒去?從上面的示例程式碼中可以看到訊息最終還是會回到Handler手上,由他自己處理。我們要搞清楚的就是這個訊息由發到收的過程。
訊息會傳送到哪兒去?
mTestHandler.sendEmptyMessage(1);
我們追隨sendEmptyMessage()方法下去:
Handler無論以何種方式傳送何種訊息,都會經過到sendMessageAtTime()方法:
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); }
而此方法會先判斷當前Handler的mQueue物件是否為空,再呼叫enqueueMessage()方法,從字面意思不難理解是將該訊息入隊儲存起來。再看enqueueMessage()方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }public final class Message implements Parcelable { //.... Handler target; }
該方法會先將Message和當前Handler繫結起來,不難理解當需要處理Message時直接甩給繫結他的Handler就是了。再呼叫queue.enqueueMessage()方法正式入隊,而queue物件到底是一個什麼樣的物件?由單向連結串列實現的訊息佇列。queue.enqueueMessage()方法就是遍歷連結串列將訊息插入表尾儲存起來,而從queue取訊息就是把表頭的Message拿出來。
接著來搞清楚queue他是何時怎樣建立的?來看Handler的建構函式。
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; }
Handler的構造方法會先呼叫Looper.myLooper()方法看能不能獲取一個Looper物件,如果獲取不到程式就直接蹦了。再從該Looper物件中獲取我們需要的訊息佇列。
Looper到底是一個怎樣的物件,有這怎樣的身份,在Handler機制中扮演這怎樣的角色?來看myLooper()方法:
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
myLooper()方法會直接就從sThreadLocal物件中獲取Looper,而sThreadLocal是一個ThreadLocal類物件,而ThreadLocal類說白了就是透過他儲存的物件是執行緒私有的。
static final ThreadLocal sThreadLocal = new ThreadLocal();
呼叫get()方法直接從ThreadLocal中獲取Looper,接下來就得看是何時set()將Loooper物件儲存到ThreadLocal中去的。Looper.prepare()方法:
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物件建立時會初始化MessageQueue()物件,正是我們需要的佇列。
之所以最上面的示例程式碼中我們並沒有呼叫prepare()方法初始化Looper,程式也沒有崩潰,那是因為在ActivityThread的Main方法中就已經初始化了Looper物件。
public final class ActivityThread { //...... public static void main(String[] args) { Looper.prepareMainLooper(); } //......}
到此我們算是明白訊息會傳送到哪兒去了,現在就要知道的是怎麼取出訊息交給Handler處理的。
首先MessageQueue封裝有完整的新增(入隊)和獲取/刪除(出隊)方法,MessageQueeue.next()方法將連結串列當中表頭第一個訊息取出。
Message next() { //.......... for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { 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 { nextPollTimeoutMillis = -1; } if (mQuitting) { dispose(); return null; } //............ } //.............. } }
程式碼雖然比較多,我們從第三行和第39行開始說起。next()方法實際是一個死迴圈,會一直從當前佇列中去取Message,即使當前佇列沒有訊息可取,也不會跳出迴圈,會一直執行,直到能夠從佇列中取到訊息next()方法才會執行結束。
其次當Looper呼叫quit()方法,mQuitting變數為ture時會跳出死迴圈,next()方法返回null方法也會執行結束。
上面提到在ActivityThread中的main()方法中會初始化Looper,其實在不久之後便會開始從佇列中取訊息。
public static void main(String[] args) { //...... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 呼叫Looper.loop()方法就會開始遍歷取訊息。public static void loop() {for (;;) { Message msg = queue.next(); // might block if (msg == null) { return; } final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } }
loop()方法中也是一個死迴圈,呼叫queue.nex()方法開始阻塞式取訊息,只有手動讓Looper停止,next()方法才會返回null。
取到訊息後,呼叫dispatchMessage()方法把訊息交由Handler處理。
msg.target.dispatchMessage(msg);
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
不僅可以給Handler設定回撥介面,Message也行。預設情況下會回撥handleMessage()方法。
本以為說得差不多了,其實還有一個關鍵的問題。我們是在主執行緒中執行的loop()方法,死迴圈為什麼沒有造成Activity阻塞卡死?查閱資料Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死後得知next()方法中會執行一個重要方法。
nativePollOnce(ptr, nextPollTimeoutMillis);
大佬分析得很好,我就不多說了。提一點,我們傳送的延時訊息,會透過Message欄位/變數when,將時長儲存下來,延時也是透過這個方法做到的。
Message next() { final long now = SystemClock.uptimeMillis(); 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 { //...... } } }
總結,Handler傳送訊息會將訊息儲存到Looper維護的訊息佇列MessageQueue中去,而Looper會死迴圈一直從佇列中取訊息,取到訊息後會交由Message繫結的Handler回撥處理。
如果文字不夠,那就拿出我的終極學習秘籍,來自三星架構師講解的Handler通訊機制原始碼,裡面還結合了BAT的面試題,影片我已經上傳到b站:
第一次看文章的朋友可以關注我,會不定期釋出大廠面試題、Android架構技術知識點及解析等內容,還有學習PDF+原始碼筆記+面試文件+進階影片分享
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2672829/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【面試必備】透過原始碼角度一步一步帶你分析 ArrayList 擴容機制面試原始碼
- Handler相關面試題你答對多少?怎樣清晰表達拿下面試官?面試題
- 一步一步帶你瞭解EventBus3.1.1 原始碼S3原始碼
- 面試官帶你學Android——面試中Handler 這些必備知識點你都知道嗎?面試Android
- 深入講解拉鍊表,還怕面試官問?面試
- 面試官:你還有什麼想問我的?面試
- 面試官問分散式技術面試題,一臉懵逼怎麼辦?分散式面試題
- 面試官,你再問我 Bit Operation 試試?面試
- 面試官:你分析過SpringMVC的原始碼嗎?面試SpringMVC原始碼
- 面試官問,Redis 是單執行緒還是多執行緒?我懵了面試Redis執行緒
- 帶你一步一步手撕Spring MVC原始碼加手繪流程圖SpringMVC原始碼流程圖
- 最新出爐,頭條三面技術四面HR,看我如何一步一步攻克面試官?面試
- 面試官說:來談談限流-從概念到實現,一問你就懵逼了?面試
- 帶你一步一步手撕 Mybatis 原始碼加手繪流程圖——執行部分MyBatis原始碼流程圖
- 詢問面試官的面試問題面試
- 如何拿下面試官?2019的一些面試實戰小結告訴你!面試
- 從零開始帶你一步一步使用YOLOv3測試自己的資料YOLO
- 當面試官說“你還有什麼問題想問的”,你該如何回答?面試
- 當面試官說 “你還有什麼問題想問的” ,你該如何回答?面試
- 當面試官說 “你還有什麼問題想問的”,你該如何回答?面試
- 【肥朝】面試官問我,Redis分散式鎖如何續期?懵了。面試Redis分散式
- 一步一步分析Redux原始碼?Redux原始碼
- 邦芒面試:這10個問題都答不上來,你還想透過面試?面試
- 面試官你來,130行帶你手寫完整Promise面試Promise
- 手把手帶你解析Handler原始碼原始碼
- 邦芒面試:十個步驟帶你俘獲面試官的心面試
- 不管卷不卷,面試還是得問問你G1原理!面試
- 面試官:註解五問你怕了嗎?面試
- 一步一步帶你掌握webpack(一)——入門Web
- 一步一步帶你掌握webpack(四)——開發Web
- 面試準備不充分,被Java守護執行緒幹懵了,面試官主打一個東西沒用但你得會面試Java執行緒
- 【兩萬字】面試官:聽說你很懂集合原始碼,接我二十道問題!面試原始碼
- 阿里面試官的突擊面試,被問到當場自閉!!!阿里面試
- 一步一步帶你掌握webpack(二)——資產管理Web
- 一步一步帶你掌握webpack(三)——輸出管理Web
- 帶你一步一步瞭解Python中的ClassPython
- 面試官就是要問我SpringMVC的原始碼,差點頂不住!面試SpringMVC原始碼
- 面試官:五年經驗,我不問問反射說不過去吧?面試反射