吐槽:想起大約一週前的一個夜晚,我正在好好地學習。忽然之間。。。胸口一陣疼痛,而後就是一直疼,而且隨着深呼吸會更加重痛。我。。。。作錯了什麼???難道嫉妒我這麼愛學習就要猝死了???怎麼能這樣!!!那天晚上,我忍着痛入眠,次日醒來,疼痛依然伴隨吾身。。。去醫院。。。一頓拍片檢查操做以後,醫生:「嗯,你肺裏進空氣了。」我(滿臉問號):「???」,我肺裏沒空氣我怎麼活的?醫生(一臉平靜):「肺泡破了吧,空氣進入胸腔了,壓縮了肺部」。。。。好吧,是氣胸。得,想我一不運動,二不高又不瘦,我會得這種病也是**奇了怪了。而後就是。。天天吸氧、拍片複查。。重複重複。。。直到如今,我仍是不能理解,,,爲何我會得這種病???java
零零碎碎的東西老是記不長久,僅僅學習別人的文章也只是他人咀嚼後留下的殘渣。無心中發現了這個每日一道面試題 ,想了想若是隻是簡單地去思考,那麼不只會收效甚微,甚至難一點的題目本身可能都懶得去想,堅持不下來。因此不如把每一次的思考、理解以及別人的看法記錄下來。不只加深本身的理解,更要激勵本身堅持下去。git
Handler 想必接觸 Android 的都已經很熟悉了,咱們一般用用於子線程與主線程的通訊,並在主線程中處理相關消息,好比更改 UI 等。Handler 的消息機制爲咱們處理線程中的通訊確實方便了許多。github
最簡單的使用就是在主線程中建立 Handler 對象並重寫 handlerMessage() 方法處理消息,在子線程中經過 handler 對象的 sendMessage 方法發送消息。面試
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// TODO write your message processing code
...
}
};
new Thread(){
@Override
public void run() {
super.run();
//TODO handling events
...
handler.sendMessage(Message.obtain());
}
};
複製代碼
爲了簡單示例,Handler 才這樣寫的。這種建立方法會形成內存泄漏,具體願意以及解決方案在每日一道面試題(第 1 期)---自定義 handler 如何有效保證內存泄漏問題 都已經說明,這裏再也不贅述。app
而關於 Handler 的原理,咱們可能會多多少少的瞭解到與 Looper、Message、MessageQueue 都是離不開的,那麼具體是怎麼配合呢?下面我根據消息機制的過程,結合源碼一步一步的解析。異步
在咱們使用時,首先就是建立 Handler,那咱們看一下 Handler 的構造函數都幹了些什麼吧。async
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//獲取本地 TLS 存儲區的 Looper 對象引用
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//使用 Looper 中的 MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製代碼
好吧,不少構造函數,不過有用的就是這兩個,其餘都是調用這兩個而已。從兩個對比能夠看出,若是你沒有傳入 Looper 對象,那麼就會經過 Looper.myLooper() 獲取。傳入的話,就是用自定義的,而且 MessageQueue 一直是使用 Looper 中的 MessageQueue,因此這裏出現了第兩個重要結論:ide
而後還有兩個參數 callBack 和 async,callBack 是 Handler 內部的一個接口,內部只有一個 handleMessage() 方法,做用咱們下面講到再說。而後就是 async,這個就好理解了,就是決定是異步處理消息,仍是同步處理消息,默認爲同步 false;函數
public interface Callback {
public boolean handleMessage(Message msg);
}
複製代碼
上一個步驟中咱們知道,Handler 是直接從 Looper.myLooper() 中得到對象的,咱們具體探討一下。oop
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
很簡單,是從一個集合中拿到的,這個 sThreadLocal,是 ThreadLocal 類的對象,表明着本地存儲區(Thread Local Storage 簡稱 TLS),線程中的惟一 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));
}
複製代碼
找到啦,由兩個方法的權限訪問修飾符可知,咱們只能調用第一個,也就是說,第二個方法的參數永遠是 true。再來看第二個方法,首先就是一個異常捕獲,有的人可能已經很熟悉了,那就是一個線程中只能有一個 Looper。後面就是沒有的話就 new 一個 Looper 對象,構造函數中都幹了什麼呢?
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
也很簡單,初始化了 mQueue 也就是 MessageQueue,還有就是當前所在的線程 mThread。從權限訪問修飾符能夠看出,這是一個私有的構造方法,因此說,咱們建立 Looper 方法只有 prepare() 方法啦。
上述 Looper 對象的建立中,new 了一個 MessageQueue 方法,咱們看看都幹了什麼。
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
複製代碼
也很簡單,初始化了兩個變量 mQuitAllowed 與 mPtr,第一個表明着消息隊列是否能夠退出,上面也說了,咱們麼的辦法,只能是 true。第二個 mPtr,涉及到 Native 層的代碼,在 native 層也作了一個初始化,具體深刻了解可到此處Android 消息機制 2-Handler(Native 層)
建立完 Handler,下一步就是發送 Message 了,去看看源碼
public final boolean sendMessage(Message msg){};
public final boolean sendEmptyMessage(int what){};
public final boolean sendEmptyMessageDelayed(int what, long delayMillis){};
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis){};
public final boolean sendMessageDelayed(Message msg, long delayMillis){};
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);
}
複製代碼
又是好多發送消息的,不過他們最後都調用了 sendMessageAtTime 方法。第一個參數是要發送的消息,而第二個是一個絕對時間,也就是發送消息的時間。在作了一些判斷以後,調用了 enqueueMessage 方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//消息得到發送該消息的 Handler 對象引用
msg.target = this;
//是否同步屬性
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
在又作了一些 Message 的屬性初始化後,調用了 queue 的 enqueueMessage 方法,而這個 queue 就是在 Looper 對象中得到的 MessageQueue 對象。下面是 MessageQueue 中的 enqueueMessage 方法
boolean enqueueMessage(Message msg, long when) {
// 每個 Message 必須有一個 target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) { //正在退出時,回收 msg,加入到消息池
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;//mMessages 爲當前消息隊列的頭結點
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//p 爲 null(表明 MessageQueue 沒有消息) 或者 msg 的觸發時間是隊列中最先的, 則進入該該分支
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//將消息按時間順序插入到 MessageQueue。通常地,不須要喚醒事件隊列,除非
//消息隊頭存在 barrier,而且同時 Message 是隊列中最先的異步消息。
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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
總結就是,MessageQueue 按照消息的觸發時間插入隊列,隊頭是最先要觸發的消息。一個新的消息加入會根據觸發時長從隊頭開始遍歷。
好了,消息已經發送給 MessageQueue 了,那麼誰來管理這個 MessageQueue,將其中的消息正確的、準確的分發呢?那就是 Looper,準確的說是 Looper 中的 loop() 方法,這也是咱們在子線程中建立 Handler 先要以前要 Looper.perpare(),以後要 Looper.loop() 的緣由。
public static void loop() {
final Looper me = myLooper();//獲取 TSL 存儲區的 Looper 對象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//獲取相對應的消息隊列
................
for (;;) {//消息主循環,除非線程退出,否則會一直循環,沒有消息時會阻塞
//獲取下一個消息,沒有消息時會阻塞,消息隊列退出後會返回 null,則該循環也退出
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
// 默認爲 null,可經過 setMessageLogging() 方法來指定輸出,用於 debug 功能
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
................
try {
//獲取消息後根據 msg 的 target 即所屬 Hnadler 分發消息
//target 即 Handler 在上面發送消息代碼解釋中有說明
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
.................
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
................
//分發完此消息,就回收此 Message 對象,留以複用
msg.recycleUnchecked();
}
}
複製代碼
一些代碼解釋已經很清楚了,在在這裏面主要是一個死循環輪詢消息。由 MessageQueue 的 next() 方法取出消息,再由 Message 所屬的 Handler 對象 dispatchMessage() 方法分發消息。首先咱們看 next() 方法。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {//當消息循環已經退出,則直接返回
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;//阻塞時長
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞函數,參數 nextPollTimeoutMillis 表示等待時長,或者消息隊列被喚醒,都會返回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//當消息中的 Handler 爲空時,在 MessageQueue 中尋找下一個異步消息
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 {
// 得到消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//改變 message 狀態
msg.markInUse();
return msg;
}
} else {
// No more messages.
//沒有消息,設置阻塞時長
nextPollTimeoutMillis = -1;
}
// 若是正在退出,返回空
if (mQuitting) {
dispose();
return null;
}
.............
}
}
複製代碼
nativePollOnce 是一個阻塞函數,參數 nextPollTimeoutMillis 則表明阻塞時長,當值爲-1 時,則會一直阻塞下去。 因此說,next 函數在 MessageQ 有消息時,會獲取消息並返回,在沒有消息時,則會一直阻塞。
獲取完消息,就要到分發消息了,也就是消息輪詢總結中的 dispatchMessage 函數,在 Handler 類中
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
這裏的調用流程就是
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
複製代碼
public interface Callback {
public boolean handleMessage(Message msg);
}
複製代碼
public void handleMessage(Message msg) {
}
複製代碼
轉了一圈,從 Handler 到 Looper 又到 MessageQ 最後又回到 Handler,整個消息機制也就差很少這樣啦,固然 Message 在其中就是一個實體啦,能夠協上數據一塊兒傳遞消息。我也是根據這個順序,一步一步的慢慢了解每一個步驟的。
最後,附上一張圖吧,會更清晰一點