相信不論是出入Android,仍是已開發多年的老司機們,確定都對Android的Handler不會陌生,而它就是今天要介紹的Android消息機制中的一部分。html
在Android系統中,有兩大特點利劍:Binder IPC機制和消息機制。Android也由大量的消息驅動方式來交互,大到四大組件的工做流程,小到異步回調更新UI等等,各處都有消息機制的存在。git
在對消息機制進行分析以前,先來看一下消息機制中,都含有哪些角色以及他們各自的做用又是什麼:bash
Message異步
消息本體,一切邏輯都圍繞它來展開。ide
MessageQueueoop
消息隊列,管理消息的入隊和出隊。post
Handlerui
消息機制的兩端,可做爲消息產生端,也可做爲消息消費端。this
Looperspa
消息機制運轉的動力,不斷的循環執行,取出消息、分發消息。
他們之間的關係,能夠經過一個簡單圖來表示一下:
在消息機制的四個角色中,咱們常用和見到的就是Handler了,那就先從Handler看起。
Handler有不少構造方法,可是可用開發中使用的只有以下幾個:
Handler()
Handler(@Nullable Callback callback)
Handler(@NonNull Looper looper)
Handler(@NonNull Looper looper, @Nullable Callback callback)
這樣來看無非是可設置兩個參數:Looper和Callback。
若是不指定Looper,在構造時會經過Looper.myLooper()獲取當前線程的Looper,若是當前線程沒有Looper那麼會拋出異常。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
複製代碼
至於Callback,後面會進行分析。
Handler的sendEmptyMessage、sendEmptyMessageAtTime、sendEmptyMessageDelayed、sendMessage和sendMessageDelayed,最終都會調用sendMessageAtTime:
public boolean sendMessageAtTime(@NonNull 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);
}
複製代碼
這其中的uptimeMillis = SystemClock.uptimeMillis() + delayMillis,之因此採用SystemClock.uptimeMillis(),是由於它是已開機時間,而若是使用System.currentTimeMillis()在用戶修改手機時間時,該值就會發生變化。
除了上面的sendMessageAtTime,還有一個特殊的方法sendMessageAtFrontOfQueue:
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//固定uptimeMillis爲0
return enqueueMessage(queue, msg, 0);
}
複製代碼
能夠發現這兩類方法最終都會經過調用方法:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//將msg的target屬性指向當前Handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//轉到MessageQueue,消息入隊
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
當Looper在經過MessageQueue讀取到下一條消息時,就會經過handler的dispatchMessage分發給目標Handler來消費這條消息:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//若是配置了Callback,就再也不走Handler的handleMessage
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
既然在建立Handler時須要制定或從當前線程獲取Looper,那麼接下來就看一下Looper。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
從構造方法能夠看出:
1.私有方法,不容許外部直接經過構造方法建立
2.初始化時會初始化MessageQueue
3.初始化時會記錄當前線程
在線程中建立Looper,可使用prepare:
public static void prepare() {
//必須可退出
prepare(true);
}
//quitAllowed是否容許退出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));
}
複製代碼
Android系統在建立主線程Looper時,是經過prepareMainLooper:
public static void prepareMainLooper() {
//不容許退出
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
複製代碼
Looper在建立成功後會經過ThreadLocal與當前線程綁定,而且從源碼中能夠看出,每一個線程只能存在一個Looper。
Looper既然是經過死循環,爲消息機制提供運轉動力,那麼在建立Looper以後,就要適時的開啓死循環:
public static void loop() {
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 (;;) {
//從queue中取消息,可能會阻塞當前線程
Message msg = queue.next();
if (msg == null) {
// 取出null,說明消息機制退出,那麼跳出循環
return;
}
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//省略部分代碼
//取到了消息,分發消息
msg.target.dispatchMessage(msg);
//省略部分代碼
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//省略部分代碼
//回收這條消息
msg.recycleUnchecked();
}
}
複製代碼
在Looper初始化時,會初始化MessageQueue,接下來就看一它有哪些內容。
//quitAllowed是否容許退出
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
複製代碼
不容許退出(quitAllowed傳false)會怎樣:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
//省略部分代碼
}
複製代碼
在前面分析Handler時,最終發送消息都會經過MessageQueue的enqueueMessage:
boolean enqueueMessage(Message msg, long when) {
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) {
//拋異常
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//下一個準備要分發的消息
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//沒有準備要分發的消息
//或者這條消息是sendMessageAtFrontOfQueue發送的
//或者這條消息要發送的時間比下一條要早
//那麼下一條就是你了
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//省略部分代碼
}
//省略部分代碼
}
return true;
}
複製代碼
在Looper中,會經過死循環的方式調用queue.next()來獲取下一條消息:
Message next() {
//省略部分代碼
for (;;) {
//省略部分代碼
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//省略部分代碼
if (msg != null) {
if (now < msg.when) {
// 省略部分代碼
} 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();
//返給looper
return msg;
}
}
//省略部分代碼
}
}
}
複製代碼
衆所周知,若是在Activity內部使用非靜態內部類的形式聲明Handler,就會默認持有Activity的引用,而進一步致使內存泄漏。
但這裏有一個點須要知道,引發內存泄漏的真正緣由是——Activity間接被GCRoots持有。當一個對象,被GCRoots直接或間接引用時,是不會被GC回收的。
那Handler背後的GCRoots又是哪位大仙呢?咱們知道,當經過handler發送消息時,會將msg.target指向handler,在looper中循環遍歷獲得msg後,會經過msg.target.hanleMessage分發消息。而Looper是跟具體的某一個線程綁定的,若是咱們在初始化Handler時,不傳或傳入mainLooper,那麼looper綁定的就是主線程,主線程在程序運行過程當中是不會退出的,那麼就意味着若是隊列中還存在此Handler發送出去的消息(多是延遲消息還沒到分發時間),消息就存在於messagequeue隊列中,被Looper綁定的線程即主線程間接持有,而msg.target又指向了handler,handler持有Activity的引用,最終致使內存泄漏。
大體能夠用下圖示意:
解決辦法:
在Activity聲明週期onDestroy的時候,經過handler.removeCallbacks(myRunnable),能夠將msg.target爲當前Handler,而且msg.callback == myRunnable的msg從隊列中移除:
//handler
public final void removeCallbacks(@NonNull Runnable r) {
mQueue.removeMessages(this, r, null);
}
//MessageQueue
void removeMessages(Handler h, Runnable r, Object object) {
if (h == null || r == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.callback == r
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.callback == r
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
複製代碼
在Activity聲明週期onDestroy的時候,經過handler.removeMessages(int what, @Nullable Object object),還能夠將msg.what = what && msg.obj == object的消息移除
若是發送的Msg比較多且狀況各異(有的是runnable,有的有特定what,有的有特定的obj),那麼能夠經過靜態內部類的方式,不讓Handler持有外部Activity強引用,改用弱引用,這裏再也不進行代碼示意
咱們知道,在Android中,主線程不僅是要完成開發者寫代碼邏輯需求,還要完成系統對它的指示,好比刷新頁面。
對於同一個Looper來講,是能夠同時存在多個Handler,能夠同時向Looper中發送消息,這其中既有Android系統中定義的各類Handler,又有開發者編寫的Handler,那麼如何才能讓MessageQueue首先將系統發佈的msg分發出來,可以被率先執行呢?
往消息隊列頭部,放入一個系統的「告示」,告知MessageQueue接下來我會發送一些優先級高的指令,務必先執行我接下來的優先指令。
//相似於enqueueMessage,根據when合理的插入這個「告示」
//此方法被hide標記
//添加成功後會返回一個惟一的token,標識該屏障
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
//未給該msg設置target,是「告示」的身份特徵
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) {
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
複製代碼
再來MessageQueue是怎麼識別「告示」的:
Message next() {
//省略部分代碼
for (;;) {
//省略部分代碼
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// msg.target == null 說明是系統「告示」,讓我先進行優先消息的分發
do {
prevMsg = msg;
msg = msg.next;
//當找到第一個msg.isAsynchronous() = true的消息時,就會跳出循環,首先分發這個消息
} while (msg != null && !msg.isAsynchronous());
}
//省略部分代碼
}
//省略部分代碼
}
}
複製代碼
經過postSyncBarrier,系統告知MessageQueue接下來先執行的事,那麼哪些纔是要先執行的事呢?就是經過msg.setAsynchronous(true)方法,標記爲true的事。
系統除了能夠在特定的場合(如刷新屏幕)添加同步屏障,告知MessageQueue先執行特定的優先級消息以外,還能夠取消同步屏障,讓MessageQueue回覆正常排隊執行。好比原本須要刷新下一幀,可是頁面在下一幀刷新時間前被關閉了,那麼就移除以前的「告知」。
沒有這個「告知」,始終在MessageQueue.next中判斷msg.isAsynchronous爲true,那麼就優先分發它行不行?
若是沒有這個添加「告知」和移除「告知」的存在,那麼有些消息,包括系統發出的普通消息(msg.isAsynchronous = false),就可能永遠不會被執行了(開發者把全部消息都進行msg.setAsynchronous(true))。必需要在合適的時機,讓queue按照時間順序,依次執行消息的分發,而不是始終將isAsynchronous標誌放在第一位。
這裏放出一個問題,能夠思考一下:若是將Message的setAsynchronous進行hide處理,在MessageQueue.next中始終判斷msg.isAsynchronous,優先分發msg.isAsynchronous == true的消息,又是否可行呢?
再來回看MessageQueue.next方法:
Message next() {
//省略部分代碼
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (msg != null) {
if (now < msg.when) {
//省略部分代碼
}else{
//省略部分代碼
//若是找到了要分發的msg,而且到了分發時間,那麼就返回給looper
return msg;
}
}
//省略部分代碼
//如下代碼執行條件,是未找到下一條msg或下一條msg還未到分發時間
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
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 {
//執行idler.queueIdle,保存返回結果
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
//若是不保留當前idler,那麼移除
mIdleHandlers.remove(idler);
}
}
}
}
}
複製代碼
再來看一下接口IdleHandler:
public static interface IdleHandler {
boolean queueIdle();
}
複製代碼
使用場景:
在Activity繪製完成後,作一些事情
結合HandlerThread, 用於單線程消息通知器
關於使用場景,更詳細的內容,能夠參考這篇文章。
阻塞App時,每每是不能在繼續處理後續的邏輯,可是Android的消息機制,雖然是死循環,可是依然在有條不紊的接收和處理任務。這跟業務代碼中,錯誤書寫的一個局部小的死循環不一樣,正是因爲存在這個死循環的存在,主線程才能一直輪詢處理新的任務,保持應用的生機。
在消息機制中,取消息時,若是沒有可分發消息或下一條要分發的消息還未到分發時間,就會進行適當的阻塞;在有消息傳入時,會根據目標線程的阻塞狀態,決定是否進行喚醒,已使其可以順利的處理接下來的消息分發。
消息機制具體的native層機制,可跳轉Gityuan大佬的文章——Android消息機制2-Handler(Native層)。