Android-ViewBinding的内存泄露

场景

在MainActivity中分别加载两个Fragment处理业务。 首先触发加载SecondFragment:

//MainActivity触发
supportFragmentManager.commit {
      add(R.id.contentLayout, FirstFragment())
      addToBackStack(null)//点击返回键可以回到FirstFragment
}
        
//FirstFragment布局中有一个自定义MyButton且有bitmap属性
class MyButton @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
) : Button(
    context, attrs, defStyle
) {
    private val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.a)
}
复制代码

然后触发加载SecondFragment;

//MainActivity触发
supportFragmentManager.commit {
      replace(R.id.contentLayout, SecondFragment())
      addToBackStack(null)
}
复制代码
Android Profile可以发现有内存泄露

1.png

- MyButton中的bitmap无法释放。

为什么认为bitmap无法释放就是内存泄漏呢?

内存泄漏:简单点说,就是该释放的内存无法得到释放,而且内存不能被使用。 从Fragment的生命周期说起,从FirstFragment切换到SecondFragment,前者生命周期从onPause->onStop->onDestoryView,注意这里只走到onDestoryView,并没有onDetach以及onDestory。其实也很好理解,FirstFragment是加入了回退栈,后续是要被恢复,所以保留了Fragment对象,但为了不占用过多的内存,View会被销毁释放资源。 当FirstFragment从回退栈回到前台,会再次触发onCreateView重建View。既然View会重建,那么之前的View就是不需要的,留着也没用,就应该销毁掉。

该释放的View、Bitmap没有被释放,所以就出现了泄漏。

例子比较简单,只是为了说明问题,如果FirstFragment View持有大量占内存的对象,而且SecondFragment的加载需要耗费比较多的内存且存在跳转的其他页面的可能性,那么FirstFragment View的释放就显得很有必要。

补充引用链:FirstFragment-MyButton-Bitmap

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;
    }
复制代码
LeakCanary日志

建议在onDestroyView要释放掉View

LeakCanary: Watching instance of androidx.constraintlayout.widget.ConstraintLayout (com.yang.myapplication.MyFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks)) with key 0f101dfe-5e4e-4448-95cc-f5d08bbdf06e

复制代码
解决方案

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

   override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
复制代码
总结

当出现Fragment没有被销毁(onDestory没有回调),而view需要被销毁时(onDestoryView),要注意把ViewBinding置空,以免出现内存泄露。

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

猜你喜欢

转载自juejin.im/post/7088212218205962276