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什么时候被洗脱的。我发现的唯一代码是当新的片段被添加时。难道不应该以某种方式管理它吗?
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;
}发布于 2019-11-18 00:08:51
这个问题现在在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();
}
}
}注意:当片段相关的代码改变时,这个解决方案自然是脆弱的,然而,这是可测试的。此外,如果使用proguard,则需要确保避免对该字段进行混淆,因此可以像这样添加proguard指令:
-keep class androidx.fragment.app.FragmentManagerImpl { *; }
或者更好的是,试着弄清楚如何使用-keepclassmembers来保持mCreatedMenus。
发布于 2018-08-10 02:09:31
这是Android SDK中的一个漏洞。看看this thread吧。
如果您在gradle应用程序文件(build.gradle)中更新为Target- support -26.0.0-beta1 support lib,则此问题已修复。
如果由于某些原因您无法更新到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();
}
}
}
}发布于 2019-01-09 04:30:40
我对androidX FragmentManager有这个问题。
已通过在片段事务后调用此方法修复泄漏。此方法位于活动内部。
private fun clearFragmentManagersAddedMenus() {
Handler(mainLooper).post {
val field = FragmentActivity::class.java.getDeclaredField("mFragments")
field.isAccessible = true
(field.get(this) as FragmentController).dispatchCreateOptionsMenu(null, null)
}
}https://stackoverflow.com/questions/44674696
复制相似问题