这段代码能避免Android Handler的内存泄漏吗?

4

handler1是一个泄漏。

我想将handler1代码转换为handler2代码。可以吗?

这两个代码有什么区别?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // leaks!
        Handler handler1 = new Handler()
        {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.e("LOG", "Hello~1");
            }
        };

        Handler handler2 = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.e("LOG", "Hello~2");
                return false;
            }
        });

        handler1.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60000);
        handler2.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60000);

        finish();
    }
}

请详细说明如何发现handler1导致内存泄漏。 - samabcde
抱歉,请更改为完整代码。 - user2324746
由于您认为handler1导致了内存泄漏,请添加“HOW”以查找内存泄漏(例如,在调用多次onCreate方法后是否抛出OutOfMemoryError?)。 - samabcde
Android Studio 已经发出警告。 - user2324746
请编辑您的问题,为handler1添加警告信息。 - samabcde
3个回答

8

为什么handler1会出现泄漏警告?

关于泄漏警告的原因,这篇文章解释得非常清楚。

引用自该文章:

在Java中,非静态内部类和匿名类都会隐式地持有对外部类的引用。而静态内部类则不会。

因此,当您通过匿名类创建handler1时,它将持有对MainActivity实例的引用,导致MainActiviy无法被垃圾回收。

解决方案

再次引用自该文章:

为了解决这个问题,可以在新文件中子类化Handler或使用静态内部类。静态内部类不会持有对外部类的隐式引用,因此活动不会泄漏。如果需要在Handler内部调用外部活动的方法,请让Handler持有对活动的弱引用,以免意外泄漏上下文。要修复我们实例化匿名Runnable类时发生的内存泄漏,可以将变量作为类的静态字段(因为匿名类的静态实例不会持有对外部类的隐式引用):

根据该文章,您需要按照以下方式更新代码:

public class MainActivity extends AppCompatActivity {

    private static class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
             Log.e("LOG", "Hello~1");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Handler handler1 = new MyHandler();

        handler1.postDelayed(new Runnable() {
            @Override
            public void run() { }
        }, 60000);

        finish();
    }
}

handler2能解决这个问题吗?

@Michael在这篇答案中提供了解决方案

引用@Michael的答案:

据我理解,这并不能避免潜在的内存泄漏。消息对象持有对mIncomingHandler对象的引用,该对象持有对Handler.Callback对象的引用,而Handler.Callback对象又持有对Service对象的引用。只要Looper消息队列中存在消息,Service将无法被垃圾收集。但是,除非您在消息队列中有长延迟的消息,否则这不会成为严重问题。

在您的情况下,handler2将持有对Handler.Callback对象的引用。由于Handler.Callback是通过匿名类创建的,因此它也将引用MainActivity实例。因此,MainActivity实例也无法被垃圾回收。


1

Android Studio Screenshot

我在模拟器(android 28)上尝试了这段代码并且转储了内存,分析器没有显示任何泄漏,我还尝试了Leakcanary并且显示了相同的结果。这让我对网络文章的准确性产生怀疑。然后我注意到了区别,如果我使用Kotlin写这个逻辑,内存不会泄漏,但是如果使用Java代码则会泄漏。

后来我发现了一些有趣的事情。在java中,无论是否使用外部类方法,匿名内部类都将通过构造函数持有外部对象引用。而在kotlin中,如果内部逻辑不使用外部类方法,则匿名内部类不持有外部对象引用。


0
我在 Kotlin 代码中使用了 Handler,以捕获传入的蓝牙消息。此代码没有任何 lint 泄漏警告:
private val incomingMsgHandler: Handler = Handler { msg ->
    msg.obj?.let {
        if (it is ByteArray) {
            val msgStr = String(it)
            setIncomingMessage(msgStr)
        }
    }
    true
}

简而言之,这使用带有lambda回调的Handler构造函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接