Kotlin 代表扰乱导航

Arb*_*tur 4 android android-fragments kotlin android-jetpack android-architecture-navigation

我正在尝试Jetpack Navigation component并设置了一个非常基本的导航图,其中只有 2 个片段,其中一个主片段 ( Foo) 包含一个按钮,该按钮调用导航操作以打开另一个片段 ( Bar)。

仅使用基本的 Android 用法和功能就可以按预期工作,我可以Foo通过按后退按钮导航回并Bar再次向前导航。

我实现了这个方便的delegate类,以我喜欢的方式通过 id 绑定视图(我最初是一个 iOS 开发者)。

class FindViewById<in R, T: View>(private val id: Int) {

    private var view: T? = null

    operator fun getValue(thisRef: R, property: KProperty<*>): T {
        var view = this.view
        if (view == null) {
            view = when (thisRef) {
                is Activity -> thisRef.findViewById(id)!!
                is Fragment -> thisRef.requireView().findViewById(id)!!
                is View -> thisRef.findViewById(id)!!
                else -> throw NullPointerException()
            }
            this.view = view // Comment out to never cache reference
        }
        return view
    }
}
Run Code Online (Sandbox Code Playgroud)

这允许我编写这样的代码

class FragmentFoo: Fragment() {
    
    private val textView: TextView by FindViewById(R.id.text_view)
    private val button: Button by FindViewById(R.id.button)
    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        button.setOnClickListener { 
            findNavController().navigate(R.id.action_foo_to_bar)
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

现在突然当我导航到Bar然后按下后退按钮时,我Foo再次到达,但我无法前进到Bar. 如果我删除其中的行this.view = viewFindViewById它会再次工作。

我的猜测是存在一些与内存相关的问题,尽管我尝试将viewa包裹在里面,WeakReference但并没有解决问题。

我认为在代理中缓存找到的视图在性能方面是一个好主意。

知道为什么会发生这种情况以及如何在缓存找到的视图时解决问题吗?

编辑

我的目的不是要找到另一种引用视图的方法,而是为什么这个委托实现会破坏导航组件,所以如果我将来要创建另一个自定义委托,我就不会再次遇到它。

解决方案

is Fragment -> {
    thisRef.viewLifecycleOwnerLiveData.value!!.lifecycle.addObserver(object: LifecycleEventObserver {
        override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
            if (event == Lifecycle.Event.ON_STOP) this@FindViewById.view = null
        }
    })
    return thisRef.requireView().findViewById(id)!!
}
Run Code Online (Sandbox Code Playgroud)

ese*_*sov 8

在 androidFragment视图中有自己明确定义的生命周期,这个生命周期是独立于片段的生命周期管理的。

当您使用导航组件时,它会在后台使用片段替换事务并将前一个片段添加到返回堆栈中。在这一点上,这个片段进入CREATED状态,正如你在这张上看到的那样,它的视图实际上被破坏了。此时,您的委托仍然保留对这个旧视图层次结构的引用,从而导致内存泄漏。

后来,当你浏览回来,片段可以追溯到通过STARTED进入RESUMED状态,但视图层次的重建-onCreateViewonViewCreated方法,在此过程中再次调用。因此,虽然片段显示了一个全新的视图层次结构,但您的委托仍然引用旧的。

因此,如果您想手动缓存任何视图引用,您需要覆盖onDestroyView和清除这些引用以避免内存泄漏和这种不正确的行为。同样对于这个特定问题,我建议使用ViewBinding

如果您想拥有自己的实现,但不喜欢清除其中的引用onDestroyView(例如,因为它破坏了良好且自包含的抽象),viewLifecycleOwnerLiveData可能有助于观察当前视图状态并在视图被销毁时清除所有引用。

请查看Fragment文档,它最近已更新并涵盖了 Fragment 的大部分方面。