【问题标题】:Memory Leak due to Snackbar由于 Snackbar 导致的内存泄漏
【发布时间】:2019-08-03 12:47:45
【问题描述】:

我刚刚将 CanaryLeak 添加到我的项目中,以查看我的应用程序中是否存在任何内存泄漏并注意到,由于 Snackbar,我的片段中确实存在泄漏。

我正在 onCreateView 中创建一个 Snackbar,并将其设置为 onDestroyView 中的 null。但是,每次旋转屏幕时都会发生内存泄漏。


    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup 
    container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_backend, container, 
        false);

        Activity parentActivity = getActivity();
        if (parentActivity != null) {
            mConnectSnackbar = 
            Snackbar.make(parentActivity.findViewById(R.id.nav_host_fragment), 
            "Connect", Snackbar.LENGTH_INDEFINITE);

            mConnectSnackbar.setAction(getString(R.string.connect), v -> 
            startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)));
        }

        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mConnectSnackbar.setAction("Connect", null);
        mConnectSnackbar.dismiss();
        mConnectSnackbar = null;
    }

当我清除对操作和 Snackbar 本身的引用时,应该没有任何原因导致内存泄漏。但是我无法弄清楚,原因可能是什么,Canary Leak 的堆转储也无济于事。 由于对 nav_host_fragment 的引用,我怀疑它可能是我的,但我不知道它是否属实以及如何解决它。

非常感谢您的帮助。

编辑 1:

添加了泄漏跟踪并删除了 hprof 文件。

┬
├─ android.view.accessibility.AccessibilityManager
│    Leaking: NO (a class is never leaking)
│    GC Root: System class
│    ↓ static AccessibilityManager.sInstance
│                                  ~~~~~~~~~
├─ android.view.accessibility.AccessibilityManager
│    Leaking: UNKNOWN
│    ↓ AccessibilityManager.mTouchExplorationStateChangeListeners
│                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ android.util.ArrayMap
│    Leaking: UNKNOWN
│    ↓ ArrayMap.mArray
│               ~~~~~~
├─ java.lang.Object[]
│    Leaking: UNKNOWN
│    ↓ array Object[].[4]
│                     ~~~
├─ androidx.core.view.accessibility.AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper
│    Leaking: UNKNOWN
│    ↓ AccessibilityManagerCompat$TouchExplorationStateChangeListenerWrapper.mListener
│                                                                            ~~~~~~~~~
├─ com.google.android.material.snackbar.BaseTransientBottomBar$SnackbarBaseLayout$1
│    Leaking: UNKNOWN
│    ↓ BaseTransientBottomBar$SnackbarBaseLayout$1.this$0
│                                                  ~~~~~~
╰→ com.google.android.material.snackbar.Snackbar$SnackbarLayout
​     Leaking: YES (View.mContext references a destroyed activity)
​     mContext instance of android.view.ContextThemeWrapper, wrapping activity com.twaice.twaice.MainActivity with mDestroyed = true
​     View#mParent is null
​     View#mAttachInfo is null (view detached)
​     View.mWindowAttachCount = 0

【问题讨论】:

    标签: android memory-leaks leakcanary


    【解决方案1】:

    这是 material-components-android 库中的内存泄漏。我刚刚提交了一个问题:https://github.com/material-components/material-components-android/issues/497

    只有在创建了小吃店但从未按照问题中描述的方式显示时才会发生这种泄漏:

    在 Material Library 1.0.0 中,当创建 BaseTransientBottomBar .SnackbarBaseLayout 实例时,它会注册一个 TouchExplorationStateChangeListener,然后它会取消注册 onDetachedFromWindow()。如果 SnackbarBaseLayout 已创建但从未附加(发生这种情况),则它永远不会分离。当底层上下文(一个活动)被破坏时,TouchExplorationStateChangeListener 由 AccessibilityManager 保存在内存中,保持其外部类 SnackbarBaseLayout 本身保持其上下文,一个被破坏的活动。实际上 SnackbarBaseLayout 正在泄漏被破坏的活动和整个视图层次结构。

    好消息是 1.1.0 版本中不再存在此代码,因此泄漏已消失,但不幸的是 1.1.0 仍处于 alpha 版本中。

    注意:在以后的帖子中,考虑提供 LeakCanary 输出的文本泄漏跟踪,这对于解决内存泄漏很有用。

    【讨论】:

    • 感谢您的见解和在 GitHub 上创建问题。我会考虑切换到 1.1.0,尽管我通常不是 alpha 版本的忠实粉丝。感谢您的注意;我会牢记在心。
    • 这是一个 androidx 库...如何在未移动到 androidx 的项目中解决此问题??
    【解决方案2】:

    我想我已经设法找到了解决方案,尽管我不明白其中的原因。如前所述,我已经在onCreateView 中创建了 Snackbar,但稍后一些其他逻辑决定是否显示 Snackbar。

    现在解决方案:内存泄漏仅在创建 Snackbar 但从未显示时存在。因此,我没有在onCreateView 中创建 Snackbar,而是将以下代码放在应该显示 Snackbar 时调用的函数中:

    if (mConnectSnackbar == null) {
            Activity parentActivity = getActivity();
            if (parentActivity != null) {
                mConnectSnackbar = 
                Snackbar.make(parentActivity.findViewById(R.id.nav_host_fragment), 
                "Connect", Snackbar.LENGTH_INDEFINITE);
    
                mConnectSnackbar.setAction(getString(R.string.connect), v -> 
                startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)));
                mConnectSnackbar.show()
            }
    } else {
           mConnectSnackbar.show();
    }
    
    

    自从我添加此更改后,没有发生内存泄漏。但是,我真的不明白为什么在不调用 show() 时会发生这种情况。如果有人能提供一些解释为什么会发生这种情况,我将不胜感激。

    【讨论】:

      猜你喜欢
      • 2016-02-15
      • 1970-01-01
      • 1970-01-01
      • 2016-07-10
      • 1970-01-01
      • 2011-03-03
      • 1970-01-01
      • 2013-07-23
      • 2015-08-31
      相关资源
      最近更新 更多