ar-*_*r-g 6 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
Run Code Online (Sandbox Code Playgroud)
但当我看着 FragmentManagerImpl
FragmentManagerImpl.mCreatedMenus得到清理时我没有找到.我找到的唯一代码是添加新片段的时候.不应该以某种方式管理它吗?
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;
}
Run Code Online (Sandbox Code Playgroud)
时至今日,这个问题在androidx.fragment v1.10(截至2019年11月)上仍然存在,因此这里有一些见解。
假设使用片段f的真值调用setHasOptionsMenu()。分离f后,与f关联的片段管理器(FM)将无法处理菜单上隐含的更改。请记住,菜单可能会受到同一FM托管的多个片段的影响。其中之一f脱离的事实应该导致FM重建菜单,但是同样,这没有得到处理。此外,当分离f时,在支持菜单的上下文中与f关联的资源也不会被清除。特别是,不会在f上调用onDestroyOptionsMenu(),并且FM在其提供菜单选项的片段列表中保留对f的引用。
在Google修复片段管理器以从该列表中删除泄漏的片段之前,一些选项是:
@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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
注意:当然,当与片段相关的代码更改时,此解决方案很脆弱,但是,这是可测试的。另外,如果使用了proguard,则需要确保避免对该字段进行混淆,因此可以添加proguard指令,如下所示:
-keep class androidx.fragment.app.FragmentManagerImpl { *; }
甚至更好的方法是尝试弄清楚如何使用-keepclassmembers保留mCreatedMenus。
这是 Android SDK 中的漏洞。看看这个线程。
如果您更新到 gradle 应用程序文件 ( ) 中的 Target-Support-26.0.0-beta1 支持库,该问题即可解决build.gradle。
如果由于某些原因您无法更新到supportLibVersion>=26-beta1,那么有一个解决方法:
public class FragmentUtils {
/**
* Hack to force update the LoaderManager's host to avoid a memory leak in retained/detached fragments.
* Call this in {@link Fragment#onAttach(Activity)}
*/
public static void updateLoaderManagerHostController(Fragment fragment) {
if (fragment.mHost != null) {
fragment.mHost.getLoaderManager(fragment.mWho, fragment.mLoadersStarted, false);
}
}
/**
* This hack is to prevent the root loader manager to leak previous instances of activities
* accross rotations. It should be called on activities using loaders directly (not via a fragments).
* If the activity has fragment, you also have to also {@link #updateLoaderManagerHostController(Fragment)} above
* for each fragment.
* Call this in {@link FragmentActivity#onCreate}
*
* @param activity an actvity that uses a loader and leaks on rotation.
*/
public static void updateLoaderManagerHostController(FragmentActivity activity) {
if (activity.mFragments != null) {
try {
final Field mHostField = activity.mFragments.getClass().getDeclaredField("mHost");
mHostField.setAccessible(true);
FragmentHostCallback mHost = (FragmentHostCallback) mHostField.get(activity.mFragments);
mHost.getLoaderManager("(root)", false, true /* the 2 last params are not taken into account*/);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1095 次 |
| 最近记录: |