簡書對(duì)文章長度有限制,緊跟著上一篇文章:
(Handler知識(shí)收集整理-1)
http://www.lxweimin.com/p/4b0bfea99e00
這里接著寫。。。。
4. 面試常見問題
- Android中,有哪些是基于Handler來實(shí)現(xiàn)通信的?
答:App的運(yùn)行、更新UI、AsyncTask、Glide、RxJava等
- 處理Handler消息,是在哪個(gè)線程?一定是創(chuàng)建Handler的線程么?
答:創(chuàng)建Handler所使用的Looper所在的線程
- 消息是如何插入到MessageQueue中的?
答: 是根據(jù)when在MessageQueue中升序排序的,when=開機(jī)到現(xiàn)在的毫秒數(shù)+延時(shí)毫秒數(shù)
- 當(dāng)MessageQueue沒有消息時(shí),它的next方法是阻塞的,會(huì)導(dǎo)致App ANR么?
答:不會(huì)導(dǎo)致App的ANR,是Linux的pipe機(jī)制保證的,阻塞時(shí),線程掛起;需要時(shí),喚醒線程
- 子線程中可以使用Toast么?
答:可以使用,但是Toast的顯示是基于Handler實(shí)現(xiàn)的,所以需要先創(chuàng)建Looper,然后調(diào)用Looper.loop。
- Looper.loop()是死循環(huán),可以停止么?
答:可以停止,Looper提供了quit和quitSafely方法
- Handler內(nèi)存泄露怎么解決?
答: 靜態(tài)內(nèi)部類+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息
下面逐個(gè)來看看這些問題:
4.1 Android中常見的Handler使用
- 保證App的運(yùn)行
App的入口其實(shí)是ActivityThread的main方法:
public static void main(String[] args) {
// 創(chuàng)建主線程中的Looper
Looper.prepareMainLooper();
// 創(chuàng)建ActivityThread對(duì)象
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
// 創(chuàng)建用于通信的Handler
sMainThreadHandler = thread.getHandler();
}
// 執(zhí)行l(wèi)oop方法
Looper.loop();
}
可以看到這個(gè)main方法中,主要有如下幾步:
- 創(chuàng)建了主線程中的Looper,創(chuàng)建Looper時(shí),會(huì)創(chuàng)建一個(gè)MessageQueue用于存放消息。
- 創(chuàng)建了ActivityThread對(duì)象,并調(diào)用了它的attach方法,這個(gè)方法就是去創(chuàng)建Application、調(diào)用Application的onCreate方法以及告訴ActivityManagerService現(xiàn)在App啟動(dòng)了。
- 創(chuàng)建了用于通信的Handler,它是一個(gè)H對(duì)象。
- 調(diào)用Looper.loop方法,開始循環(huán)從主線程中的MessageQueue中取出消息來處理。
回顧下,Handler機(jī)制的原理圖:
可以知道,App啟動(dòng)后,因?yàn)長ooper.loop是一個(gè)死循環(huán),導(dǎo)致main方法一直沒有執(zhí)行完,也就是說,我們后續(xù)App中的所有操作,都是發(fā)生在Looper.loop中的
那Handler機(jī)制,是怎么保證App的運(yùn)行的呢?我們來看看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,定義了很多消息的類型,比如啟動(dòng)Activity、停止Activity、顯示W(wǎng)indow、電量過低等,它重寫了handleMessage方法,用于處理這些消息。
那發(fā)消息是在哪里發(fā)的呢?我們以鎖屏調(diào)用Activity的onStop生命周期為例,其實(shí)就是在ApplicationThread中,它是一個(gè)ActivityThread的內(nèi)部類我們拿啟動(dòng)Activity這個(gè)消息舉例,ActivityManagerService會(huì)先通過binder調(diào)用ApplicationThread中的scheduleStopActivity方法(牽涉到進(jìn)程間通信,不懂可以略過),這個(gè)方法是在我們App的Bindler線程池中執(zhí)行的,那看看它是怎么切換到主線程去啟動(dòng)Activity的。
public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) {
sendMessage(
showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
token, 0, configChanges);
}
我們看到,這里就是調(diào)用sendMessage方法,第一個(gè)參數(shù),不就是上面H中定義的消息類型么?接著看 sendMessage方法,它最后會(huì)調(diào)用到如下這個(gè)多參的構(gòu)造方法:
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發(fā)送一個(gè)消息,而mH就是ActivityThread中定義的類型為H的成員變量,定義如下:
final H mH = new H();
所以,調(diào)用鎖屏?xí)r,調(diào)用Activity的onStop方法,流程如下:
- ActivityManagerService通過binder方式,調(diào)用ApplicationThread的scheduleStopActivity方法
- ApplicationThread的scheduleStopActivity方法,通過H把消息加入到主線線程的MessageQueue中
- 主線程的Looper遍歷MessageQueue,取到該消息時(shí),調(diào)用H的handleMessage方法進(jìn)行處理
- 在H的handleMessage中,調(diào)用Activity的onStop方法
流程圖如下:
那么,我們App運(yùn)行原理就出來了,App的運(yùn)行依賴于Handler機(jī)制,當(dāng)主線程當(dāng)MessageQueue有消息時(shí),主線程的Looper.loop會(huì)不斷從主線程中的MessageQueue中取出消息來處理(比如Activity的onCreate其實(shí)就是屬于對(duì)MessageQueue中取出的一個(gè)消息的處理),這樣就保證了App運(yùn)行
當(dāng)MessageQueue沒有消息時(shí),MessageQueue的next方法會(huì)阻賽,導(dǎo)致當(dāng)前線程掛起,等有消息(一般為系統(tǒng)進(jìn)程通過binder調(diào)用App的ApplicationThread中的方法,注意,方法在binder線程池中執(zhí)行,然后ApplicationThread使用ActivityThread中的H對(duì)象發(fā)送消息,加入消息到主線程的MessageQueue中,當(dāng)發(fā)現(xiàn)主線程被掛起了,則會(huì)喚醒主線程)
所以,當(dāng)沒有任何消息時(shí),我們的App的主線程,是屬于掛起的狀態(tài)。有消息來時(shí)(鎖屏、點(diǎn)擊等),主線程會(huì)被喚醒,所以說,Handler機(jī)制保證了App的運(yùn)行。
4.2 Handler更新UI
我們知道,如果在子線程直接更新UI會(huì)拋出異常,異常如下:
我們可以使用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();
這種方式,就是在子線程中,用主線程中創(chuàng)建的hander調(diào)用sendMessage發(fā)送消息,把Message加入到主線程的MessageQueue中,等主線程的Looper從MessageQueue中取到這個(gè)消息時(shí),會(huì)調(diào)用這個(gè)Message的target的handleMessage方法,這個(gè)target其實(shí)就是我們發(fā)消息用到的handler,也就是調(diào)用了我們重寫的handleMessage方法。
發(fā)消息:Handler.sendMessage(Message)
處理消息:Message.target.handleMessage(其中target就是發(fā)消息的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方法需要傳入一個(gè)Runnalbe對(duì)象。我們來看看post方法源碼
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
可以看到,先調(diào)用了getPostMessage構(gòu)建了一個(gè)Message對(duì)象,然后調(diào)用了sendMessageDelayed方法,前面知道,sendMessage也是調(diào)用的這個(gè)方法,所以我們只要關(guān)注怎么構(gòu)建的Message對(duì)象,看getPostMessage方法。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
// post方法傳入的Runnable對(duì)象作為Message的callback
m.callback = r;
return m;
}
其實(shí)很簡單,就是把post方法傳入的參數(shù)作為Message的callback來創(chuàng)建一個(gè)Message。
我們再來回顧一下從MessageQueue中取出消息來對(duì)消息對(duì)處理,方法是Handler對(duì)dispatchMessage方法
public void dispatchMessage(Message msg) {
// callback其實(shí)就是post方法傳入對(duì)Runnable對(duì)象
if (msg.callback != null) {
handleCallback(msg);
} else {
// 不會(huì)執(zhí)行到這里
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
// 相當(dāng)于調(diào)用了post方法傳入對(duì)Runnable對(duì)象對(duì)run方法
message.callback.run();
}
可以知道,我們使用post發(fā)送消息,就是使用傳入對(duì)Runnable對(duì)象封裝成一個(gè)Message,然后加入到主線程中的MessageQueue,等主線程的Looper取出該消息處理時(shí),因?yàn)镸essage.callback不為空,而調(diào)用其run方法,也就是我們調(diào)用post方法傳入的Runnable對(duì)象的run方法,且不會(huì)調(diào)用Hander的handleMessage方法。
發(fā)送消息:Handler.post(Runnable)
處理消息:Message.callback.run(callback為調(diào)用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();
我們?nèi)绻茉谧泳€程中獲取到Activity對(duì)象,是可以調(diào)用其runOnUiThread方法,來更新UI。我們來看看Activity的runOnUiThread源碼。
public final void runOnUiThread(Runnable action) {
// 如果不是UI線程,則調(diào)用Handler的post
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
// 如果是ui線程,則直接回調(diào)Runnable的run方法
action.run();
}
}
如果是UI線程,就直接調(diào)用了傳入的Runnable對(duì)象的run方法,我們主要是看非UI線程的邏輯。
如果不是UI線程,則直接使用mHandler的post方法,看來Activity的runOnUiThread方法也是基于Handler的post方法來實(shí)現(xiàn)的,后面的邏輯就是把傳入的Runnable封裝成Message發(fā)送出去,上面講過了,就不再復(fù)述了。
我們再看看這個(gè)mHandler的定義,其實(shí)就是Activity的成員屬性
final Handler mHandler = new Handler();
Activity是在主線程創(chuàng)建的,所以這個(gè)Handler也是在主線程中創(chuàng)建的,且持有的Looper為主線程的Looper。那么使用這個(gè)Handler調(diào)用post方法發(fā)出的消息,是加入到主線程的MessageQueue中,這樣就完成了子線程跟主線程的通信。
發(fā)送消息:Activity. runOnUiThread(Runnable)
處理消息:Message.callback.run(callback為runOnUiThread方法傳入的Runnable)
2.4 View的post方法
new Thread(new Runnable() {
@Override
public void run() {
// 調(diào)用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;
}
可以看到這里也是調(diào)用的Handler的post方法,跟Activity. runOnUiThread類似。
發(fā)送消息:View.post(Runnable)
處理消息:Message.callback.run(callback為post方法傳入的Runnable對(duì)象)
總結(jié)一下:
- Handler.sendMessage: 把消息加入到主線程的MessageQueue中,主線程中的Looper從MessageQueue中取出消息,調(diào)用Message.target.handleMessage方法
- Handler.post: 基于Handler.sendMessage,把消息加入到主線程的MessageQueue中,主線程中的Looper從MessageQueue中取出消息,調(diào)用Message.callback.run方法
- Activity.runOnUiThread: 基于Handler.post
- View.post: 基于Handler.post
所以,以上子線程更新主線程UI的所有方式,都是依賴于Handler機(jī)制。
- AsyncTask
當(dāng)我們想在子線程中做耗時(shí)任務(wù)時(shí),會(huì)考慮使用AsyncTask,我們來舉個(gè)栗子,在子線程中去創(chuàng)建自定義的MyAsyncTask并執(zhí)行它,在doInBackground中去模擬耗時(shí)操作:
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);
// 開啟一個(gè)子線程,去執(zhí)行異步任務(wù)
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,并重寫相關(guān)方法,并打印執(zhí)行所在線程
class MyAsyncTask extends AsyncTask<Void, Integer, Void>{
@Override
protected Void doInBackground(Void... voids) {
Log.e(TAG, "doInBackground, thread name:" + Thread.currentThread().getName());
// 模擬耗時(shí)任務(wù)
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("執(zhí)行完了!");
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e(TAG, "onProgressUpdate, thread name:" + Thread.currentThread().getName());
}
}
}
可以看到,我們創(chuàng)建的子線程名為Thread-4,而AsyncTask的方法所在線程如下:
- onPreExecute: Thread-4,其實(shí)就是調(diào)用AsyncTask的execute方法的線程
- doInBackground: AsyncTask #1,這其實(shí)是AsyncTask的線程池中的一個(gè)線程
- onProgressUpdate: main,即主線程
- onPostExecute: main,即主線程
關(guān)于onPreExecute,這很好實(shí)現(xiàn),不需要切換線程,直接回調(diào)就可以;而doInBackground方法的執(zhí)行,可以直接取AsyncTask維持的線程池來執(zhí)行就可以。我們重點(diǎn)關(guān)注onProgressUpdate和onPostExecute方法,是怎么從子線程切換到主線程的。
我們從AsyncTask的源碼中可以看到這樣一個(gè)內(nèi)部類
private static class InternalHandler extends Handler {
public InternalHandler() {
// 使用主線程的Looper去創(chuàng)建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中主要調(diào)用onPostExecute
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// 調(diào)用onProgressUpdate
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
從handleMessage方法看到,這里切換到主線程,也是使用的Handler機(jī)制來實(shí)現(xiàn)的,但是為什么我們不管在任何線程創(chuàng)建的AsyncTask去執(zhí)行,最后都可以在主線程去回調(diào)這兩個(gè)方法呢?主要是創(chuàng)建InternalHandler時(shí),使用的是主線程的Looper,這樣使用這個(gè)InternalHandler發(fā)送消息時(shí),消息就會(huì)加入到主線程Looper對(duì)應(yīng)的MessageQueue中,所以主線程Looper取出消息處理時(shí),InternalHandler的handleMessage方法就是在主線程中回調(diào)的了。
所以AsyncTask其實(shí)就是基于線程池+Handler機(jī)制來實(shí)現(xiàn)的。
其他使用Handler的地方
RxJava: 子線程切換到主線程執(zhí)行觀察者的回調(diào)方法(RxJava我不熟悉)
Glide:圖片準(zhǔn)備好以后的回顯
LocalBroadcastManager: 傳統(tǒng)的廣播是基于binder實(shí)現(xiàn)的,本地廣播LocalBroadcastManager是基于Handler實(shí)現(xiàn)的
其實(shí)還有很多使用到handler機(jī)制的地方,就不一一舉例了,反正記住,Handler機(jī)制很重要。
4.3 處理Handler消息,是在哪個(gè)線程?一定是創(chuàng)建Handler的線程么?
以前總覺得,處理消息的線程,就是創(chuàng)建Handler的線程,但是上一篇文章的分析,我們知道這樣說其實(shí)是不準(zhǔn)確的(因?yàn)槲覀儎?chuàng)建Handler通常使用的默認(rèn)Looper)。
處理消息的線程,其實(shí)是發(fā)送handler所持有的Looper所在的線程。
其實(shí)原理很好理解,我們知道Handler的原理如圖
所以,消息的處理分發(fā)所在線程完全取決于消息所在MessageQueue的線程,如果想要在某個(gè)線程中處理消息,只要做到把消息加入到那個(gè)線程所對(duì)應(yīng)的MessageQueue中。
就像上面講到AsyncTask的例子,就算我們在子線程創(chuàng)建了AsyncTask(即在子線程創(chuàng)建了用于通信的Handler),但只要我們創(chuàng)建Handler的時(shí)候,通過Looper.getMainLooper()傳入主線程的Looper ,那么消息就加入到了主線程所對(duì)應(yīng)的MessageQueue中,消息就是在主線程中處理的。
如下,我們在子線程中創(chuàng)建一個(gè)handler,然后在主線程發(fā)送消息,因?yàn)閯?chuàng)建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);
// 開啟一個(gè)子線程,去創(chuàng)建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) {
// 發(fā)送一個(gè)消息
mHandler.sendEmptyMessage(100);
}
});
}
}
log輸出,可以看出,處理消息是在子線程中:
按照之前的說法,如果我們想在主線程中處理消息,只要把消息加入到主線程的MessageQueue中,所以我們可以創(chuàng)建Looper時(shí),傳入主線程的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);
// 開啟一個(gè)子線程,去創(chuàng)建Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
// 創(chuàng)建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) {
// 發(fā)送一個(gè)消息
mHandler.sendEmptyMessage(100);
}
});
}
}
可以看到,創(chuàng)建Handler使用了主線程的Looper后,的確消息是在主線程中處理的了:
4.4 是如何插入到MessageQueue中?
我們之前說,所有handler.post和handler.sendMessage都會(huì)調(diào)用到Handler的sendMessageDelayed方法,方法如下:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
這里邏輯就很簡單了,直接調(diào)用了sendMessageAtTime方法,第一個(gè)參數(shù)為Message,第二個(gè)參數(shù)為SystemClock.uptimeMillis() + delayMillis,其中delayMillis為延時(shí)的時(shí)間,單位為毫秒,SystemClock.uptimeMillis() 為開機(jī)到現(xiàn)在的時(shí)間(不包括休眠時(shí)間),單位為毫秒。第二個(gè)參數(shù)主要是決定該Message在MessageQueue的順序,比如現(xiàn)在開機(jī)時(shí)間為100s,發(fā)送一個(gè)延時(shí)20s的消息,則兩者之和為120s; 過了5秒,又發(fā)了一個(gè)延時(shí)5s的消息,則兩者只喝為105+5 = 110s。
sendMessageAtTime最后會(huì)調(diào)用到MessageQueue的enqueueMessage方法,我們來看看這個(gè)方法:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// when = 開機(jī)到目前的時(shí)間 + 延時(shí)時(shí)間
msg.when = when;
Message p = mMessages;
boolean needWake;
// 如果當(dāng)前消息隊(duì)列為空或者當(dāng)前when小于隊(duì)頭when
// 則把消息插入到隊(duì)頭
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;
// 死循環(huán),根據(jù)when尋找Message插入到MessageQueue合適的位置
for (;;) {
prev = p;
p = p.next;
// MessageQueue中依次往后找到第一個(gè)Message.when大于當(dāng)前Message.when的Message
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 把當(dāng)前Message插入到MessageQueue的合適位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 如果需要喚醒,則調(diào)用nativeWake去喚醒處理線程
nativeWake(mPtr);
}
}
return true;
}
從上面的代碼,可以很容易看出,Message在MessageQueue中是根據(jù)when從小到大來排隊(duì)的,when是開機(jī)到現(xiàn)在的時(shí)間+延時(shí)時(shí)間。
比如,我們假設(shè)開機(jī)時(shí)間為100s,此時(shí)MessageQueue沒有消息,這時(shí)候發(fā)一個(gè)延時(shí)20s的消息,即when為120000,則MessageQueue中的消息情況如圖:
過了5s,我們又發(fā)了一個(gè)延時(shí)10s的消息,則when為115000,此時(shí)MessageQueue如圖:
又過了5s,我們發(fā)了一個(gè)不延時(shí)的消息,即when為110000,此時(shí)MessageQueue如圖:
所以,Message在MessageQueue中是根據(jù)when從小到大來排隊(duì)的,when是開機(jī)到現(xiàn)在的時(shí)間+延時(shí)時(shí)間。
4.5 MessageQueue的next會(huì)造成App的ANR么
我們知道Activity如果5s的事件都不能相應(yīng)用戶的請求,則會(huì)ANR。我們在來回顧下Looper.loop方法:
public static void loop() {
final Looper me = myLooper();
// 取到當(dāng)前線程的MessageQueue
final MessageQueue queue = me.mQueue;
// 死循環(huán)
for (;;) {
// 調(diào)用MessageQueue.next,從隊(duì)列中取出消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 對(duì)消息對(duì)分發(fā)
msg.target.dispatchMessage(msg);
// 省略無關(guān)代碼
}
}
在前面講過Looper.loop方法維持了App的運(yùn)行,它是里面使用了一個(gè)死循環(huán),我們App平常的操作(Activity的生命周期、點(diǎn)擊事件等)都是屬于調(diào)用了 msg.target.dispatchMessage(msg)對(duì)消息的處理。但是如果MessageQueue中沒有消息時(shí),MessageQueue的next方法會(huì)阻塞,那它會(huì)導(dǎo)致App對(duì)ANR么?
我們來看看MessageQueue的next方法
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 死循環(huán)
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) {
// 如果是延時(shí)消息,則算出需要阻塞的時(shí)間nextPollTimeoutMillis
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 如果不是延時(shí)顯示,則直接把消息返回,以供處理
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 {
// 沒有消息時(shí),設(shè)置nextPollTimeoutMillis為-1,阻塞MessageQueue
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 一般都會(huì)滿足if條件,然后mBlocked設(shè)置為true,繼續(xù)continue
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// 省略無關(guān)代碼
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
next方法中,首先設(shè)置一個(gè)死循環(huán),然后調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一個(gè)native方法,用于阻塞MessageQueue,主要是關(guān)注它的第二個(gè)參數(shù)nextPollTimeoutMillis,有如下三種可能:
- 如果nextPollTimeoutMillis=-1,一直阻塞不會(huì)超時(shí)。
- 如果nextPollTimeoutMillis=0,不會(huì)阻塞,立即返回。
- 如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時(shí)),如果其間有程序喚醒會(huì)立即返回。
我們先繼續(xù)往下看,開始nextPollTimeoutMillis為0,也就是不會(huì)阻塞,則繼續(xù)往下,這時(shí)候有三種情況
- 延時(shí)消息,則直接算出目前需要延時(shí)的時(shí)間nextPollTimeoutMillis,注意,這時(shí)候并沒有把消息返回,而是繼續(xù)往下,設(shè)置mBlocked為true,表示消息隊(duì)列已阻塞,并continue執(zhí)行for循環(huán)體,再次執(zhí)行nativePollOnce方法,這時(shí)候nextPollTimeoutMillis>0,則會(huì)導(dǎo)致MessageQueue休眠nextPollTimeoutMillis毫秒,接著應(yīng)該會(huì)走到情況2.
- 不是延時(shí)消息,則設(shè)置mBlocked為false,表示消息隊(duì)列沒有阻塞,直接把消息返回,且把消息出隊(duì)。
- 如果消息為空,則調(diào)用位置nextPollTimeoutMillis為-1,繼續(xù)往下,設(shè)置mBlocked為true,表示消息隊(duì)列已阻塞,并continue繼續(xù)for循環(huán),這時(shí)候調(diào)用nativePollOnce會(huì)一直阻塞,且不會(huì)超時(shí)。
所以,當(dāng)消息隊(duì)列為空時(shí),其實(shí)是調(diào)用本地方法nativePollOnce,且第二個(gè)參數(shù)為-1,它會(huì)導(dǎo)致當(dāng)前線程阻塞,且不會(huì)超時(shí),所以不會(huì)出現(xiàn)ANR的情況,其實(shí)這是由Linux的管道機(jī)制(pipe)來保證的,當(dāng)線程阻塞時(shí),對(duì)CPU等資源等消耗時(shí)極低的,具體的原理可以自行查閱。
那線程阻塞以后,什么時(shí)候才能再喚醒呢?記得之前我們說消息加入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;
}
// 如果需要喚醒當(dāng)前線程,則調(diào)用nativeWake方法
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
其實(shí)就是使用Handler發(fā)送消息,把消息加入到MessageQueue時(shí),會(huì)判斷當(dāng)前MessageQueue是否阻塞,如果阻塞了,則需要調(diào)用nativeWake方法去喚醒線程,而這個(gè)阻塞是在前面提到的MessageQueue的next方法中,MessageQueue沒有消息或者消息為延時(shí)消息時(shí)設(shè)置的。
所以MessageQueue的next方法可能會(huì)阻塞線程,但不會(huì)造成ANR。
- 當(dāng)MessageQueue沒有消息時(shí),next方法中調(diào)用nativePollOnce導(dǎo)致線程阻塞,直到有新消息加入MesssageQueue時(shí)調(diào)用nativeWake來喚醒線程
- 當(dāng)MessageQueue有消息時(shí)且隊(duì)頭消息為延時(shí)消息時(shí),next方法調(diào)用nativePollOnce導(dǎo)致線程阻塞nextPollTimeoutMillis的時(shí)間,中途有新消息加入MessageQueue時(shí)調(diào)用nativeWake可以喚醒線程,也可以等nextPollTimeoutMillis后自動(dòng)喚醒線程
4.6 子線程使用Toast
有時(shí)候,我們需要在子線程中直接彈出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();
}
}
運(yùn)行程序,會(huì)發(fā)現(xiàn)程序會(huì)崩潰。是因?yàn)樽泳€程不能更新UI么?其實(shí)不是不這樣的。
- Toast是屬于系統(tǒng)Window,不受子線程更新UI限制。
- onCreate方法中,子線程可能可以更新UI,因?yàn)樽泳€程不能更新UI的檢測是在ViewRootImpl的checkThread完成的,而onCreate方法中,ViewRootImpl還沒有創(chuàng)建,所以不會(huì)去檢測。
既然不是這兩方面的原因,我們來看看報(bào)錯(cuò)的log吧
這跟我們在子線程中直接使用handler好像報(bào)的錯(cuò)誤類似,那我們也跟使用hendler的套路一樣,先調(diào)用Looper.prepare然后再調(diào)用Looper.loop呢?代碼如下:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
// 子線程中彈出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
發(fā)現(xiàn)就可以了,可以看出Toast也是需要使用Handler,我們來看看Toast的實(shí)現(xiàn),直接看Toast中的一個(gè)內(nèi)部類TN,它是一個(gè)IBinder實(shí)現(xiàn)類,我們來看它的定義:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
// 調(diào)用handleShow,處理顯示邏輯
handleShow();
}
}
final Handler mHandler = new Handler();
// 省略無關(guān)代碼
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的真實(shí)顯示
public void handleShow() {
if (mView != mNextView) {
mView = mNextView;
// 省略無關(guān)代碼
// 把View加入到Window上
mWM.addView(mView, mParams);
}
}
//省略無關(guān)代碼
}
可以看出,Toast的顯示,使用了Binder通信,其實(shí)就是WindowManagerService會(huì)拿到TN對(duì)象,調(diào)用其show方法,但是這是Binder線程池中執(zhí)行的,所以使用handler切換到調(diào)用Toast的show方法所在的線程去執(zhí)行,這里使用的就是handler.post,所以就需要調(diào)用Toast.show方法所在線程有Looper。最后調(diào)用的就是handleShow方法,把View加載到Window上。
總結(jié)一下Handler:
- Toast是系統(tǒng)Window來實(shí)現(xiàn)的
- Toast的顯示使用了IPC
- Toast的顯示使用了Handler機(jī)制
- 子線程可以使用Toast,不過需要使用Handler的套路
4.7 Looper.loop可以停止么?
前面我們知道,loop方法中是一個(gè)死循環(huán),又因?yàn)榇a是順序執(zhí)行的,所以它之后的代碼是得不到執(zhí)行的,如下:
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);
// 開啟一個(gè)子線程,去執(zhí)行異步任務(wù)
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方法是一個(gè)死循環(huán)
Looper.loop();
// 得不到執(zhí)行
Log.e(TAG, "Looper.loop之后" );
}
}).start();
}
}
log如圖,只會(huì)打印loop方法之前的,loop之后的代碼得不到執(zhí)行:
這樣我們就要考慮一個(gè)問題了,并不是所有線程都需要像主線程一樣一直運(yùn)行下去,有些線程希望做完耗時(shí)任務(wù)后能回收,但是因?yàn)長ooper.loop方法,導(dǎo)致線程只是阻塞,隨時(shí)有被喚醒的可能,不能釋放。那有什么辦法能停止loop方法么?
其實(shí)Looper提供了quit和quitSafely方法來停止Looper,我們先來看看quit的用法,在點(diǎn)擊事件中調(diào)用了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);
// 開啟一個(gè)子線程,去執(zhí)行異步任務(wù)
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方法是一個(gè)死循環(huán)
Looper.loop();
// 得不到執(zhí)行
Log.e(TAG, "Looper.loop之后" );
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 調(diào)用Looper的quit方法,停止Looper
mHandler.getLooper().quit();
}
});
}
}
開始和之前一樣,Looper.loop后的方法不會(huì)得到執(zhí)行,我們點(diǎn)擊按鈕后,Looper會(huì)停止,Looper.loop之后的代碼也可以得到執(zhí)行,log如下:
我們來看看Looper的quit和quitSafely的源碼:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
我們發(fā)現(xiàn),兩個(gè)方法都是調(diào)用了MessageQueue的quit方法,只是傳入的參數(shù)不同,我們來看看MessageQueue的quit方法:
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
// MessageQueue正在停止,用于next方法退出死循環(huán)
mQuitting = true;
if (safe) {
// 刪除MessageQueue中的延時(shí)消息
removeAllFutureMessagesLocked();
} else {
// 刪除MessageQueue中的所有消息
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
首先把mQuitting設(shè)置為true,主要用于MessageQueue的next方法退出死循環(huán),然后通過safe去判斷邏輯邏輯,這里就可以看出Looper的quit和quitSafely的區(qū)別了
- quit: 刪除MesageQueue中所有消息
- quitSafely: 刪除MessageQueue中的延時(shí)消息
我們繼續(xù)看mQuitting對(duì)MessageQueue的next方法的影響,回到next方法,我們只看關(guān)鍵性代碼:
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;
}
}
}
}
直接看最后的部分,對(duì)mQuitting做判斷,我們之前在MessageQueue的quit方法中,會(huì)把這個(gè)屬性設(shè)置為true,其實(shí)就是會(huì)影響到這里。滿足條件以后,調(diào)用了dispose方法,并返回了null。
我們先來看dispose方法
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
其實(shí)就是調(diào)用了nativeDestroy方法,它是一個(gè)native方法,用于在底層停止MessageQueue。
這里只是停止了MessageQueue的next中的死循環(huán),Looper.loop方法中的死循環(huán)還是沒有退出,我們繼續(xù)看Looper.loop方法。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
// 當(dāng)mQuitting為true,queue.next方法返回了null
Message msg = queue.next(); // might block
if (msg == null) {
// 直接return,退出loop的死循環(huán)
return;
}
// 省略無關(guān)代碼
}
}
前面我們知道,當(dāng)調(diào)用了Looper的quit或者quitSafely時(shí),會(huì)設(shè)置當(dāng)前線程的MessageQueue的 mQuitting為true,然后導(dǎo)致了MessageQueue的next返回了null,然后直接return了,退出了loop中的死循環(huán),這樣就完成了停止Looper的邏輯。
4.8 Handler的內(nèi)存泄漏
我們通常會(huì)使用如下的方式去使用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);
// 匿名內(nèi)部類
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) {
// 發(fā)送延時(shí)100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 使用leakcanary做內(nèi)存泄漏檢測
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
if (refWatcher != null) {
refWatcher.watch(this);
}
}
}
但是會(huì)有一個(gè)問題,我們進(jìn)入這個(gè)頁面然后點(diǎn)擊按鈕,發(fā)送一個(gè)延時(shí)100s的消息,再退出這個(gè)Activity,這時(shí)候可能導(dǎo)致內(nèi)存泄漏。
根本原因是因?yàn)槲覀儎?chuàng)建的匿名內(nèi)部類Handler對(duì)象持有了外部類Activity的對(duì)象,我們知道,當(dāng)使用handler發(fā)送消息時(shí),會(huì)把handler作為Message的target保存到MessageQueue,由于延時(shí)了100s,所以這個(gè)Message暫時(shí)沒有得到處理,這時(shí)候它們的引用關(guān)系為MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,如下圖所示
當(dāng)退出這個(gè)Activity時(shí),因?yàn)镠andler還持有Activity,所以gc時(shí)不能回收該Activity,導(dǎo)致了內(nèi)存泄漏,使用LeakCanary檢測,效果如下圖所示:
當(dāng)然,過了100s,延時(shí)消息得到了處理,Activity對(duì)象屬于不可達(dá)的狀態(tài)時(shí),會(huì)被回收。
那怎么來解決Handler泄漏呢?主要有如下兩種方式:
- 靜態(tài)內(nèi)部類+弱引用
- 移除MessageQueue中的消息
4.8.1 靜態(tài)內(nèi)部類+弱引用
我們知道,靜態(tài)內(nèi)部類是不會(huì)引用外部類的對(duì)象的,但是既然靜態(tài)內(nèi)部類對(duì)象沒有持有外部類的對(duì)象,那么我們怎么去調(diào)用外部類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);
// 創(chuàng)建Handler對(duì)象,把Activity對(duì)象傳入
mHandler = new MyHandler(HandlerActivity.this);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 發(fā)送延時(shí)100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
// 靜態(tài)內(nèi)部類
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對(duì)象以后,調(diào)用activity的方法
if (activity != null) {
}
}
}
}
}
首先,我們自定義了一個(gè)靜態(tài)內(nèi)部類MyHandler,然后創(chuàng)建MyHandler對(duì)象時(shí)傳入當(dāng)前Activity的對(duì)象,供Hander以弱應(yīng)用的方式持有,這個(gè)時(shí)候Activity就被強(qiáng)引用和弱引用兩種方式引用了,我們繼續(xù)發(fā)起一個(gè)延時(shí)100s的消息,然后退出當(dāng)前Activity,這個(gè)時(shí)候Activity的強(qiáng)引用就不存在了,只存在弱引用,gc運(yùn)行時(shí)會(huì)回收掉只有弱引用的Activity,這樣就不會(huì)造成內(nèi)存泄漏了。
但這個(gè)延時(shí)消息還是存在于MessageQueue中,得到這個(gè)Message被取出時(shí),還是會(huì)進(jìn)行分發(fā)處理,只是這時(shí)候Activity被回收掉了,activity為null,不能再繼續(xù)調(diào)用Activity的方法了。所以,其實(shí)這是Activity可以被回收了,而Handler、Message都不能被回收。
至于為什么使用弱引用而沒有使用軟引用,其實(shí)很簡單,對(duì)比下兩者回收前提條件就清楚了
- 弱引用(WeakReference): gc運(yùn)行時(shí),無論內(nèi)存是否充足,只有弱引用的對(duì)象就會(huì)被回收
- 軟引用(SoftReference): gc運(yùn)行時(shí),只有內(nèi)存不足時(shí),只有軟引用的對(duì)象就會(huì)被回收
很明顯,當(dāng)我們Activity退出時(shí),我們希望不管內(nèi)存是否足夠,都應(yīng)該回收Activity對(duì)象,所以使用弱引用合適。
4.8.2 移除MessageQueue中的消息
我們知道,內(nèi)存泄漏的源頭是MessageQueue持有的Message持有了Handler持有了Activity,那我們在合適的地方把Message從MessageQueue中移除,不就可以解決內(nèi)存泄漏了么?
Handler為我們提供了removeCallbacksAndMessages等方法用于移除消息,比如,在Activity的onDestroy中調(diào)用Handler的removeCallbacksAndMessages,代碼如下:
@Override
protected void onDestroy() {
super.onDestroy();
// 移除MessageQueue中target為該mHandler的Message
mHandler.removeCallbacksAndMessages(null);
}
其實(shí)就是在Activity的onDestroy方法中調(diào)用mHandler.removeCallbacksAndMessages(null),這樣就移除了MessageQueue中target為該mHandler的Message,因?yàn)镸essageQueue沒有引用該Handler發(fā)送的Message了,所以當(dāng)Activity退出時(shí),Message、Handler、Activity都是可回收的了,這樣就能解決內(nèi)存泄漏的問題了。
5. 結(jié)尾
Handler的知識(shí)不多,但細(xì)節(jié)特別多,一旦久一點(diǎn)沒看就會(huì)忘記。
所以,不管是別人寫的還是自己寫的,先把相關(guān)知識(shí)記下來,下次忘記了回來再看一下就行了。