以前使用單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