DialogFragment的内存泄漏问题
前段时间,leakcanary报了一个有关dialogFragment的内存泄露,当时心里就犯嘀咕了,我这个DialogFragment业务很简单呀,也没用到handler这些东西。翻了翻源码,DialogFragment中的dialog,就是通过handler来执行dismiss,cancel,show这些操作。那难道是源码造成了泄漏?
源码分析
在DialogFragment的生命周期onActivityCreated中,mDialog被初始化,同时设置了onCancelListener和onDismissListener,这个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;
}
}
咋一看这也没啥问题。通过查看日志,发现每次造成泄漏的线程都不是同一个,可明明DialogFragment就是通过主线程去启动的,咋会跑到其他线程去。难道是message复用机制导致的?
Message的复用
obtainMessage会去执行Message.obtain(),这里就是message的复用机制了,通过链表结构的sPool获取message。这个message为啥可能会造成泄漏呢?
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();
}
来看看sPool如何工作的,当消息队列中处理完事件后,会执行Message.recycleUnchecked,回收message,很明显各种参数都被初始化的,咋还会造成内存泄漏呢?虽然message里面没有保存什么信息,但message还是属于HanderThread的。等于说所有的HandlerThread对象,都会造成内存泄漏,只不过没被复用的时候,泄漏的只是一个空的message对象而已。
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++;
}
}
}
情景再现
这里举个例子说明下DialogFragment泄漏的原因。
handlderThreadA的最后一个message在执行完后,被置为message.sPool,同时进入静默状态,造成空message的泄漏。DialogFragment在设置listener时,使用obtainMessage方法。Message.obtain会复用sPool,也就有可能获取到handlderThreadA的空message,给已经泄露的空message赋值obj,就会导致DialogFragment泄露。
找到问题的原因,问题也就好解决了,在DialogFragment销毁的时候,获取所有存活的HandlerThread,给他们每个人发个空消息,用来替换被泄漏的message。