内存泄漏的MessageQueue在android?

qybjjes1  于 2023-02-20  发布在  Android
关注(0)|答案(3)|浏览(151)

我的www.example.com中有一个内存泄漏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以及如何关闭它的所有泄漏。提前感谢!

mfuanj7w

mfuanj7w1#

什么是消息队列?

有3个关键的Android类捆绑在一起:Handler、Looper和MessageQueue。当创建Looper示例时,它会创建自己的MessageQueue示例。然后,您可以创建一个Handler并将Looper示例给予它。当您调用Handler.post()(或postDelayed)时,实际上是在后台调用Handler.sendMessage,它将消息示例入队到与该Handler关联的Looper关联的消息队列中。
那些排队的消息会发生什么?在代码的其他地方(例如专用的HandlerThread),有东西调用Looper.loop(),它会永远循环,一次从关联的Message队列中删除一个条目并运行该消息。如果队列为空,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)
这告诉我们消息排队时有延迟,将在681ms内执行(在进行堆转储之后)
Message.callback = instance @319985112 of com.application.app.MainActivity$$ExternalSyntheticLambda2
这告诉我们排队的回调是在MainActivity中定义的一个内部lambda,很难判断是哪一个,但是如果你反编译字节码(例如class文件或dex),你应该能够分辨出哪个lambda有这个名字。

修复

最有可能的情况是,您有一段代码在主线程上不断地将自己重新调度为postDelayed,即使在Activity被销毁之后也是如此。
编辑:注意另一个答案中使用WeakReference的建议:这可不是个好建议。医生说:https://square.github.io/leakcanary/fundamentals-fixing-a-memory-leak/#4-fix-the-leak
内存泄漏不能通过用弱引用替换强引用来修复。当试图快速解决内存问题时,这是一个常见的解决方案,但是它从来没有工作过。导致引用保留时间过长的bug仍然存在。除此之外,它还导致了更多的bug,因为一些对象现在将比它们应该的更早被垃圾收集。它还使代码更难维护。

w8f9ii69

w8f9ii692#

检查Activity的所有数据成员,确定是否存在一些超过Activity生命周期的数据成员。
还要检查您传递活动上下文和MainActivity.this示例的位置。
最后,检查哪些回调/ lambda与此活动相关联,可能会有这样的情况,即您的类的成员之一与其他一些类(如回收器视图适配器)共享,这可能会导致泄漏。
在处理内存泄漏问题时,作为一个经验法则,我使用WeakReference封装大多数(如果不是所有)数据传递,这样既可以避免NPE,又可以从解耦的类中获益。

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

bnlyeluc

bnlyeluc3#

如果您需要快速修复,我自己测试并成功解决此问题的最简单直接的方法是使用此magic库WeakHandler
如果你需要阅读更多的check here
这是一个示例代码
在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);
 }
}

相关问题