在安卓系统中,消息队列出现了内存泄漏问题?

3

我在MainActivity.java中发现了内存泄漏问题,这是由LeakCanary检测出来的。以下是我的泄漏跟踪信息:

┬───
│ 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

我不理解这里的问题所在。当调用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)以及如何关闭所有泄漏它的问题。提前感谢!

3个回答

8

什么是MessageQueue?

有三个关键的Android类与之相关:Handler、Looper和MessageQueue。当创建一个Looper实例时,它会创建自己的MessageQueue实例。然后你可以创建一个Handler并将其与Looper实例关联起来。当你调用Handler.post()(或postDelayed)时,在底层实际上是调用Handler.sendMessage,它将一个Message实例排入关联到该Handler的Looper相关的Message队列中。

那些排队的消息会发生什么呢?在代码的其他地方(即专门的HandlerThread中),某些东西调用Looper.loop(),它无限循环,从关联到该Handler的Message队列中逐一删除条目并运行该消息。如果队列为空,Looper会等待下一条消息(这是通过本机代码完成的)。更多的上下文信息:https://developer.squareup.com/blog/a-journey-on-the-android-main-thread-psvm/

我们从LeakTrace中能读到什么?

我们看到顶部的MessageQueue是为以下HandlerThread而设的:"main"。这是主线程。因此,如果在主线程上进行了postDelayed,则会将消息排入消息队列。

消息被存储为一个链接列表:MessageQueue通过其mMessages字段保留第一个消息,然后每个消息保留下一个。

我们可以看到队列的头是一条消息,我们可以看到它的内容。

Message.when = 37524601(堆转储后681毫秒)

这告诉我们该消息被延迟排队,并将在681ms后执行(在进行堆转储后)

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

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

问题解决

最有可能的情况是,在Activity被销毁后,某个代码片段仍然将自己作为一个postDelayed在主线程上重新安排,那么需要在onDestroy()中取消该回调函数。

编辑:关于在另一个答案中使用WeakReference的建议说明:这不是好的通用建议。从文档中可以看出:https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/#4-fix-the-leak

用弱引用来替换强引用无法解决内存泄漏问题。这是在试图快速解决内存问题时的常见解决方案,但它从未奏效。导致引用被保留时间过长的缺陷仍然存在。此外,它会创建更多的错误,因为有些对象现在将被垃圾回收机制更早地回收。这还会使代码难以维护。


0

检查您的Activity的所有数据成员,看看是否有某个数据成员超出了Activity的生命周期。

还要检查在哪些地方传递了activity context和MainActivity.this实例。

最后,请检查与此Activity相关的回调/lambda,可能存在一个类成员被共享给其他类(如RecyclerView Adapter),这可能导致泄漏。

作为一个经验法则,在处理内存泄漏问题时,我会尽可能地使用WeakReference来封装所有数据传递,这样既可以避免NPE,又可以获得解耦类的好处。

编辑 - 如下面的评论中所述,使用弱引用是一种不好的做法,有更好的方法来解决内存泄漏问题。请检查@Pierre的答案或下面发布的评论链接。


1
使用WeakReferences来避免泄漏而不是正确处理生命周期很可能会引入意外的错误。更多信息请参见:https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/#4-fix-the-leak - Pierre-Yves Ricau
1
@Pierre-YvesRicau 感谢您提供的信息,对于我的错误我深表歉意。 - Taranmeet Singh

0

如果您需要快速解决问题,最简单直接的方法是使用这个我亲自测试过并且成功的神奇库WeakHandler

如果您需要阅读更多 请点击此处

以下是示例代码

在Build 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);
 }
}

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