我刚刚将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,但我不知道它是否正确以及如何解决。
非常感谢您的帮助。
添加了泄漏跟踪并删除了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
答案 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()
时会发生这种情况。如果有人可以提供一些原因,我将不胜感激。