ViewBinding内存泄露

作者: 杨0612 | 来源:发表于2021-12-15 17:06 被阅读0次
    在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置空,以免出现内存泄露。

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

    相关文章

      网友评论

        本文标题:ViewBinding内存泄露

        本文链接:https://www.haomeiwen.com/subject/uohyfrtx.html