android 中的 MessageQueue 内存泄漏?

Memory Leak in MessageQueue in android?

我的 MainActivity.java 中存在内存泄漏,这是由 LeakCanary 检测到的。这是我的 Leak Trace。

┬───
│ GC Root: Input or output parameters in native code
│
├─ android.os.MessageQueue instance
│    Leaking: NO (MessageQueue#mQuitting is false)
│    HandlerThread: "main"
│    ↓ MessageQueue.mMessages
│                   ~~~~~~~~~
├─ android.os.Message instance
│    Leaking: UNKNOWN
│    Retaining 14.2 kB in 348 objects
│    Message.what = 0
│    Message.when = 37524601 (681 ms after heap dump)
│    Message.obj = null
│    Message.callback = instance @319985112 of com.application.app.
│    MainActivity$$ExternalSyntheticLambda2
│    ↓ Message.callback
│              ~~~~~~~~
├─ com.application.app.MainActivity$$ExternalSyntheticLambda2 instance
│    Leaking: UNKNOWN
│    Retaining 12 B in 1 objects
│    f[=11=] instance of com.application.app.MainActivity with mDestroyed =
│    true
│    ↓ MainActivity$$ExternalSyntheticLambda2.f[=11=]
│                                             ~~~
╰→ com.application.app.MainActivity instance
      Leaking: YES (ObjectWatcher was watching this because com.defenderstudio.
      geeksjob.MainActivity received Activity#onDestroy() callback and
      Activity#mDestroyed is true)
      Retaining 11.2 MB in 5622 objects
      key = e98df529-afa0-4e0c-b0f0-51a5d3eaf67c
      watchDurationMillis = 5249
      retainedDurationMillis = 248
      mApplication instance of android.app.Application
      mBase instance of androidx.appcompat.view.ContextThemeWrapper

METADATA

Build.VERSION.SDK_INT: 30
Build.MANUFACTURER: samsung
LeakCanary version: 2.7
App process name: com.application.app
Count of retained yet cleared: 6 KeyedWeakReference instances
Stats: LruCache[maxSize=3000,hits=6544,misses=134885,hitRate=4%]
RandomAccess[bytes=5904498,reads=134885,travel=75990168059,range=41137566,size=5
3483782]
Heap dump reason: 7 retained objects, app is visible
Analysis duration: 31639 ms

我不明白这里有什么问题。当调用 ondestroy() 时,我关闭了所有 postdelayed() 方法。这是代码:

@Override
protected void onDestroy() {
    dialog = new Dialog(MainActivity.this, R.style.dialog);
    if (dialog.isShowing()) {
        dialog.dismiss();
    }
    if (handler != null && statusChecker != null) {
        handler.removeCallbacks(statusChecker);
    }
    if (databaseReference != null && userSignInInfoReference != null && eventListener != null) {
        databaseReference.removeEventListener(eventListener);
        userSignInInfoReference.removeEventListener(eventListener);
    }
    progressDialog = new ProgressDialog(MainActivity.this, R.style.ProgressDialogStyle);
    if (progressDialog.isShowing()) {
        progressDialog.dismiss();
    }
    headerView = null;
    super.onDestroy();
}

请帮帮我!

注意:另外请告诉我什么是 MessageQueue 以及它的所有泄漏是如何关闭的。提前致谢!

检查你的 Activity 的所有数据成员,有一些数据成员已经超过了你的 activity 的生命周期。

还要检查您传递 activity 上下文和 MainActivity.this 实例的地方。

最后检查哪些回调/lambda 与此相关联 activity 可能存在这样一种情况,即您的 class 成员之一正在与其他 class 共享,例如可能导致泄漏的回收器视图适配器。

作为处理内存泄漏问题时的经验法则,我将大部分(如果不是全部)数据通过 WeakReference 进行封装,这样您既可以避免 NPE,又可以从解耦中获益 class。

Edit - 正如在下面的评论中所分享的那样,使用弱引用是一种不好的做法,并且有更好的方法来解决内存泄漏。请检查@Pierre 或 link 对下面发表的评论的回答。

什么是消息队列?

有 3 个键 Android class 捆绑在一起:Handler、Looper 和 MessageQueue。创建 Looper 实例时,它会创建自己的 MessageQueue 实例。然后你可以创建一个 Handler 并给它 Looper 实例。当您调用 Handler.post()(或 postDelayed)时,在幕后您实际上是在调用 Handler.sendMessage,它会将一个 Message 实例排入与该 Handler 关联的 Looper 关联的 Message 队列中。

那些排队的消息会怎样?在代码的其他地方(即专用的 HandlerThread),调用 Looper.loop() 的东西会永远循环,一次从关联的消息队列中删除一个条目并 运行 该消息。如果队列为空,则 Looper 等待下一条消息(这是通过本机代码完成的)。更多上下文:https://developer.squareup.com/blog/a-journey-on-the-android-main-thread-psvm/

我们可以从 LeakTrace 中读到什么?

我们看到顶部的 MessageQueue 用于以下 HandlerThread:“main”。这是主线程。因此,如果您在主线程上执行 postDelayed,则会将一条消息排入消息队列。

消息存储为链表:MessageQueue 保留第一条消息(通过其 mMessages 字段),然后每条消息保留下一条。

我们可以看到队列的头部是一个Message,我们可以看到它的内容。

Message.when = 37524601 (681 ms after heap dump)

这告诉我们消息被延迟排队并将在 681 毫秒内执行(在进行堆转储之后)

Message.callback = instance @319985112 of com.application.app.MainActivity$$ExternalSyntheticLambda2

这告诉我们排队的回调是 MainActivity 中定义的内部 lambda。很难弄清楚是哪一个,但如果你反编译字节码(例如 class 文件或 dex),你应该能够分辨出哪个 lambda 具有该名称。

修复

很可能,您有一段代码会在主线程上将自己重新安排为 postDelayed,即使在 activity 被销毁之后也是如此。该回调需要在 onDestroy()

中取消

编辑:注意在其他答案中使用 Wea​​kReference 的建议:这不是好的一般建议。来自文档:https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/#4-fix-the-leak

Memory leaks cannot be fixed by replacing strong references with weak references. It’s a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.