Android 中 MessageQueue 中的内存泄漏?

问题描述 投票:0回答:3

LeakCanary 检测到我的 MainActivity.java 内存泄漏。这是我的 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$0 instance of com.application.app.MainActivity with mDestroyed =
│    true
│    ↓ MainActivity$$ExternalSyntheticLambda2.f$0
│                                             ~~~
╰→ 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

我不明白这里有什么问题。当

postdelayed()
被调用时,我关闭了所有
ondestroy()
方法。这是代码:

@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以及它的所有泄漏有多接近。提前致谢!

java android memory-leaks message-queue leakcanary
3个回答
8
投票

什么是消息队列?

有 3 个关键的 Android 类捆绑在一起: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。很难弄清楚是哪一个,但如果你反编译字节码(例如类文件或 dex),你应该能够分辨出哪个 lambda 具有该名称。

修复

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

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

内存泄漏不能通过用弱引用替换强引用来修复。在尝试快速解决内存问题时,这是一种常见的解决方案,但它永远行不通。导致引用保留时间超过必要时间的错误仍然存在。最重要的是,它会产生更多错误,因为现在某些对象将比应有的时间更快地被垃圾收集。它还使代码更难维护。


0
投票

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

还要检查您传递活动上下文和 MainActivity.this 实例的位置。

最后检查哪些回调/lambda 与此活动相关联,可能有一种情况是您班级的一个成员正在与其他班级共享,例如回收者视图适配器,这可能会导致泄漏。

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

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


0
投票

如果您需要快速修复,解决这个问题的最简单直接的方法是使用这个 magic library WeakHandler

如果您需要阅读更多内容在这里查看

这是示例代码

在构建gradle中,添加这个库

dependencies {
    implementation 'com.badoo.mobile:android-weak-handler:1.0' 
}

然后

import com.badoo.mobile.util.WeakHandler;

public class ExampleActivity extends Activity {

private WeakHandler handler; // Just use WeakHandler instead of Handler

protected void onCreate(Bundle savedInstanceState) {
    handler = new WeakHandler();
    ...
}

private void onClick(View view) {
    handler.postDelayed(new Runnable() {
        view.setVisibility(View.INVISIBLE);
    }, 5000);
 }
}
© www.soinside.com 2019 - 2024. All rights reserved.