Handler内存泄漏原因分析和解决

2,517 阅读4分钟

经常听到说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); 即用来阻塞当前消息,直到消息的时间到达。

详细分析不再描述,大家可以网上搜索下该方法。 分享一篇文章,方便大家直接学习。

juejin.cn/post/684490…