在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都会加到回退栈,这是为了在触发回退键时可以回到上一个组件,当然,实现方式有很多,我们不做过多的讨论。
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置空,以免出现内存泄露。
以上分析有不对的地方,请指出,互相学习,谢谢哦!
网友评论