我是程式碼搬運工,不能僅僅只是搬運,還要整理一下。
1. Handler組成部分:
- Message:訊息
- Handler:訊息的發起者
- Looper:訊息的遍歷者
- MessageQueue:訊息佇列
2. Handler的使用流程:
使用Handler之前的準備工作有三步:
-
呼叫Looper.prepare()(主執行緒不需要調這個,因為APP建立時,main方法裡面已經幫我們建立了)
-
建立Handler物件,重寫handleMessage方法(你可以不重寫),用於處理message回撥的
-
呼叫Looper.loop()
其中:
Looper.prepare()的作用主要有以下三點:
-
建立Looper物件
-
建立MessageQueue物件,並讓Looper物件持有
-
讓Looper物件持有當前執行緒
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 規定了一個執行緒只有一個Looper,也就是一個執行緒只能呼叫一次Looper.prepare()
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 如果當前執行緒沒有Looper,那麼就建立一個,存到sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼
從上面的程式碼可以看出,一個執行緒最多隻有一個Looper物件。當沒有Looper物件時,去建立一個Looper,並存放到sThreadLocal中
private Looper(boolean quitAllowed) {
// 建立了MessageQueue,並供Looper持有
mQueue = new MessageQueue(quitAllowed);
// 讓Looper持有當前執行緒物件
mThread = Thread.currentThread();
}
複製程式碼
這裡主要就是建立了訊息佇列MessageQueue,並讓它供Looper持有,因為一個執行緒最多隻有一個Looper物件,所以一個執行緒最多也只有一個訊息佇列。然後再把當前執行緒賦值給mThread。
Handler使用流程:
-
Handler.post(或sendMessage): handler傳送訊息msg
-
MessageQueue.enqueueMessage(msg, uptimeMillis):msg加入message佇列
-
loop() 方法中從MessageQue中取出msg,然後回撥handler的dispatchMessage,然後執行callback(如果有的話) 或 handleMessage。(注意,loop方法是一直在迴圈的,從前面的handler準備工作開始就已經一直在執行了)
如圖所示:
3. Handler具體原始碼:
3.1. Message獲取
Message的獲取方式有兩種:
1. Message msg = new Message();
2. Message msg = Message.obtain();
從全域性池返回一個新的訊息例項。允許我們在許多情況下避免分配新物件。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
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();
}
複用之前用過的 Message 物件,這裡實際上是用到了一種享元設計模式,這種設計模式最大的特點就是複用物件,避免重複建立導致的記憶體浪費
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
每次message使用完了之後會呼叫recycleUnchecked回收message,方便下次使用
複製程式碼
Message核心的資訊有這些:
public int what;
public int arg1;
public int arg2;
public Object obj;
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
複製程式碼
3.2 Handler的傳送訊息
handler提供的傳送訊息的方法有很多,大致分為兩類:
- Handler.post(xxx);
- Handler.sendMessage(xxx);
雖然方法很多,但最終都會回撥到這個方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
這個方法就是將訊息新增到佇列裡。
3.3 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;
}
// 標記這個 Message 已經被使用
msg.markInUse();
msg.when = when;
// 這是這個訊息佇列裡的目前第一條待處理的訊息(當前訊息佇列的頭部,有可能為空)
Message p = mMessages;
boolean needWake;
// 如果目前佇列裡沒有訊息 或 這條訊息msg需要立即執行 或 這條訊息msg的延遲時間比佇列裡的第一條待處理的訊息還要早的話,走這個邏輯
if (p == null || when == 0 || when < p.when) {
// 把訊息插入到訊息佇列的頭部
// 最新的訊息,如果已經blocked了,需要喚醒
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 下面這個for迴圈的作用就是找出msg應該放置的正確位置
// 經過下面這個for迴圈,最終會找出msg的前一個訊息是prev,後一個訊息是p
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 上面的for迴圈得出的結果就是:msg應該在prev後面,在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;
}
複製程式碼
上面就是handler傳送訊息過來,然後新增到訊息佇列裡。
下面就開始講新增訊息到佇列之後的事情:取訊息,然後執行。
3.4 Loop的取訊息
前面已經說到,在使用Handler之前有三步準備工作:
-
呼叫Looper.prepare()(主執行緒不需要調這個,因為APP建立時,main方法裡面已經幫我們建立了)
-
建立Handler物件,重寫handleMessage方法(你可以不重寫),用於處理message回撥的
-
呼叫Looper.loop()
其中第三步的Looper.loop()的作用就是不斷的從MessageQueue佇列裡取訊息,也就是說,在使用handler發訊息之前,就已經開始了loop的迴圈了。
loop()原始碼比較長,這裡摘取核心部分:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*
* 大概的翻譯就是:在這個執行緒中執行訊息佇列。確保呼叫{@link #quit()}來結束迴圈。
*
*/
public static void loop() {
····
····
for (;;) {
// 不斷的從MessageQueue的next方法裡取出佇列的頭部訊息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
·····
·····
·····
try {
// msg.target是Message在建立時傳入的Handler,也就是傳送這條訊息的傳送者handler
// 所以最終會回撥到handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
····
····
// 回收msg,重複利用
msg.recycleUnchecked();
}
}
複製程式碼
loop()的作用就是不斷的從MessageQueue裡取訊息,然後回撥到dispatchMessage裡,再看看dispatchMessage裡幹啥了
/**
* 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);
}
}
複製程式碼
所以最終會執行callback或handleMessage。
4. 面試常見問題
- Android中,有哪些是基於Handler來實現通訊的?
答:App的執行、更新UI、AsyncTask、Glide、RxJava等
- 處理Handler訊息,是在哪個執行緒?一定是建立Handler的執行緒麼?
答:建立Handler所使用的Looper所在的執行緒
- 訊息是如何插入到MessageQueue中的?
答: 是根據when在MessageQueue中升序排序的,when=開機到現在的毫秒數+延時毫秒數
- 當MessageQueue沒有訊息時,它的next方法是阻塞的,會導致App ANR麼?
答:不會導致App的ANR,是Linux的pipe機制保證的,阻塞時,執行緒掛起;需要時,喚醒執行緒
- 子執行緒中可以使用Toast麼?
答:可以使用,但是Toast的顯示是基於Handler實現的,所以需要先建立Looper,然後呼叫Looper.loop。
- Looper.loop()是死迴圈,可以停止麼?
答:可以停止,Looper提供了quit和quitSafely方法
- Handler記憶體洩露怎麼解決?
答: 靜態內部類+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的訊息
下面逐個來看看這些問題:
4.1 Android中常見的Handler使用
- 保證App的執行
App的入口其實是ActivityThread的main方法:
public static void main(String[] args) {
// 建立主執行緒中的Looper
Looper.prepareMainLooper();
// 建立ActivityThread物件
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
// 建立用於通訊的Handler
sMainThreadHandler = thread.getHandler();
}
// 執行loop方法
Looper.loop();
}
複製程式碼
可以看到這個main方法中,主要有如下幾步:
- 建立了主執行緒中的Looper,建立Looper時,會建立一個MessageQueue用於存放訊息。
- 建立了ActivityThread物件,並呼叫了它的attach方法,這個方法就是去建立Application、呼叫Application的onCreate方法以及告訴ActivityManagerService現在App啟動了。
- 建立了用於通訊的Handler,它是一個H物件。
- 呼叫Looper.loop方法,開始迴圈從主執行緒中的MessageQueue中取出訊息來處理。
回顧下,Handler機制的原理圖:
可以知道,App啟動後,因為Looper.loop是一個死迴圈,導致main方法一直沒有執行完,也就是說,我們後續App中的所有操作,都是發生在Looper.loop中的
那Handler機制,是怎麼保證App的執行的呢?我們來看看ActivityThread中用於通訊的Handler的定義:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
// 省略部分程式碼
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
// 省略部分程式碼
}
}
return Integer.toString(code);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_SHOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, true, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_HIDE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, false, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
// 省略部分程式碼
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
複製程式碼
通過程式碼可以看到,H繼承於Handler,定義了很多訊息的型別,比如啟動Activity、停止Activity、顯示Window、電量過低等,它重寫了handleMessage方法,用於處理這些訊息。
那發訊息是在哪裡發的呢?我們以鎖屏呼叫Activity的onStop生命週期為例,其實就是在ApplicationThread中,它是一個ActivityThread的內部類我們拿啟動Activity這個訊息舉例,ActivityManagerService會先通過binder呼叫ApplicationThread中的scheduleStopActivity方法(牽涉到程式間通訊,不懂可以略過),這個方法是在我們App的Bindler執行緒池中執行的,那看看它是怎麼切換到主執行緒去啟動Activity的。
public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) {
sendMessage(
showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
token, 0, configChanges);
}
複製程式碼
我們看到,這裡就是呼叫sendMessage方法,第一個引數,不就是上面H中定義的訊息型別麼?接著看 sendMessage方法,它最後會呼叫到如下這個多參的構造方法:
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
複製程式碼
可以看到,就是使用mH傳送一個訊息,而mH就是ActivityThread中定義的型別為H的成員變數,定義如下:
final H mH = new H();
複製程式碼
所以,呼叫鎖屏時,呼叫Activity的onStop方法,流程如下:
- ActivityManagerService通過binder方式,呼叫ApplicationThread的scheduleStopActivity方法
- ApplicationThread的scheduleStopActivity方法,通過H把訊息加入到主線執行緒的MessageQueue中
- 主執行緒的Looper遍歷MessageQueue,取到該訊息時,呼叫H的handleMessage方法進行處理
- 在H的handleMessage中,呼叫Activity的onStop方法
流程圖如下:
那麼,我們App執行原理就出來了,App的執行依賴於Handler機制,當主執行緒當MessageQueue有訊息時,主執行緒的Looper.loop會不斷從主執行緒中的MessageQueue中取出訊息來處理(比如Activity的onCreate其實就是屬於對MessageQueue中取出的一個訊息的處理),這樣就保證了App執行
當MessageQueue沒有訊息時,MessageQueue的next方法會阻賽,導致當前執行緒掛起,等有訊息(一般為系統程式通過binder呼叫App的ApplicationThread中的方法,注意,方法在binder執行緒池中執行,然後ApplicationThread使用ActivityThread中的H物件傳送訊息,加入訊息到主執行緒的MessageQueue中,當發現主執行緒被掛起了,則會喚醒主執行緒)
所以,當沒有任何訊息時,我們的App的主執行緒,是屬於掛起的狀態。有訊息來時(鎖屏、點選等),主執行緒會被喚醒,所以說,Handler機制保證了App的執行。
4.2 Handler更新UI
我們知道,如果在子執行緒直接更新UI會丟擲異常,異常如下:
我們可以使用Handler在子執行緒中更新UI,常用的方式有如下幾種:
- Handler的sendMessage方式
- Handler的post方式
- Activity的runOnUiThread方法
- View的post方式
2.1 Handler的sendMessage方式
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
btn.setText("handler.sendMessage方式");
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 使用sendMessage方式
Message msg = Message.obtain();
msg.what = 100;
handler.sendMessage(msg);
}
}).start();
複製程式碼
這種方式,就是在子執行緒中,用主執行緒中建立的hander呼叫sendMessage傳送訊息,把Message加入到主執行緒的MessageQueue中,等主執行緒的Looper從MessageQueue中取到這個訊息時,會呼叫這個Message的target的handleMessage方法,這個target其實就是我們發訊息用到的handler,也就是呼叫了我們重寫的handleMessage方法。
發訊息:Handler.sendMessage(Message) 處理訊息:Message.target.handleMessage(其中target就是發訊息的handler)
2.2 Handler的post方法
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
btn.setText("handler.sendMessage方式");
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 使用post
handler.post(new Runnable() {
@Override
public void run() {
btn.setText("handler.post方式");
}
});
}
}).start();
複製程式碼
在子執行緒中使用handler的post方法,也是可以更新UI的,post方法需要傳入一個Runnalbe物件。我們來看看post方法原始碼
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
複製程式碼
可以看到,先呼叫了getPostMessage構建了一個Message物件,然後呼叫了sendMessageDelayed方法,前面知道,sendMessage也是呼叫的這個方法,所以我們只要關注怎麼構建的Message物件,看getPostMessage方法。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
// post方法傳入的Runnable物件作為Message的callback
m.callback = r;
return m;
}
複製程式碼
其實很簡單,就是把post方法傳入的引數作為Message的callback來建立一個Message。
我們再來回顧一下從MessageQueue中取出訊息來對訊息對處理,方法是Handler對dispatchMessage方法
public void dispatchMessage(Message msg) {
// callback其實就是post方法傳入對Runnable物件
if (msg.callback != null) {
handleCallback(msg);
} else {
// 不會執行到這裡
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
// 相當於呼叫了post方法傳入對Runnable物件對run方法
message.callback.run();
}
複製程式碼
可以知道,我們使用post傳送訊息,就是使用傳入對Runnable物件封裝成一個Message,然後加入到主執行緒中的MessageQueue,等主執行緒的Looper取出該訊息處理時,因為Message.callback不為空,而呼叫其run方法,也就是我們呼叫post方法傳入的Runnable物件的run方法,且不會呼叫Hander的handleMessage方法。
傳送訊息:Handler.post(Runnable) 處理訊息:Message.callback.run(callback為呼叫post方法傳入的Runnable)
2.3 Activity的runOnUiThread方法
new Thread(new Runnable() {
@Override
public void run() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
btn.setText("Activity的runOnUiThread方法");
}
});
}
}).start();
複製程式碼
我們如果能在子執行緒中獲取到Activity物件,是可以呼叫其runOnUiThread方法,來更新UI。我們來看看Activity的runOnUiThread原始碼。
public final void runOnUiThread(Runnable action) {
// 如果不是UI執行緒,則呼叫Handler的post
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
// 如果是ui執行緒,則直接回撥Runnable的run方法
action.run();
}
}
複製程式碼
如果是UI執行緒,就直接呼叫了傳入的Runnable物件的run方法,我們主要是看非UI執行緒的邏輯。
如果不是UI執行緒,則直接使用mHandler的post方法,看來Activity的runOnUiThread方法也是基於Handler的post方法來實現的,後面的邏輯就是把傳入的Runnable封裝成Message傳送出去,上面講過了,就不再複述了。
我們再看看這個mHandler的定義,其實就是Activity的成員屬性
final Handler mHandler = new Handler();
複製程式碼
Activity是在主執行緒建立的,所以這個Handler也是在主執行緒中建立的,且持有的Looper為主執行緒的Looper。那麼使用這個Handler呼叫post方法發出的訊息,是加入到主執行緒的MessageQueue中,這樣就完成了子執行緒跟主執行緒的通訊。
傳送訊息:Activity. runOnUiThread(Runnable) 處理訊息:Message.callback.run(callback為runOnUiThread方法傳入的Runnable)
2.4 View的post方法
new Thread(new Runnable() {
@Override
public void run() {
// 呼叫View的post方法
btn.post(new Runnable() {
@Override
public void run() {
btn.setText("View.post方式");
}
});
}
}).start();
複製程式碼
我們直接看View的post方法
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
複製程式碼
可以看到這裡也是呼叫的Handler的post方法,跟Activity. runOnUiThread類似。
傳送訊息:View.post(Runnable) 處理訊息:Message.callback.run(callback為post方法傳入的Runnable物件)
總結一下:
- Handler.sendMessage: 把訊息加入到主執行緒的MessageQueue中,主執行緒中的Looper從MessageQueue中取出訊息,呼叫Message.target.handleMessage方法
- Handler.post: 基於Handler.sendMessage,把訊息加入到主執行緒的MessageQueue中,主執行緒中的Looper從MessageQueue中取出訊息,呼叫Message.callback.run方法
- Activity.runOnUiThread: 基於Handler.post
- View.post: 基於Handler.post
所以,以上子執行緒更新主執行緒UI的所有方式,都是依賴於Handler機制。
- AsyncTask
當我們想在子執行緒中做耗時任務時,會考慮使用AsyncTask,我們來舉個栗子,在子執行緒中去建立自定義的MyAsyncTask並執行它,在doInBackground中去模擬耗時操作:
public class AsyncTaskActivity extends AppCompatActivity {
private static final String TAG = "AsyncTaskActivity";
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_asynctask);
btn = (Button) findViewById(R.id.btn);
// 開啟一個子執行緒,去執行非同步任務
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run, thread name:" + Thread.currentThread().getName());
MyAsyncTask asyncTask = new MyAsyncTask();
asyncTask.execute();
}
}).start();
}
// 自定義AsyncTask,並重寫相關方法,並列印執行所線上程
class MyAsyncTask extends AsyncTask<Void, Integer, Void>{
@Override
protected Void doInBackground(Void... voids) {
Log.e(TAG, "doInBackground, thread name:" + Thread.currentThread().getName());
// 模擬耗時任務
for (int i = 0; i < 5; i ++) {
SystemClock.sleep(1000);
publishProgress(i);
}
return null;
}
@Override
protected void onPreExecute() {
Log.e(TAG, "onPreExecute, thread name:" + Thread.currentThread().getName());
}
@Override
protected void onPostExecute(Void aVoid) {
Log.e(TAG, "onPostExecute, thread name:" + Thread.currentThread().getName());
btn.setText("執行完了!");
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e(TAG, "onProgressUpdate, thread name:" + Thread.currentThread().getName());
}
}
}
複製程式碼
可以看到,我們建立的子執行緒名為Thread-4,而AsyncTask的方法所線上程如下:
- onPreExecute: Thread-4,其實就是呼叫AsyncTask的execute方法的執行緒
- doInBackground: AsyncTask #1,這其實是AsyncTask的執行緒池中的一個執行緒
- onProgressUpdate: main,即主執行緒
- onPostExecute: main,即主執行緒
關於onPreExecute,這很好實現,不需要切換執行緒,直接回撥就可以;而doInBackground方法的執行,可以直接取AsyncTask維持的執行緒池來執行就可以。我們重點關注onProgressUpdate和onPostExecute方法,是怎麼從子執行緒切換到主執行緒的。
我們從AsyncTask的原始碼中可以看到這樣一個內部類
private static class InternalHandler extends Handler {
public InternalHandler() {
// 使用主執行緒的Looper去建立Handler
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// finish中主要呼叫onPostExecute
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// 呼叫onProgressUpdate
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
複製程式碼
從handleMessage方法看到,這裡切換到主執行緒,也是使用的Handler機制來實現的,但是為什麼我們不管在任何執行緒建立的AsyncTask去執行,最後都可以在主執行緒去回撥這兩個方法呢?主要是建立InternalHandler時,使用的是主執行緒的Looper,這樣使用這個InternalHandler傳送訊息時,訊息就會加入到主執行緒Looper對應的MessageQueue中,所以主執行緒Looper取出訊息處理時,InternalHandler的handleMessage方法就是在主執行緒中回撥的了。
所以AsyncTask其實就是基於執行緒池+Handler機制來實現的。
-
其他使用Handler的地方
-
RxJava: 子執行緒切換到主執行緒執行觀察者的回撥方法(RxJava我不熟悉)
-
Glide:圖片準備好以後的回顯
-
LocalBroadcastManager: 傳統的廣播是基於binder實現的,本地廣播LocalBroadcastManager是基於Handler實現的
其實還有很多使用到handler機制的地方,就不一一舉例了,反正記住,Handler機制很重要。
4.3 處理Handler訊息,是在哪個執行緒?一定是建立Handler的執行緒麼?
以前總覺得,處理訊息的執行緒,就是建立Handler的執行緒,但是上一篇文章的分析,我們知道這樣說其實是不準確的(因為我們建立Handler通常使用的預設Looper)。
處理訊息的執行緒,其實是傳送handler所持有的Looper所在的執行緒。
其實原理很好理解,我們知道Handler的原理如圖
所以,訊息的處理分發所線上程完全取決於訊息所在MessageQueue的執行緒,如果想要在某個執行緒中處理訊息,只要做到把訊息加入到那個執行緒所對應的MessageQueue中。就像上面講到AsyncTask的例子,就算我們在子執行緒建立了AsyncTask(即在子執行緒建立了用於通訊的Handler),但只要我們建立Handler的時候,通過Looper.getMainLooper()傳入主執行緒的Looper ,那麼訊息就加入到了主執行緒所對應的MessageQueue中,訊息就是在主執行緒中處理的。
如下,我們在子執行緒中建立一個handler,然後在主執行緒傳送訊息,因為建立handler使用的是子執行緒中的Looper,所以訊息是在主執行緒中處理的。程式碼如下:
public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
btn = (Button) findViewById(R.id.btn);
// 開啟一個子執行緒,去建立Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 傳送一個訊息
mHandler.sendEmptyMessage(100);
}
});
}
}
複製程式碼
log輸出,可以看出,處理訊息是在子執行緒中:
按照之前的說法,如果我們想在主執行緒中處理訊息,只要把訊息加入到主執行緒的MessageQueue中,所以我們可以建立Looper時,傳入主執行緒的Looper,程式碼如下:public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
btn = (Button) findViewById(R.id.btn);
// 開啟一個子執行緒,去建立Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
// 建立Handler傳入主執行緒的Looper
mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 傳送一個訊息
mHandler.sendEmptyMessage(100);
}
});
}
}
複製程式碼
可以看到,建立Handler使用了主執行緒的Looper後,的確訊息是在主執行緒中處理的了:
4.4 是如何插入到MessageQueue中?
我們之前說,所有handler.post和handler.sendMessage都會呼叫到Handler的sendMessageDelayed方法,方法如下:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製程式碼
這裡邏輯就很簡單了,直接呼叫了sendMessageAtTime方法,第一個引數為Message,第二個引數為SystemClock.uptimeMillis() + delayMillis,其中delayMillis為延時的時間,單位為毫秒,SystemClock.uptimeMillis() 為開機到現在的時間(不包括休眠時間),單位為毫秒。第二個引數主要是決定該Message在MessageQueue的順序,比如現在開機時間為100s,傳送一個延時20s的訊息,則兩者之和為120s; 過了5秒,又發了一個延時5s的訊息,則兩者只喝為105+5 = 110s。
sendMessageAtTime最後會呼叫到MessageQueue的enqueueMessage方法,我們來看看這個方法:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// when = 開機到目前的時間 + 延時時間
msg.when = when;
Message p = mMessages;
boolean needWake;
// 如果當前訊息佇列為空或者當前when小於隊頭when
// 則把訊息插入到隊頭
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 死迴圈,根據when尋找Message插入到MessageQueue合適的位置
for (;;) {
prev = p;
p = p.next;
// MessageQueue中依次往後找到第一個Message.when大於當前Message.when的Message
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 把當前Message插入到MessageQueue的合適位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 如果需要喚醒,則呼叫nativeWake去喚醒處理執行緒
nativeWake(mPtr);
}
}
return true;
}
複製程式碼
從上面的程式碼,可以很容易看出,Message在MessageQueue中是根據when從小到大來排隊的,when是開機到現在的時間+延時時間。
比如,我們假設開機時間為100s,此時MessageQueue沒有訊息,這時候發一個延時20s的訊息,即when為120000,則MessageQueue中的訊息情況如圖:
過了5s,我們又發了一個延時10s的訊息,則when為115000,此時MessageQueue如圖: 又過了5s,我們發了一個不延時的訊息,即when為110000,此時MessageQueue如圖: 所以,Message在MessageQueue中是根據when從小到大來排隊的,when是開機到現在的時間+延時時間。4.5 MessageQueue的next會造成App的ANR麼
我們知道Activity如果5s的事件都不能相應使用者的請求,則會ANR。我們在來回顧下Looper.loop方法:
public static void loop() {
final Looper me = myLooper();
// 取到當前執行緒的MessageQueue
final MessageQueue queue = me.mQueue;
// 死迴圈
for (;;) {
// 呼叫MessageQueue.next,從佇列中取出訊息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 對訊息對分發
msg.target.dispatchMessage(msg);
// 省略無關程式碼
}
}
複製程式碼
在前面講過Looper.loop方法維持了App的執行,它是裡面使用了一個死迴圈,我們App平常的操作(Activity的生命週期、點選事件等)都是屬於呼叫了 msg.target.dispatchMessage(msg)對訊息的處理。但是如果MessageQueue中沒有訊息時,MessageQueue的next方法會阻塞,那它會導致App對ANR麼?
我們來看看MessageQueue的next方法
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 死迴圈
for (;;) {
// 阻塞MessageQueue
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;
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) {
// 如果是延時訊息,則算出需要阻塞的時間nextPollTimeoutMillis
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,阻塞MessageQueue
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 一般都會滿足if條件,然後mBlocked設定為true,繼續continue
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// 省略無關程式碼
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
複製程式碼
next方法中,首先設定一個死迴圈,然後呼叫nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一個native方法,用於阻塞MessageQueue,主要是關注它的第二個引數nextPollTimeoutMillis,有如下三種可能:
- 如果nextPollTimeoutMillis=-1,一直阻塞不會超時。
- 如果nextPollTimeoutMillis=0,不會阻塞,立即返回。
- 如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時),如果其間有程式喚醒會立即返回。
我們先繼續往下看,開始nextPollTimeoutMillis為0,也就是不會阻塞,則繼續往下,這時候有三種情況
- 延時訊息,則直接算出目前需要延時的時間nextPollTimeoutMillis,注意,這時候並沒有把訊息返回,而是繼續往下,設定mBlocked為true,表示訊息佇列已阻塞,並continue執行for迴圈體,再次執行nativePollOnce方法,這時候nextPollTimeoutMillis>0,則會導致MessageQueue休眠nextPollTimeoutMillis毫秒,接著應該會走到情況2.
- 不是延時訊息,則設定mBlocked為false,表示訊息佇列沒有阻塞,直接把訊息返回,且把訊息出隊。
- 如果訊息為空,則呼叫位置nextPollTimeoutMillis為-1,繼續往下,設定mBlocked為true,表示訊息佇列已阻塞,並continue繼續for迴圈,這時候呼叫nativePollOnce會一直阻塞,且不會超時。
所以,當訊息佇列為空時,其實是呼叫本地方法nativePollOnce,且第二個引數為-1,它會導致當前執行緒阻塞,且不會超時,所以不會出現ANR的情況,其實這是由Linux的管道機制(pipe)來保證的,當執行緒阻塞時,對CPU等資源等消耗時極低的,具體的原理可以自行查閱。
那執行緒阻塞以後,什麼時候才能再喚醒呢?記得之前我們說訊息加入MessageQueue的邏輯麼?我們再來回顧一下enqueueMessage的流程:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// 如果MessageQueue為空
needWake = mBlocked;
} else {
// 如果MessageQueue中有訊息
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; // invariant: p == prev.next
prev.next = msg;
}
// 如果需要喚醒當前執行緒,則呼叫nativeWake方法
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製程式碼
其實就是使用Handler傳送訊息,把訊息加入到MessageQueue時,會判斷當前MessageQueue是否阻塞,如果阻塞了,則需要呼叫nativeWake方法去喚醒執行緒,而這個阻塞是在前面提到的MessageQueue的next方法中,MessageQueue沒有訊息或者訊息為延時訊息時設定的。 所以MessageQueue的next方法可能會阻塞執行緒,但不會造成ANR。
- 當MessageQueue沒有訊息時,next方法中呼叫nativePollOnce導致執行緒阻塞,直到有新訊息加入MesssageQueue時呼叫nativeWake來喚醒執行緒
- 當MessageQueue有訊息時且隊頭訊息為延時訊息時,next方法呼叫nativePollOnce導致執行緒阻塞nextPollTimeoutMillis的時間,中途有新訊息加入MessageQueue時呼叫nativeWake可以喚醒執行緒,也可以等nextPollTimeoutMillis後自動喚醒執行緒
4.6 子執行緒使用Toast
有時候,我們需要在子執行緒中直接彈出Toast來提示一些資訊,程式碼如下:
public class ToastActivity extends AppCompatActivity {
private static final String TAG = "ToastActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toast);
new Thread(new Runnable() {
@Override
public void run() {
// 子執行緒中彈出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
}
}).start();
}
}
複製程式碼
執行程式,會發現程式會崩潰。是因為子執行緒不能更新UI麼?其實不是不這樣的。
- Toast是屬於系統Window,不受子執行緒更新UI限制。
- onCreate方法中,子執行緒可能可以更新UI,因為子執行緒不能更新UI的檢測是在ViewRootImpl的checkThread完成的,而onCreate方法中,ViewRootImpl還沒有建立,所以不會去檢測。
既然不是這兩方面的原因,我們來看看報錯的log吧
這跟我們在子執行緒中直接使用handler好像報的錯誤類似,那我們也跟使用hendler的套路一樣,先呼叫Looper.prepare然後再呼叫Looper.loop呢?程式碼如下:new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
// 子執行緒中彈出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
複製程式碼
發現就可以了,可以看出Toast也是需要使用Handler,我們來看看Toast的實現,直接看Toast中的一個內部類TN,它是一個IBinder實現類,我們來看它的定義:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
// 呼叫handleShow,處理顯示邏輯
handleShow();
}
}
final Handler mHandler = new Handler();
// 省略無關程式碼
WindowManager mWM;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
// 這裡是Binder執行緒池,用handler切換到有Looper的執行緒
mHandler.post(mShow);
}
// Toast的真實顯示
public void handleShow() {
if (mView != mNextView) {
mView = mNextView;
// 省略無關程式碼
// 把View加入到Window上
mWM.addView(mView, mParams);
}
}
//省略無關程式碼
}
複製程式碼
可以看出,Toast的顯示,使用了Binder通訊,其實就是WindowManagerService會拿到TN物件,呼叫其show方法,但是這是Binder執行緒池中執行的,所以使用handler切換到呼叫Toast的show方法所在的執行緒去執行,這裡使用的就是handler.post,所以就需要呼叫Toast.show方法所線上程有Looper。最後呼叫的就是handleShow方法,把View載入到Window上。
總結一下Handler:
- Toast是系統Window來實現的
- Toast的顯示使用了IPC
- Toast的顯示使用了Handler機制
- 子執行緒可以使用Toast,不過需要使用Handler的套路
4.7 Looper.loop可以停止麼?
前面我們知道,loop方法中是一個死迴圈,又因為程式碼是順序執行的,所以它之後的程式碼是得不到執行的,如下:
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 開啟一個子執行緒,去執行非同步任務
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop之前" );
// Looper.loop方法是一個死迴圈
Looper.loop();
// 得不到執行
Log.e(TAG, "Looper.loop之後" );
}
}).start();
}
}
複製程式碼
log如圖,只會列印loop方法之前的,loop之後的程式碼得不到執行:
這樣我們就要考慮一個問題了,並不是所有執行緒都需要像主執行緒一樣一直執行下去,有些執行緒希望做完耗時任務後能回收,但是因為Looper.loop方法,導致執行緒只是阻塞,隨時有被喚醒的可能,不能釋放。那有什麼辦法能停止loop方法麼?其實Looper提供了quit和quitSafely方法來停止Looper,我們先來看看quit的用法,在點選事件中呼叫了Looper的quit方法,修改後的程式碼如下
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 開啟一個子執行緒,去執行非同步任務
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop之前" );
// Looper.loop方法是一個死迴圈
Looper.loop();
// 得不到執行
Log.e(TAG, "Looper.loop之後" );
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 呼叫Looper的quit方法,停止Looper
mHandler.getLooper().quit();
}
});
}
}
複製程式碼
開始和之前一樣,Looper.loop後的方法不會得到執行,我們點選按鈕後,Looper會停止,Looper.loop之後的程式碼也可以得到執行,log如下:
我們來看看Looper的quit和quitSafely的原始碼: public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
複製程式碼
我們發現,兩個方法都是呼叫了MessageQueue的quit方法,只是傳入的引數不同,我們來看看MessageQueue的quit方法:
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
// MessageQueue正在停止,用於next方法退出死迴圈
mQuitting = true;
if (safe) {
// 刪除MessageQueue中的延時訊息
removeAllFutureMessagesLocked();
} else {
// 刪除MessageQueue中的所有訊息
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
複製程式碼
首先把mQuitting設定為true,主要用於MessageQueue的next方法退出死迴圈,然後通過safe去判斷邏輯邏輯,這裡就可以看出Looper的quit和quitSafely的區別了
- quit: 刪除MesageQueue中所有訊息
- quitSafely: 刪除MessageQueue中的延時訊息
我們繼續看mQuitting對MessageQueue的next方法的影響,回到next方法,我們只看關鍵性程式碼:
Message next() {
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
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();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// 判斷mQuitting
if (mQuitting) {
dispose();
return null;
}
}
}
}
複製程式碼
直接看最後的部分,對mQuitting做判斷,我們之前在MessageQueue的quit方法中,會把這個屬性設定為true,其實就是會影響到這裡。滿足條件以後,呼叫了dispose方法,並返回了null。
我們先來看dispose方法
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
複製程式碼
其實就是呼叫了nativeDestroy方法,它是一個native方法,用於在底層停止MessageQueue。
這裡只是停止了MessageQueue的next中的死迴圈,Looper.loop方法中的死迴圈還是沒有退出,我們繼續看Looper.loop方法。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
// 當mQuitting為true,queue.next方法返回了null
Message msg = queue.next(); // might block
if (msg == null) {
// 直接return,退出loop的死迴圈
return;
}
// 省略無關程式碼
}
}
複製程式碼
前面我們知道,當呼叫了Looper的quit或者quitSafely時,會設定當前執行緒的MessageQueue的 mQuitting為true,然後導致了MessageQueue的next返回了null,然後直接return了,退出了loop中的死迴圈,這樣就完成了停止Looper的邏輯。
4.8 Handler的記憶體洩漏
我們通常會使用如下的方式去使用handler來通訊
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 匿名內部類
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 處理訊息
}
};
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 傳送延時100s的訊息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 使用leakcanary做記憶體洩漏檢測
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
if (refWatcher != null) {
refWatcher.watch(this);
}
}
}
複製程式碼
但是會有一個問題,我們進入這個頁面然後點選按鈕,傳送一個延時100s的訊息,再退出這個Activity,這時候可能導致記憶體洩漏。
根本原因是因為我們建立的匿名內部類Handler物件持有了外部類Activity的物件,我們知道,當使用handler傳送訊息時,會把handler作為Message的target儲存到MessageQueue,由於延時了100s,所以這個Message暫時沒有得到處理,這時候它們的引用關係為MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,如下圖所示
當退出這個Activity時,因為Handler還持有Activity,所以gc時不能回收該Activity,導致了記憶體洩漏,使用LeakCanary檢測,效果如下圖所示: 當然,過了100s,延時訊息得到了處理,Activity物件屬於不可達的狀態時,會被回收。那怎麼來解決Handler洩漏呢?主要有如下兩種方式:
- 靜態內部類+弱引用
- 移除MessageQueue中的訊息
4.8.1 靜態內部類+弱引用
我們知道,靜態內部類是不會引用外部類的物件的,但是既然靜態內部類物件沒有持有外部類的物件,那麼我們怎麼去呼叫外部類Activity的方法呢?答案是使用弱引用。程式碼如下:
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 建立Handler物件,把Activity物件傳入
mHandler = new MyHandler(HandlerActivity.this);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 傳送延時100s的訊息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
// 靜態內部類
static class MyHandler extends Handler {
private WeakReference<Activity> activityWeakReference;
public MyHandler(Activity activity) {
activityWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 處理訊息
if (activityWeakReference != null) {
Activity activity = activityWeakReference.get();
// 拿到activity物件以後,呼叫activity的方法
if (activity != null) {
}
}
}
}
}
複製程式碼
首先,我們自定義了一個靜態內部類MyHandler,然後建立MyHandler物件時傳入當前Activity的物件,供Hander以弱應用的方式持有,這個時候Activity就被強引用和弱引用兩種方式引用了,我們繼續發起一個延時100s的訊息,然後退出當前Activity,這個時候Activity的強引用就不存在了,只存在弱引用,gc執行時會回收掉只有弱引用的Activity,這樣就不會造成記憶體洩漏了。
但這個延時訊息還是存在於MessageQueue中,得到這個Message被取出時,還是會進行分發處理,只是這時候Activity被回收掉了,activity為null,不能再繼續呼叫Activity的方法了。所以,其實這是Activity可以被回收了,而Handler、Message都不能被回收。
至於為什麼使用弱引用而沒有使用軟引用,其實很簡單,對比下兩者回收前提條件就清楚了
- 弱引用(WeakReference): gc執行時,無論記憶體是否充足,只有弱引用的物件就會被回收
- 軟引用(SoftReference): gc執行時,只有記憶體不足時,只有軟引用的物件就會被回收
很明顯,當我們Activity退出時,我們希望不管記憶體是否足夠,都應該回收Activity物件,所以使用弱引用合適。
4.8.2 移除MessageQueue中的訊息
我們知道,記憶體洩漏的源頭是MessageQueue持有的Message持有了Handler持有了Activity,那我們在合適的地方把Message從MessageQueue中移除,不就可以解決記憶體洩漏了麼?
Handler為我們提供了removeCallbacksAndMessages等方法用於移除訊息,比如,在Activity的onDestroy中呼叫Handler的removeCallbacksAndMessages,程式碼如下:
@Override
protected void onDestroy() {
super.onDestroy();
// 移除MessageQueue中target為該mHandler的Message
mHandler.removeCallbacksAndMessages(null);
}
複製程式碼
其實就是在Activity的onDestroy方法中呼叫mHandler.removeCallbacksAndMessages(null),這樣就移除了MessageQueue中target為該mHandler的Message,因為MessageQueue沒有引用該Handler傳送的Message了,所以當Activity退出時,Message、Handler、Activity都是可回收的了,這樣就能解決記憶體洩漏的問題了。
5. 結尾
Handler的知識不多,但細節特別多,一旦久一點沒看就會忘記。 所以,不管是別人寫的還是自己寫的,先把相關知識記下來,下次忘記了回來再看一下就行了。