首先,要分析Handler出现泄漏的原因,这里我们查看Handler相关源码即可
// 从这里出发
handler.sendMessageDelayed(message, 1*60*1000);
// 这里是Handler的sendMessageDelayed方法
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 获取当前Handler所指向的MessageQueue
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
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);
}
// 核心问题就出现在这里
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);
}
核心问题就出现在这里enqueueMessage
,这里的msg.target = this
相当于将msg.target
指向了当前的Handler,这样就导致当前Handler永远无法被gc回收(因为存在引用),如果作为内部类的Handler无法被回收,那么作为外部类的Activity自然也无法被回收。
那么我们看Handler机制是怎样解决这个问题的,其实很简单,在Looper
的loop()
方法里就有:
public static void loop() {
// ... 过滤其他的代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// ... 过滤其他的代码
msg.target.dispatchMessage(msg);
// ... 过滤其他的代码
msg.recycleUnchecked();
}
}
在loop()
里通过msg.target.dispatchMessage(msg)
触发Handler的handleMessage
,然后通过msg.recycleUnchecked()
对当前msg
进行清除引用
void recycleUnchecked() {
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++;
}
}
}
在recycleUnchecked()
里,通过target = null
将msg的target重置为null,这样也就让Handler失去了引用,那么gc就能正常的回收Handler了
既然源码已经分析完了,那么我们就能知道我们日常使用的时候出现泄漏的原因了:handler
通过sendMessageDelayed
发送的延迟消息,由于延迟时间过长,当前Activity
可能已经关闭,导致在消息被消费之前,gc无法回收handler
,而又由于handler
作为非静态内部类会持有外部类的引用,Activity
也无法被gc回收。
解决方案有三:
- 1:使用静态的
Handler
,然后通过weakreference
传入Activity
,这样就能保证Activity
会被回收,但是Handler
依然无法回收 - 2:使用作为外部类的
Handler
,然后通过weakreference
传入Activity
。这里的情况和上面完全相同 - 3:在
Activity
的onDestroy()
方法里调用handler.removeCallbacksAndMessages(null)
,将target为当前handler的所有msg全部清理,这样handler
就会被gc直接回收,然后Activity
也会被回收
我们来看handler.removeCallbacksAndMessages(null)
到底做了啥:
/**
* 移除obj为参数token的所有msg,如果token为null的话,移除所有msg
*/
public final void removeCallbacksAndMessages(@Nullable Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
// 清空该msg里的引用
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
// 清空该msg里的引用
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
从这里可以看出,调用了msg.recycleUnchecked()
清理当前msg的所有引用,从而解决了内存泄漏的问题