Handler機制是面試官非常喜歡問的知識點,源碼我看完幾遍,還是會覺得不清晰,裏面的代碼非常繞。後來我決定放棄探究細節,先把相關的類和調用的方法畫一個草圖,然後理清互相調用的關係,再結合關於Handler的高頻面試題,去尋找答案,這樣一輪下來,會對Handler有更深的認識。現在把這個過程,面試題尋找的答案以及相關的解釋整理成這篇文章,如有不對,歡迎大家予以指正。
高頻面試題
1.獲取Message實例的方式有哪些?哪一種更好?
獲取Message實例的方法主要有兩種,一種是直接創建,Message msg = new Message
。另一種是通過Message.obtain()
或者Handler.obtatinMessage()
來得到一個Message對象。更推薦使用後一種方式,這種方式得到的對象是從對象回收池中得到,複用已經處理完的Message對象,而不是重新生成一個新對象。Message.obtain()
和Handler.obtatinMessage()
最終都是調用了Message類的obtain()
方法,查看方法內容,就知道是從對象回收池裏得到Message。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
2.當Activity有多個Handler的時候,Message消息是否會混亂?怎麼樣區分當前消息由哪個Handler處理?
不會混亂,哪個Handler發送的消息,到時候也是這個handler處理。在發送消息的時候,會綁定target,這個target就是Handler本身,當需要handler調用dispatchMessage(msg)
處理消息的時候,這個Handler就是發送消息時綁定的handler。
無論用哪一種方法發送消息,最終都會調用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
來發送消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
這裏的this,就是當前的handler。在來看需要Handler處理消息的時候,取的是哪一個handler,下面貼出主要源碼。
public static void loop() {
......
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
......
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();
}
}
這是循環消息時的部分代碼,處理消息代碼是msg.target.dispatchMessage(msg);
,這裏的target就是當時發送消息的handler。
3.在子線程發送消息,卻能夠在主線程接收消息,主線程和子線程是怎麼樣切換的?
子線程用handler發送消息,發送的消息被送到與主線程相關聯的MessageQueue,也是主線程相關聯的Looper在循環消息,handler所關聯的是主線程的Looper和MessageQueue,所以最後消息的處理邏輯也是在主線程。只有發送消息是在子線程,其它都是在主線程,Handler與哪個線程的Looper相關聯,消息處理邏輯就在與之相關的線程中執行,相應的消息的走向也就在相關聯的MessageQueue中。所以子線程切換到主線程是很自然的過程,並沒有想象中的複雜。
4.能不能在子線程中創建Handler?
可以,但是在創建前先調用prepare()方法創建Looper。Handler創建的時候,會去檢查是否有創建Looper,如果沒有創建就會拋出異常。相關源碼如下:
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
所以我們在子線程需要先調用prepare()方法創建Looper。這裏還多提一點,在主線程創建就不需要自己創建Looper,因爲在ActivityTread類裏面,已經爲我們創建好了,相關源碼如下:
public static void main(String[] args) {
......
Looper.prepareMainLooper();// 爲主線程創建looper
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.prepareMainLooper();
這句代碼就是爲主線程創建Looper。 所以在主線程中直接創建一個Handler,就直接可以循環消息,因爲安卓的主線程已經爲我們準備好了Looper。
5.一個線程可以有幾個Handler?幾個Looper?
一個線程可以有多個Handler,但是隻有一個Looper。創建Handler之前,需要創建Looper,否則會報錯。源碼裏面已經做了說明。
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {//判斷Looper是否被創建
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
再來看Looper的創建,是在prepare()方法裏。
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
在創建之前去判斷looper是否存在,存在就會拋出Only one Looper may be created per thread
異常,這是在告訴我們一個線程只能有一個Looper。而TreadLocal的作用就是線程間隔離,確保一個線程對應一個Looper。還可以看看Looper構造方法的源碼
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue的初始化是在Looper的構造方法裏。不管一個線程有多少個Handler,相關聯的都是同一個Looper和MessageQueue。
關於Handler,可以問的問題有很多,以上只是抽出一些我認爲比較重要的問題。在尋找答案以後,我將Handler機制的整個過程在腦海中過了一遍,並且畫了個草圖。
Handler機制中重要類的相互關聯圖
Handler機制原理涉及幾個重要的類:Handler、Message、MessageQueue、Looper。
就用子線程向主線程發送消息來說明整個過程。
首先在主線程創建一個Handler,在Handler類裏面會創建Looper以及MessageQueue的對象,並且在Handler構造方法裏面賦值
final Looper mLooper;
final MessageQueue mQueue;
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
mLooper = Looper.myLooper();
得到的Looper是主線程的LoopermQueue = mLooper.mQueue;
得到的MessageQueue就是在Looper構造方法裏面創建的MessageQueue。
創建好了Handler實例,我們就會在子線程調用handler.sendMessage(msg);
發送消息,將message放到MessageQueue裏面。在enqueueMessage()
裏面就給每個message設置target,這個target就是當前的handler。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
再然後調用Looper.loop();
(在ActivityThread的main()裏面已經幫我們寫好)開始循環消息,拿到消息以後就會用handler取出消息進行處理,重點代碼是msg.target.dispatchMessage(msg);
,這裏的handler就和一開始我們爲message設置的Handler對應。
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
最後我們在Handler的handleMessage(msg)
裏面去處理我們的消息。
這就是子線程給主線程發送消息的整個過程,源碼很多,我只是截取了部分重點。這裏多提一點就是線程處理消息。 在線程中處理消息需要做三件事情
1. 先創建一個Looper (Looper.prepare()
) 2. 再創建Handler,默認是和當前線程的Looper關聯起來 3. 循環消息(Looper.loop()
)
這三個步驟的順序不能調換。因爲主線程已經幫我們創建了Looper,所以我們不需要寫,如果是在子線程創建Looper就需要了。
Handler機制的理解要靠自己去琢磨,不斷的看源碼,去理解,理清它們之間的互相調用。只有比較深入的理解了Handler,才能在面試中回答面試官的問題,靠死記硬背是不可取的。多花時間去看,多動腦,不偷懶,總能把Handler拿下。
最後
文章到這裏就結束了,有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。
最後這裏是關於我自己的Android 學習,面試文檔,視頻收集大整理,有興趣的夥伴們可以看看~