以前使用单Activity多Fragment架构完成过一个项目,在后期维护时发现,不少Fragment在关闭以后,内存没法被回收,出现了内存泄漏问题。leakcanary显示引用链信息以下:架构
LoginFragment关闭后仍然被FragmentManangerImpl中的mCreatedMenus所引用,致使LoginFragment没法被释放。app
我又使用Android Profiler工具查看了内存中的实例,步骤以下:
一、运行app,打开Android Profiler工具,而后点击MEMORY,会显示以下视图:工具
二、点击左上角的“Force garbage collection”按钮执行一次垃圾回收,再点击它旁边的“Dump Java heap”按钮即可将堆中的实例所有分析出来:this
如上图我把fragment过滤出来,LoginFragment确实还在内存中,可是FragmentManangerImpl中mAdded中已经不存在LoginFragment,再看看mCreatedMenus:spa
里面包含了LoginFragment。mCreatedMenus是个什么东西呢,为何会引用着已关闭的页面?它是FragmentManangerImpl中的属性,只能去FragmentManangerImpl中找找答案了。.net
在FragmentManangerImpl找到了以下一段代码:orm
public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (this.mCurState < 1) {
return false;
} else {
boolean show = false;
ArrayList<Fragment> newMenus = null;blog
int i;
Fragment f;
for(i = 0; i < this.mAdded.size(); ++i) {
f = (Fragment)this.mAdded.get(i);
if (f != null && f.performCreateOptionsMenu(menu, inflater)) {
show = true;
if (newMenus == null) {
newMenus = new ArrayList();
}生命周期
newMenus.add(f);
}
}内存
if (this.mCreatedMenus != null) {
for(i = 0; i < this.mCreatedMenus.size(); ++i) {
f = (Fragment)this.mCreatedMenus.get(i);
if (newMenus == null || !newMenus.contains(f)) {
f.onDestroyOptionsMenu();
}
}
}
this.mCreatedMenus = newMenus;
return show;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
mCreatedMenus会在dispatchCreateOptionsMenu方法中被初始化并赋值,而其余地方并无改变或重置mCreatedMenus中的元素,因此只有dispatchCreateOptionsMenu方法会影响mCreatedMenus。
dispatchCreateOptionsMenu方法是干吗的呢,其实从方法名就能够猜到它是用来分发建立ActionBar菜单的。的确,从代码中咱们就能够看出,它遍历了mAdded中全部的元素,而后调用元素的performCreateOptionsMenu方法。既然与建立菜单有关,那就去LoginFragment看看。
LoginFragment有建立Toolbar,并把Toolbar交给Activity来建立菜单,代码以下:
setHasOptionsMenu(true);
((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar);
1
2
而LoginFragment上一级Fragment中并无建立Toolbar菜单。
到这里咱们大体就知道缘由了,有两个地方:
一、LoginFragment中有建立菜单,而它的上一级Fragment没有建立菜单,这样致使从LoginFragment返回到上一级后,AppCompatActivity中的FragmentManangerImpl没有执行dispatchCreateOptionsMenu方法,全部mCreatedMenus中仍是保存了LoginFragment的实例。若是上一级Fragment有建立菜单不会有此问题;
二、((AppCompatActivity) getActivity()).setSupportActionBar(mToolbar)这句代码致使Activity中引用了Fragment的mToolbar,若是Fragment关闭后,没有去掉这个引用就会致使没法释放Fragment。
固然,若是一个Activity中只有一两个Fragment没什么关系,Activity销毁后也就随之销毁了。可是在单Activity项目中,整个App就一个Activity,被关闭的Fragment在整个App生命周期中一直存在,这样就有很大问题了。
找到缘由后,分析出解决方法就比较简单了。解决办法有两个:
一、Fragment中的菜单由本身来建立,不交给Activity,代码以下:
mToolbar.setNavigationIcon(R.drawable.ic_back);
mToolbar.setNavigationOnClickListener(v -> {
//TODO
});
mToolbar.inflateMenu(R.menu.toolbar_menu);
mToolbar.setOnMenuItemClickListener(menuItem -> {
//TODO
return true;
});
1
2
3
4
5
6
7
8
9
二、菜单仍是交给Activity管理,若是上一级Fragment有建立菜单那不用处理,若是没有须要在上一级Fragment清除掉引用,代码以下:
((AppCompatActivity) getActivity()).setSupportActionBar(null);
( getActivity()).onCreatePanelMenu(0,null);
1
2
onCreatePanelMenu方法会使dispatchCreateOptionsMenu被调用,从而给mCreatedMenus从新赋值。
固然最好是使用第一个方法,每一个Fragment中的菜单由本身来管理。 ———————————————— 版权声明:本文为CSDN博主「ganduwei」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处连接及本声明。 原文连接:https://blog.csdn.net/ganduwei/article/details/82844848