经常听到说Handler会引起内存泄漏,今天我们就通过源码来分析下,为什么Handler会引起内存泄漏,什么情况会引起泄漏,如何避免。
一、先来看下Handler一般定义
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
通常,我们在Activity中使用匿名内部类来定义Handler。 这样会引起内存泄漏吗? 答案是,不会,虽然匿名内部类会持有其宿主对象(Activity)引用,但只要Activity关闭释放时,匿名内部类也被释放,就不会。 比如Android中的点击事件,一般我们也是定义成匿名内部类。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
二、接下来分析下Handler发送消息
通过查看源码,发送消息最后都是调用的enqueueMessage()方法。
mHandler.sendMessageDelayed(msg, 60 * 1000);
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可见,Message中引用了Handler对象。然后将该Message存储在了MessageQueue中。而Handler中的MessageQueue即是主线程的Looper中的对象。
我们知道,主线程ActivityThread在main()方法中,创建了Looper对象,从而创建了MessageQueue对象。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
所以Looper和MessageQueue是和线程一一对应的,而主线程一直存在,不被销毁,所以MessageQueue一直存在,因此Queue中的Message也一直存在。
结论1
只要Handler调用发送消息,消息进入到MessageQueue,而没有处理,那么该Handler就不会被释放,即使Activity被释放。 比如使用延迟消息发送
mHandler.sendMessageDelayed(msg, 60 * 1000);
代码执行完后,消息消费要等60s后,这时Activity关闭,那么Handler就不会释放,就会导致内存泄漏。
三、最后分析下消息处理
消息分发是在Looper.loop()中开始的
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
......
for (;;) {
Message msg = queue.next(); // might block
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
可见,处理完消息,则释放,同时释放Handler。
四、如何避免内存泄漏呢
- 使用static class 定义Handler,对Activity的引用使用弱引用
- onDestroy()
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
扩展
Android如何做到消息的延迟处理呢,通过上面的源码,我们知道,无论是正常消息,还是延迟消息,都放到了MessageQueue中,那么延迟的处理,就只能是在取消息的时候。 因此,我们看下MessageQueue.next()方法
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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) {
// 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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
nativePollOnce(ptr, nextPollTimeoutMillis); 即用来阻塞当前消息,直到消息的时间到达。
详细分析不再描述,大家可以网上搜索下该方法。 分享一篇文章,方便大家直接学习。