記一次關於Fragment的內存泄漏

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

相關文章
相關標籤/搜索