Snackbar导致的内存泄漏

时间:2019-08-03 12:47:45

标签: android memory-leaks leakcanary

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

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


    @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

2 个答案:

答案 0 :(得分:3)

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

仅当创建了快餐栏但从未按问题中的说明显示时,才会发生此泄漏:

  

在材料库1.0.0中,创建BaseTransientBottomBar .SnackbarBaseLayout实例时,它将注册一个TouchExplorationStateChangeListener,然后取消注册onDetachedFromWindow()。如果创建了SnackbarBaseLayout但从未附加它(发生这种情况),则它永远不会分离。当基础上下文(活动)被销毁时,AccessExplorerManager将TouchExplorationStateChangeListener保留在内存中,并保留其外部类SnackbarBaseLayout,而外部类SnackbarBaseLayout仍保留其上下文(销毁的活动)。实际上,SnackbarBaseLayout泄漏了被破坏的活动和整个视图层次。

好消息是此代码在1.1.0版本中不再存在,因此泄漏消失了,尽管遗憾的是1.1.0仍在alpha版本中。

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

答案 1 :(得分:0)

即使我不知道这样做的原因,我也设法找到了解决方案。如前所述,我已经在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()时会发生这种情况。如果有人可以提供一些原因,我将不胜感激。