Android工作中的问题记录

ViewBinding内存泄露

2021-12-15  本文已影响0人  杨0612
在Fragment中使用ViewBinding出现内存泄露。

场景:在MainActivity中分别加载两个Fragment处理业务。
通过以下方式加载AFragment:

       supportFragmentManager.commit {
            add(R.id.contentLayout, FirstFragment())
            addToBackStack(null)
        }

在AFragment中触发耗时任务加载数据;

class AFragment : Fragment() {

    private var _binding: FragmentFirstBinding? = null
    private val binding get() = _binding!!

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            val date = withContext(Dispatchers.IO) {
                delay(10000 )//模拟耗时加载数据
                "success"
            }
            binding.buttonFirst.text = date
        }
    }

在耗时任务还没有结束时,用户触发加载BFragment;
加载方式如下:

        supportFragmentManager.commit {
            replace(R.id.contentLayout, SecondFragment())
            addToBackStack(null)
        }

大家可能有注意到了,两个Fragment都会加到回退栈,这是为了在触发回退键时可以回到上一个组件,当然,实现方式有很多,我们不做过多的讨论。

这时候通过LeakCanary可以发现有内存泄露 20211215162636.jpg
1处是AFragment的ViewBinding实例,2处是AFragment布局id,3处是布局的类型。在3处,提示有内存泄露,也就是view出现了泄露。在4处,LeakCanary给了建议:对view的引用应该要被clear避免出现泄露。
引用链:Fragment-ViewBinding-根布局
为什么会出现泄露呢?

从AFragment到BFragment,前者生命周期从onPause->onStop->onDestoryView,注意这里只走到onDestoryView,并没有onDetach以及onDestory(不要问我为什么,这是Framework决定的)。其实也很好理解,AFragment是加入了回退栈,也就是期望被恢复,为了不占用过多的内存,Framework这时候会把view销毁,注意不是销毁Fragment,但view被Fragment持有,所以就出现了内存泄露。
可以简单把ViewBinding理解为对所有view封装,方便外部调用。
以下是onDestoryView官方注释,注意到这句“The next time the fragment needs
* to be displayed, a new view will be created”,当Fragment恢复时,会创建新的view添加到Fragment,也就是重走onCreateView,那么我理解旧的view就应该可以被销毁。


    /**
     * Called when the view previously created by {@link #onCreateView} has
     * been detached from the fragment.  The next time the fragment needs
     * to be displayed, a new view will be created.  This is called
     * after {@link #onStop()} and before {@link #onDestroy()}.  It is called
     * <em>regardless</em> of whether {@link #onCreateView} returned a
     * non-null view.  Internally it is called after the view's state has
     * been saved but before it has been removed from its parent.
     */
    @MainThread
    @CallSuper
    public void onDestroyView() {
        mCalled = true;
    }
解决方案

将ViewBinding置空就欧了。其实这也是官方的建议,当你新建项目的时候,就能看到这样的案列。

   override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

#######总结
当出现Fragment没有被销毁,而view需要被销毁时,要注意把ViewBinding置空,以免出现内存泄露。

以上分析有不对的地方,请指出,互相学习,谢谢哦!

上一篇下一篇

猜你喜欢

热点阅读