LeakCanary 在我的代码中发现了泄漏
* classifieds.yalla.features.ad.page.seller.SellerAdPageFragment has leaked:
* GC ROOT android.view.inputmethod.InputMethodManager$1.this$0 (anonymous subclass of com.android.internal.view.IInputMethodClient$Stub)
* references android.view.inputmethod.InputMethodManager.mNextServedView
* references android.support.v4.widget.DrawerLayout.mContext
* references classifieds.yalla.features.host.HostActivity.fragNavController
* references com.ncapdevi.fragnav.FragNavController.mFragmentManager
* references android.support.v4.app.FragmentManagerImpl.mCreatedMenus
* references java.util.ArrayList.elementData
* references array java.lang.Object[].[0]
* leaks classifieds.yalla.features.ad.page.seller.SellerAdPageFragment instance
但是当我查看 FragmentManagerImpl
当 FragmentManagerImpl.mCreatedMenus
被清除时,我没有找到。我发现的唯一代码是添加新 fragment 时的代码。难道不应该以某种方式对其进行管理吗?
public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
boolean show = false;
ArrayList<Fragment> newMenus = null;
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null) {
if (f.performCreateOptionsMenu(menu, inflater)) {
show = true;
if (newMenus == null) {
newMenus = new ArrayList<Fragment>();
}
newMenus.add(f);
}
}
}
}
if (mCreatedMenus != null) {
for (int i=0; i<mCreatedMenus.size(); i++) {
Fragment f = mCreatedMenus.get(i);
if (newMenus == null || !newMenus.contains(f)) {
f.onDestroyOptionsMenu();
}
}
}
mCreatedMenus = newMenus;
return show;
}
最佳答案
这个问题现在在 androidx.fragment v1.10(2019 年 11 月)上仍然很重要,所以这里有一些见解。
假设调用 setHasOptionsMenu() 时使用 fragment f 的真值。 当 f 被分离时,与 f 关联的 fragment 管理器 (FM) 将不会处理菜单上隐含的更改。 请记住,菜单可能会受到同一 FM 托管的多个 fragment 的影响。事实上,其中一个 f 脱离了 FM,这应该会导致 FM 重建菜单,但话又说回来,这并没有得到处理。 此外,当 f 被分离时,在支持菜单的上下文中与 f 关联的资源也不会被清除。 特别是,不会在 f 上调用 onDestroyOptionsMenu() 并且 FM 在其提供菜单选项的 fragment 列表中保留对 f 的引用。
在 Google 修复 fragment 管理器以从该列表中删除泄漏的 fragment 之前,一些选项是:
- 接受泄露的 fragment 。当 Activity 被销毁时, fragment 管理器将被清除,然后 fragment 将被 GC 认领。
- 不要使用 setHasOptionsMenu() 机制。例如,您可以提出自己的菜单实现。
- 使用反射从该列表中删除 fragment 。当然反射并不理想,但泄漏 fragment 更糟糕。 在其他泄漏的 fragment 中,添加如下内容
@Override
public void onDetach() {
super.onDetach();
// get the fragment manager associated with this fragment
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
try {
Field field =
fragmentManager.getClass().getDeclaredField("mCreatedMenus");
field.setAccessible(true);
if (field.get(fragmentManager) instanceof ArrayList) {
ArrayList fragments = (ArrayList)field.get(fragmentManager);
if (fragments != null && fragments.remove(this)) {
Log.d(TAG, "Yay, no leak today");
}
}
} catch (NoSuchFieldException | SecurityException |
IllegalAccessException e) {
e.printStackTrace();
}
}
}
注意:当然,当与 fragment 相关的代码更改时,此解决方案很脆弱,但是,这是可测试的。此外,如果使用 proguard,您需要确保避免对该字段进行混淆,因此您可以像这样添加 proguard 指令:
-keep class androidx.fragment.app.FragmentManagerImpl { *; }
或者更好的是,尝试找出如何使用 -keepclassmembers 来保留 mCreatedMenus。
关于android - FragmentManager(v4) 不从 mCreatedMenus 中删除 fragment ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44674696/