资讯专栏INFORMATION COLUMN

Handler 引起的内存泄露分析以及解决方法

fish / 3100人阅读

摘要:引起的内存泄露分析以及解决方法是系统提供的一种在子线程更新的机制,但是使用不当会导致。所以引用的对象是在主线程中创建的。主线程的因为在活动中,所以主线程一直存在。自然会引起内存泄露的。

Handler 引起的内存泄露分析以及解决方法

Handler是Android系统提供的一种在子线程更新UI的机制,但是使用不当会导致memory leak。严重的话可能导致OOM

Java语言的垃圾回收机制采用了可达性分析来判断一个对象是否还有存在的必要性,如无必要就回收该对象引用的内存区域,

Handler handler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

}

然后在其他地方来发送一个延迟消息

handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        
    }
}, 500);

我们一般使用Handler就是这样,但是这样会当Activity销毁后会导致memory leak.

原因就是activity销毁了,但是以为我们的Handler对象是一个内部类,因为内部类会持有外部类的一个引用。所以当activity销毁了,但是因为Handler还持有改Activity的引用,导致GC启动后,可达性分析发现该Activity对象还有其他引用。所以无法销毁改Activity,

但是handler仅仅是Activity的一个内存对象。及时他引用了Activity,他们之间也只是循环引用而已。而循环引用则不影响GC回收内存。

其实真正的原因是Handler调用postDelayed发送一个延迟消息时:

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

而sendMessageDelayed的实现是

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

再往下看

public boolean sendMessageAtTime(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(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到,在enqueueMessage的实现中。将msg.target = this;

就是讲改Handler对象赋值给了message的target对象。所以message对象就引用了Handler对象""

进而messageQueue对象就引用了Handler对象。此次逐渐明朗。就是messagequeue———message———

Handler——Activity。

所以我们可以在任一环节做文章即可避免Handler持有Activity对象导致的内存泄露问题。

我们可以在Activity销毁时将任务队列清空,或者 在Activity 销毁时将Handler对象销毁。

总之,就是在任一环节将该引用链条切换就好了,这样GC就可以销毁Activity对象了。

此时还是没有触及到问题的核心,就是为什么messageQueue为什么会持有message对象进而持有Handler对象,导致Activity销毁时还有其他引用。为什么Activity销毁时MessageQueue不销毁呢,这才是问题的核心,如果messageQueue销毁了啥问题也没有了。当然我们也可以在Activity销毁时手动销毁messageQueue对象。这样也可以避免内存泄露。

从这我们可以看出messagequeue的生命周期比Activity长了。所以才导致这些问题。

其实熟悉Handler机制的话就会明白背后的原因了

 final Looper mLooper;
 final MessageQueue mQueue;

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
    }

从构造方法我们可以看出,无参的构造方法最终调用了两参的构造方法。

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;

这几行代码才是重中之重。

首先调用Looper.myLooper()方法。如果looper为null,说明没有调用looper.prepare()方法。从抛出的运行时异常可以看出来。(ps:所以在子线程使用handler时,第一就是要调用Looper.prepare方法)

looper不为空话话,将looper复制个Handler的looper对象,然后将looper的queue对象赋值给handler的queue对象。

可以说Handler的looper字段和queue字段都是来着looper对象的。

可以看出我们在Handler里发送的消息最终发送到了handler的queue对象所执行的内存区域,而这片内存区域也是Looper对象的queue对象所指向的。所以说该queue对象里所有的message对象都收到Looper对象的queue对象的管理。

真正的大boss来了,都是Looper搞鬼。

因为我们是在主线程中初始化的Handler。所以Handler引用的looper对象是在主线程中创建的。

在代码ActivityThread.main()中:

public static void main(String[] args) {
        ....

        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
 Looper.prepareMainLooper();
 
  public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

在prepareMainLooper方法中首先调用了prepare方法,这就是为什么我们在主线程使用Handler时不需要自己手动调动looper的prepare方法的原因。

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));
}

在prepare方法中首先从sThreadLocal对象中取出looper对象。如果不为null.说明已经初始化过了,直接抛出异常。

没有初始化的话直接初始化然后放到sThreadLocal中。sThreadLocal是一个ThreadLocal类型。持有线程的私有数据。

此时,真相大白了。主线程的ThreadLocal——>looper——>messagequue——>message——>handler——>Acitivity

因为APP在活动中,所以主线程一直存在。looper一直存在,messageQueue一直存在。所以当我们发送了延迟消息时,而此时Activity销毁的话。自然会引起内存泄露的。

解决方法也很明了了。既然我们不能再looper层面做文章,就只能在handler和message层面做文章了。在Activity销毁时 将Handler手动置为null,或者将messagequeue 清空,或者将Handler设置为静态内部类。然后内部通过若引用持有Activity对象。总之就是要让Handler和message改放手时就放手

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77835.html

相关文章

  • 内存泄漏优化

    摘要:内存泄漏造成什么影响它是造成应用程序的主要原因之一。造成的内存泄漏早时期的时候处理耗时操作多数都是采用的方式,后来逐步被取代,直到现在采用的方式来处理异步。 目录介绍: 01.什么是内存泄漏 02.内存泄漏造成什么影响 03.内存泄漏检测的工具有哪些 04.关于Leakcanary使用介绍 05.错误使用单例造成的内存泄漏 06.Handler使用不当造成内存泄漏 07.Thread...

    fanux 评论0 收藏0
  • Android内存泄漏优化

    摘要:内存泄漏造成什么影响它是造成应用程序的主要原因之一。因此总结来看,线程产生内存泄露的主要原因有两点线程生命周期的不可控。造成的内存泄漏早时期的时候处理耗时操作多数都是采用的方式,后来逐步被取代,直到现在采用的方式来处理异步。 目录介绍: 01.什么是内存泄漏 02.内存泄漏造成什么影响 03.内存泄漏检测的工具有哪些 04.关于Leakcanary使用介绍 05.错误使用单例造成的内...

    sourcenode 评论0 收藏0
  • 彻底搞懂Java内存泄露

    摘要:所以如果赶在之前切断是可以避免内存泄露的。经过测试情况始终没有内存泄露。如果当退出时候,还有消息未处理或正在处理,由于引用又引用,此时将引发内存泄露。总结如果某些单例需要使用到对象,推荐使用的,不要使用的,否则容易导致内存泄露。 之前一直在简书写作,第一次发布到SF上来,也是第一次使用SF,后面会尽量同步到SF,更多文章请关注:简书 编程之乐转载请注明出处:谢谢! Java内存回收方式...

    seasonley 评论0 收藏0

发表评论

0条评论

fish

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<