这段代码可以避免Android处理程序的内存泄漏吗?

时间:2018-09-12 02:53:33

标签: android memory-leaks callback handler

handler1是一个泄漏。

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

两个代码有什么区别?

公共类MainActivity扩展了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();
}

}

3 个答案:

答案 0 :(得分:1)

Android Studio Screenshot

我在模拟器(android 28)上尝试此代码并转储内存,探查器显示没有泄漏,我也尝试了Leakcanary并显示了相同的结果。这使我怀疑网上文章的准确性。然后我注意到了区别,如果我使用 Kotlin 编写此逻辑,内存不会泄漏,但是使用 Java 代码会泄漏。

后来我发现了一些有趣的东西。在Java中,无论是否使用外部类方法,匿名内部类都会通过构造函数保存外部对象引用。在Kotlin中,如果内部逻辑未使用外部类方法,则匿名内部类将不保留外部对象引用。

答案 1 :(得分:0)

为什么处理程序1泄漏警告?

出于泄漏警告的原因,this article解释得很好。

文章引用

  

在Java中,非静态内部和匿名类持有对其外部类的隐式引用。另一方面,静态内部类则没有。

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

解决方案

再次引用本文

  

要解决此问题,请将处理程序子类化到新文件中,或改为使用静态内部类。静态内部类不持有对其外部类的隐式引用,因此不会泄漏活动。如果您需要在Handler中调用外部活动的方法,请让Handler对该活动持有WeakReference,以免意外泄漏上下文。为了解决在实例化匿名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 This Handler class should be static or leaks might occur: IncomingHandler的回答 提供解决方案。

引用@Michael的答案

  

据我了解,这无法避免潜在的内存泄漏。消息对象包含对mIncomingHandler对象的引用,该对象包含对Handler.Callback对象的引用,该对象包含对Service对象的引用。只要Looper消息队列中有消息,该服务就不会是GC。但是,除非您在消息队列中有很长的延迟消息,否则这不是一个严重的问题。

在您的情况下,handler2将保留对Handler.Callback对象的引用。由于Handler.Callback是由匿名类创建的,因此它将保留对MainActiviy实例的引用太。因此MainActiviy实例也不能被垃圾收集。

答案 2 :(得分:0)

我在Kotlin代码中使用了Handler来捕获传入的蓝牙传入消息。该代码没有棉绒泄漏警告:

private val incomingMsgHandler: Handler = Handler { msg ->
    msg.obj?.let {
        if (it is ByteArray) {
            val msgStr = String(it)
            setIncomingMessage(msgStr)
        }
    }
    true
}

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

相关问题