记一次关于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

相关文章
相关标签/搜索