Android DataBinding存在内存泄漏问题。

26

我正在使用数据绑定,已经声明了一个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$1.this$0 (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还有什么意义呢?


你为什么要持有绑定(binding)?它有什么用处?这是一个真正的问题,我一直将绑定(binding)作为onCreateView的局部变量使用。 - Agent_L
@Agent_L 我可能需要在onCreateView之外访问多个“视图”以执行操作,例如按钮的“可见性”等。 - OhhhThatVarun
对我来说,DataBinding 的整个意义在于再也不必访问 View - Agent_L
@Agent_L 怎么做的?你是通过 ViewModelBinding Adapters 进行所有操作的吗? - OhhhThatVarun
是的,这就是重点。在Fragment/Activity中不再有代码了。 - Agent_L
显示剩余2条评论
4个回答

31

以下是Google Docs推荐的在Fragments中初始化和清除绑定的方法:

Kotlin:

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;
}

此外,这里有一篇中等博客文章可供阅读,以使用属性委托来消除绑定内存泄漏。


3
请注意链接是指ViewBinding,但我认为DataBinding的概念相同,尽管我找不到说明这一点的文档,而且示例也没有将视图引用设置为空。 - Daniel Wilson
我们可以使用 WeakReference<FragmentClassDataBinding> 来实现这个功能,而不必担心在 onDestroyView() 中取消绑定 / 设置 binding = null 吗? - hsm59

6
根据片段生命周期,已调用onDestroyView(),但片段并未完全销毁,因此onDestroy()未被调用。在这种情况下,如果您不手动重置绑定属性,则它将引用视图树(一种泄漏)。

但是,如果我需要这样做,那么绑定.lifecycleOwner = viewLifecycleOwner 的意义是什么?

如果向绑定对象提供LifecycleOwner,则允许观察任何生成的绑定类中的所有LiveData对象。但它无法知道外部对binding实例的引用(来自项目中的任何其他类),因此无法自动重置它们。

1
我应该怎么解决在onDestroyView中出现binding = null的问题? - OhhhThatVarun
如果引用视图有点像泄漏,例如,在我的碎片中的TextView实例也是泄漏吗?因此,传统用法在片段/活动顶部定义所有视图是内存泄漏。那我们应该如何访问视图才是正确的呢? - beigirad
1
@beigirad,引用视图不一定是泄漏。例如,如果您有活动并将视图保存在变量中-这不是内存泄漏,GC在活动销毁后解决该问题。如果您有片段,则只能在onCreateViewonDestroyView调用之间使用视图。如果您可以在调用onDestroyView后访问视图,则存在问题。这对于绑定实例也是如此。 - ConstOrVar
3
如果你在Fragment中将绑定存储在某个变量中,那么手动将其设置为null是完全正常的(就像你需要为存储在变量中的每个视图做的那样),因为Fragment的视图生命周期小于Fragment的生命周期。 - ConstOrVar
2
@MohsenEinhesari,将绑定存储在变量中并不总是必需的-因此它取决于屏幕逻辑。根据文档unbind()仅删除侦听器,但仍保留视图缓存。 - ConstOrVar
显示剩余5条评论

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

我在我的绑定中找不到任何unbind方法。 - Aorlinn
抱歉,是我的错,我使用了ViewBinding而不是DataBinding。 - Aorlinn
太棒了!对我来说起作用了。 - Srushti

0
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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接