【问题标题】:Can this code avoid the Android handler memory leak?这段代码可以避免 Android 处理程序内存泄漏吗?
【发布时间】:2018-09-12 02:53:32
【问题描述】:

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 导致内存泄漏的详细信息
  • 对不起。更改为完整代码
  • 你认为handler1造成了内存泄漏,请加上“HOW”你发现内存泄漏的原因(例如OutOfMemoryError多次调用onCreate方法后抛出?)。跨度>
  • Android Studio 已经发出警告。
  • 请编辑您的问题以将警告消息放入handler1

标签: android memory-leaks callback handler


【解决方案1】:

为什么要为 handler1 发出泄漏警告?

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

引用文章

在 Java 中,非静态内部类和匿名类包含对其外部类的隐式引用。而静态内部类则不然。

所以当您通过匿名类创建handler1 时,它将持有对MainActivity 实例的引用,并且MainActiviy 不能被垃圾回收。

解决方案

再次引用文章

要解决此问题,在新文件中子类化处理程序或改用静态内部类。静态内部类不持有对其外部类的隐式引用,因此活动不会泄露。如果您需要从 Handler 中调用外部 Activity 的方法,请让 Handler 持有该 Activity 的 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能解决问题吗?

@MichaelThis Handler class should be static or leaks might occur: IncomingHandler的回答 提供解决方案。

引用@Michael 的回答

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

在您的情况下,handler2 将持有对 Handler.Callback 对象的引用。并且由于 Handler.Callback 是由匿名类创建的,因此它也将持有对 MainActiviy 实例的引用。所以MainActiviy 实例也不能被垃圾回收。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      我在 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 构造函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-02
        • 1970-01-01
        • 1970-01-01
        • 2011-05-30
        相关资源
        最近更新 更多