android - FragmentManager(v4) 不从 mCreatedMenus 中删除 fragment

标签 android android-fragments android-support-library leakcanary

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/

相关文章:

java - 如何使用 HdmiControlService

Android View 处理滚动手势但忽略触摸

android - 如何在一个在Android中也有侧面抽屉导航的 Activity 中制作标签?

android - 找不到 com.android.support :support-v4:22. 0.0。 SDK更新后

android - 使用 RowsFragment 时,ObjectAdapter.setPresenterSelector 有什么作用吗?

android - 无法解析 : com. android.support :customtabs:[26. 0.0,26.1.0]

android - flutter : screen not scrolling up when keyboard appears in android

java - 在 Android Studio Java 中读取数据库标题并显示在 ListView 中

android - 更改 fragment 的外观

android - 导航组件无法弹出以退出应用程序