Android DataBinding 正在泄漏内存

Android DataBinding is leaking Memory

我正在使用数据绑定,并且我已经为绑定声明了一个 lateinit var,当我要去显示泄漏的不同片段 Leaky canary 时。

片段

class HomeFragment : BottomNavViewHostBaseFragment() {

    private lateinit var viewModel: HomeViewModel
    private lateinit var binding: FragmentHomeBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        viewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        binding.viewModel = viewModel
        return binding.root
    }

   ...
}

这是来自 Leaky Carny 的信息

androidx.constraintlayout.widget.ConstraintLayout has leaked:
Toast$TN.mNextView
↳ LinearLayout.mContext
↳ MainActivity.navigationView
↳ NavigationView.listener
↳ BaseFragment$setNavigationDrawerItemSelectedListener.this[=12=] (anonymous implementation of com.google.android.material.navigation.NavigationView$OnNavigationItemSelectedListener) ↳ OrdersHostFragment.mFragmentManager
↳ FragmentManagerImpl.mActive
↳ HashMap.table
↳ array HashMap$HashMapEntry[].[0]
↳ HashMap$HashMapEntry.value
↳ HomeFragment.!(binding)!
↳ FragmentHomeBindingImpl.!(mboundView0)!
↳ ConstraintLayout

我该如何解决这个问题,我是否需要在 onDestroyView 中执行 binding=null?但是,如果我需要这样做,那么 binding.lifecycleOwner = viewLifecycleOwner 的意义何在?

根据片段 lifecycle,调用了 onDestroyView(),但片段未完全销毁 - 因此未调用 onDestroy()。在这种情况下,如果您不手动重置绑定 属性,它会引用 view tree(这是某种泄漏)。

But if I need to do this then what is the point of binding.lifecycleOwner = viewLifecycleOwner then?

如果您向绑定对象提供 LifecycleOwner,它允许观察生成的绑定 class 中的所有 LiveData 个对象。但它无法了解外部 binding 实例的外部引用(来自您项目的任何其他 classes)——这就是它无法自动重置它们的原因。

这里是google docs推荐的初始化和清除Fragments绑定的方法:

科特林:

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view
}

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

Java:

private ResultProfileBinding binding;

@Override
public View onCreateView (LayoutInflater inflater, 
                          ViewGroup container, 
                          Bundle savedInstanceState) {
    binding = ResultProfileBinding.inflate(inflater, container, false);
    View view = binding.getRoot();
    return view;
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    binding = null;
}

此外,这里有一个 medium blog post 可以阅读以消除与 属性 委托的绑定内存泄漏。

class FragmentViewBindingDelegate<T : ViewBinding>(
    bindingClass: Class<T>,
    private val fragment: Fragment
) : ReadOnlyProperty<Fragment, T> {

    /**
     * initiate variable for binding view
     */
    private var binding: T? = null

    /**
     * get the bind method from View class
     */
    private val bindMethod = bindingClass.getMethod("bind", View::class.java)

    init {
        fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
            val viewLifecycleOwnerLiveDataObserver =
                Observer<LifecycleOwner?> {
                    val viewLifecycleOwner = it ?: return@Observer

                    viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
                        override fun onDestroy(owner: LifecycleOwner) {
                            binding = null
                        }
                    })
                }

            override fun onCreate(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
            }

            override fun onDestroy(owner: LifecycleOwner) {
                fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
            }
        })
    }

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        binding?.let { return it }


        /**
         * Checking the fragment lifecycle
         */
        val lifecycle = fragment.viewLifecycleOwner.lifecycle
        if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
            error("Cannot access view bindings. View lifecycle is ${lifecycle.currentState}!")
        }


        /**
         * Bind layout
         */
        val invoke = bindMethod.invoke(null, thisRef.requireView()) as T

        return invoke.also { this.binding = it }
    }
}
 
inline fun <reified T : ViewBinding> Fragment.viewBinding() = FragmentViewBindingDelegate(T::class.java, this)

片段内的用法:

  private val binding: Fragment_NAME_Binding by viewBinding()

只需在 onDestroyView() 方法中调用 binding.unbind()