DialogFragment的內(nèi)存泄漏問(wèn)題
前段時(shí)間,leakcanary報(bào)了一個(gè)有關(guān)dialogFragment的內(nèi)存泄露,當(dāng)時(shí)心里就犯嘀咕了,我這個(gè)DialogFragment業(yè)務(wù)很簡(jiǎn)單呀,也沒(méi)用到handler這些東西。翻了翻源碼,DialogFragment中的dialog,就是通過(guò)handler來(lái)執(zhí)行dismiss,cancel,show這些操作。那難道是源碼造成了泄漏?
源碼分析
在DialogFragment的生命周期onActivityCreated中,mDialog被初始化,同時(shí)設(shè)置了onCancelListener和onDismissListener,這個(gè)listener就是DialogFragment本身。
public void setOnCancelListener(@Nullable OnCancelListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnCancelListener is already taken by "
+ mCancelAndDismissTaken + " and can not be replaced.");
}
if (listener != null) {
mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);
} else {
mCancelMessage = null;
}
}
public void setOnDismissListener(@Nullable OnDismissListener listener) {
if (mCancelAndDismissTaken != null) {
throw new IllegalStateException(
"OnDismissListener is already taken by "
+ mCancelAndDismissTaken + " and can not be replaced.");
}
if (listener != null) {
mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener);
} else {
mDismissMessage = null;
}
}
咋一看這也沒(méi)啥問(wèn)題。通過(guò)查看日志,發(fā)現(xiàn)每次造成泄漏的線(xiàn)程都不是同一個(gè),可明明DialogFragment就是通過(guò)主線(xiàn)程去啟動(dòng)的,咋會(huì)跑到其他線(xiàn)程去。難道是message復(fù)用機(jī)制導(dǎo)致的?
Message的復(fù)用
obtainMessage會(huì)去執(zhí)行Message.obtain(),這里就是message的復(fù)用機(jī)制了,通過(guò)鏈表結(jié)構(gòu)的sPool獲取message。這個(gè)message為啥可能會(huì)造成泄漏呢?
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();
}
來(lái)看看sPool如何工作的,當(dāng)消息隊(duì)列中處理完事件后,會(huì)執(zhí)行Message.recycleUnchecked,回收message,很明顯各種參數(shù)都被初始化的,咋還會(huì)造成內(nèi)存泄漏呢?雖然message里面沒(méi)有保存什么信息,但message還是屬于HanderThread的。等于說(shuō)所有的HandlerThread對(duì)象,都會(huì)造成內(nèi)存泄漏,只不過(guò)沒(méi)被復(fù)用的時(shí)候,泄漏的只是一個(gè)空的message對(duì)象而已。
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
情景再現(xiàn)
這里舉個(gè)例子說(shuō)明下DialogFragment泄漏的原因。
handlderThreadA的最后一個(gè)message在執(zhí)行完后,被置為message.sPool,同時(shí)進(jìn)入靜默狀態(tài),造成空message的泄漏。DialogFragment在設(shè)置listener時(shí),使用obtainMessage方法。Message.obtain會(huì)復(fù)用sPool,也就有可能獲取到handlderThreadA的空message,給已經(jīng)泄露的空message賦值obj,就會(huì)導(dǎo)致DialogFragment泄露。
找到問(wèn)題的原因,問(wèn)題也就好解決了,在DialogFragment銷(xiāo)毀的時(shí)候,獲取所有存活的HandlerThread,給他們每個(gè)人發(fā)個(gè)空消息,用來(lái)替換被泄漏的message。