Handler是我們在Android開發過程中經常會使用到或者與之打交道的一個類,比如我們會在子線程發起網絡請求,然後到主線程進行UI的刷新。雖然現在有很多能夠自由進行線程切換的相關庫,比如RxJava,但瞭解Handler的原理還是很有必要的,也能夠幫助我們更好的去使用相關庫,其中的設計思想也特別重要。
1、Handler是如何創建的?
What?這也太簡單了吧,看我們平常經常寫的一段代碼
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
這不是就創建了一個Handler麼?但是,我們所寫的這段代碼究竟幹了什麼事呢?我們看Handler的構造函數
//當我們new Handler()的時候,調用的是這個無參的構造方法,其最終會調用下面一個構造方法
public Handler() {
this(null, false);
}
public Handler(@Nullable 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());
}
}
//獲取到Looper對象
mLooper = Looper.myLooper();
if (mLooper == null) {//注意這個異常,點1
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
以上代碼就創建了一個Handler,其中需要注意的是點1,獲取一個Looper對象,並且如果mLooper == null的時候會拋出異常。這就尷尬了啊,我們好像沒有對Looper實例化啊,那這個Looper是在什麼時候創建的呢?其實我們如果在主線程創建Handler的時候,其實得到的是主線程的Looper。如果我們將創建Handler的時候,如果我們在子線程創建Handler,如果沒有調用Looper.prepare(),那麼就會拋出上面點1的那個異常。所以如果我們是在子線程創建Handler需要這樣創建。
//準備Looper
Looper.prepare();
//創建Handler
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
//開啓消息循環
Looper.loop();
通過以上我們就創建出了我們的Handler。那麼問題點又來了,我們在主線程創建Handler的時候好像並沒有調用Looper.prepare();Looper.loop();這2個方法啊,爲什麼沒拋異常呢?其實答案就是並不是沒有調用,而是不需要我們手動去調用,系統已經自動幫我們調用了這2個方法。在ActivityThread這個類的main方法,也是整個應用程序的主入口處。我們其實都知道java代碼的運行是需要main函數作爲主入口的,而我們自己寫的時候也沒有寫這個方法,而能運行起來的關鍵也是系統幫我們做了這件事。代碼如下:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
............//此部分忽略很多代碼
Looper.prepareMainLooper();
// 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");
}
好了,我們的Handler已經被創建了,那麼消息是怎麼放入,又是怎麼取出的呢?接下來我們先來看消息的放入。
一、消息如何放入
我們平常使用Handler發消息有2個系列,sendMessage和post系列,其不同點在於send是直接發送一個Message,回調在我們 new Handler重寫handleMessage的地方。而post系列需要傳入一個Runnable,會回調到此Runnable裏進行消息處理。sendMeage有很多不同參數的形態,我們以sendMessageDelayed作爲例子看下代碼。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
爲什麼要看這個方法呢?其實看源碼我們發現就算我們調用sendMessage其實它也是調用sendMessageDelayed這個方法。這段代碼唯一需要注意的是SystemClock.uptimeMillis(),這個方法得到的是開機到現在所經歷的時間毫秒數,然後加上我們所希望延遲的毫秒數,不就實現了一個延時執行的效果麼。然後最終又調用了sendMessageAtTime這個方法,我們看下這個方法又做了什麼。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
//mQueue是我們創建Handler的時候得到的
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);
}
//上面的方法調用了此方法 傳入queue message time
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//這方法是MessageQueue裏的哈
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標記爲可用狀態
msg.markInUse();
//希望在何時執行
msg.when = when;
//mMessages可用理解爲上一次入隊的Message
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//p == null MessageQueue的Message爲空 說明存儲Message的鏈表是空的,我們的Message應該放在第一個
//when == 0 希望立即執行 需要放在第一個
//when < p.when 希望執行的時間比mMessage的時間小,也是要放在第一個
// New head, wake up the event queue if blocked.
//將當前Message的next指向mMessage
msg.next = p;
//更新mMessage
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();
//if裏條件不滿足的時候,說明消息需要做插隊處理
Message prev;
for (;;) {
//先將mMessage指向prev 注意此時的mMessage就是上次入隊的Message並且一定不爲空
prev = p;
//將p指向p的下一個Message
p = p.next;
//p==null 說明後面沒有Message了,可用插入此位置
//when < p.when 說明希望執行的時間比p的時間小,就應該插入此位置
//當滿足以上2個條件的任意一個,說明已經找到了要插入的位置,跳出循環
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//將msg的next執向p
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;
}
當執行完MessageQueue.enqueueMessage(Message msg, long when)之後,就完成了我們Message的入隊操作。裏面的核心步驟也做了相應的註釋,有的步驟理解起來有點傲,需要耐心的去慢慢推導。
二、消息如何取出
消息的取出其實就是Looper.loop()開啓之後,然後進入一個死循環,去嘗試取出我們的Message。我們Looper.loop()的代碼。
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.");
}
//得到queue
final MessageQueue queue = me.mQueue;
.................//此部分代碼省略
for (;;) {
//去queue裏面獲取Message 具體看後面的代碼next()方法
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
............//此部分代碼省略
try {
//dispatchMessage 調用的是Handler的方法,在下面的代碼講解
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
..............//此部分代碼省略
msg.recycleUnchecked();
}
}
//MessageQueue裏面的方法
Message next() {
.........//此部分代碼省略
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;
//msg不爲空且msg.target爲空 這個target其實就是Handler 說明msg不可用,要重新去拿
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();
//最終會將我們的msg返回回去
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...............//此部分代碼省略
// 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;
}
}
//msg.target.dispatchMessage(msg); 調用的就是此方法
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {//msg.callback其實是通過post系列進來的 callback其實是一個Runnable
handleCallback(msg);
} else {//這部分是send系列的回調處理
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//是不是特熟悉?不就是new Handler我們會重新的接收消息的方法麼
handleMessage(msg);
}
}
以上貼出的爲關鍵部分的代碼,通過以上的流程就可以成功的取到消息了。
以上我們明確了Handler的創建,消息如何入隊、出隊。那麼Handler、Message、MessageQueue、Looper在其中他們各自扮演了什麼角色?還有我們常說的Handler導致內存泄漏又是怎麼一回事呢?我們先看下這個幾個類各自的持有對象情況。
Handler:
Looper mLooper;//Looper對象
MessageQueue mQueue;//MessageQueue對象
Callback mCallback;//回調 在new Handler中可以傳入
Message:
Handler target;//持有的Handler
Runnable callback;//post系列傳進來的Runnable
Message next;//當前Message的下一個Message
MessageQueue:
Message mMessages;//上一次入隊的Message
//MessageQueue最主要負責了入隊、出隊的方法,以及一些Native方法
Looper:
Looper sMainLooper;//主線程Looper
MessageQueue mQueue;//MessageQueue對象
Thread mThread;//當前的線程對象
ThreadLocal sThreadLocal;//可以理解爲Map存儲,線程數據隔離
Handler通過send或者post系列,發送Message,MessageQueue負責Message的入隊、出隊操作,Looper負責震整個消息調度,最核心的爲loop()方法,不斷的從裏面取出消息,發送到負責處理消息的Handler。
關於會引起內存泄漏,我們看下對象的引用線:Looper->MessageQueue->Message->Handler(message.target)->Activity 如果Handler非靜態類,那麼就是這樣一條引用線,也就是說如果不需要的Message不移除,那麼所持有的Handler就不會移除,而Handler又持有了Activity的引用,直到消息被出隊。所以我們需要在Activity的onDestory中調用handler.removeCallbacksAndMessages(null);將Message移除。
關於Message的創建,new Message()和Message.obtain() ,前一種是直接申請一個Message內存空間,而後一種會在廢棄Message池中複用Message對象,而好處就是可以內存複用,減少gc次數。這種內存複用池的方法在很多框架設計中都會用到,屬於內存優化的範疇。