Handler内存泄漏原因解析

738 阅读3分钟

首先,要分析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机制是怎样解决这个问题的,其实很简单,在Looperloop()方法里就有:

    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:在ActivityonDestroy()方法里调用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的所有引用,从而解决了内存泄漏的问题