fun <VB : ViewBinding> AppCompatActivity.viewBinding(inflate: (LayoutInflater) -> VB) = lazy {
inflate(layoutInflater).also { binding ->
setContentView(binding.root)
if (binding is ViewDataBinding) binding.lifecycleOwner = this
}
}
//将函数当做参数传递
fun <VB : ViewBinding> Fragment.binding(bind: (View) -> VB) = FragmentBindingDelegate(bind)
//属性委托
class FragmentBindingDelegate<VB : ViewBinding>(private val bind: (View) -> VB) :
ReadOnlyProperty<Fragment, VB> {
private var binding: VB? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): VB {
binding = try {
thisRef.requireView().getBinding(bind).also { binding ->
if (binding is ViewDataBinding) binding.lifecycleOwner = thisRef.viewLifecycleOwner
}
} catch (e: IllegalStateException) {
throw IllegalStateException("The property of ${property.name} has been destroyed.")
}
/*thisRef.viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
//fragment销毁时置为null,避免内存泄露
binding = null
}
})*/
thisRef.viewLifecycleOwner.lifecycle.addObserver(LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) {
binding = null
}
})
return binding!!
}
}
属性委托(Property Delegation)是 Kotlin 的一个强大特性,它允许你将属性的 getter 和 setter 逻辑委托给另一个对象处理,而不是在类中直接编写这些逻辑。
1. 核心概念
当你声明一个委托属性时:
val binding by viewBinding(ActivityMainBinding::inflate)
编译器不会生成普通的字段,而是生成一个隐藏的委托对象。当你访问 binding 时,实际上是在调用委托对象的 getValue 方法。
2. 代码中的具体实现
FragmentBindingDelegate 类实现了属性委托:
- 接口实现:它实现了
ReadOnlyProperty<Fragment, VB>接口。这意味着它是一个只读属性委托,专门用于Fragment,返回类型是VB (ViewBinding)。 getValue方法:- 这是委托的核心。当你在
Fragment中第一次访问binding时,这个方法会被调用。 - 它通过
thisRef.requireView().getBinding(bind)完成实际的ViewBinding初始化。 - 上述代码中还处理了生命周期观察,确保在
Fragment销毁时将binding置为null,防止内存泄漏。
- 这是委托的核心。当你在
3. 为什么要用属性委托?
在 Android 开发中,属性委托非常有用,原因如下:
-
简化代码:你不需要在每个
Fragment中都手动写private var _binding: ...和val binding get() = _binding!!这种样板代码。 -
统一管理逻辑:如上述代码所示,你可以把“如何初始化”、“如何处理生命周期”、“如何防止空指针”等复杂逻辑全部封装在
FragmentBindingDelegate里。 -
延迟初始化:像上述代码的
viewBinding函数使用了lazy(Kotlin 内置的委托),确保ViewBinding只有在第一次使用时才创建,避免在onCreateView之前访问导致崩溃。
4. 委托对象的生命周期
当你在 Fragment 中写:
private val binding by binding(FragmentTestBinding::bind)
这里的 binding(...) 函数会创建一个新的 FragmentBindingDelegate 实例。这个委托实例是作为 Fragment 的一个成员变量存在的。
5. binding 变量的本质
在委托模式下,Fragment 中的 binding 并不是直接持有 ViewBinding 对象,它只是一个“代理人”。
- 当你调用
binding.root时,Kotlin实际上是在调用委托对象的getValue(this, property)方法。 ViewBinding的真实引用其实存储在FragmentBindingDelegate类内部的private var binding: VB? = null里。
6.销毁过程是如何发生的?
- 添加观察者:在第一次访问
binding时,委托对象会向viewLifecycleOwner添加一个生命周期观察者。 - 监听销毁:当
Fragment的视图销毁(onDestroy)时,观察者被触发。 - 置空操作:执行
binding = null。注意: 这里置空的是委托对象内部的那个binding变量,而不是Fragment里的binding属性。 - 断开引用:一旦委托对象内部的
binding变为null,它就放弃了对ViewBinding以及它所持有的所有View的引用。
虽然 Fragment 实例可能还持有着那个 FragmentBindingDelegate 委托对象,但委托对象内部已经不再持有沉重的 View 引用了。
所以:
Fragment中的binding属性:依然存在(因为它是 val),但它现在只是一个指向“空壳”委托对象的指针。- 真正的
ViewBinding对象:因为委托对象内部的引用被切断,且没有其他强引用指向它,它会被 GC 回收。 - 内存泄露:不会发生,因为最占内存的 View 层级结构已经被释放了。
潜在的小风险
如果 Fragment 本身发生了内存泄露(例如被某个单例或长生命周期对象引用),那么这个 FragmentBindingDelegate 也会跟着泄露。但这个委托模式至少确保了只要 Fragment 进入销毁流程,View 资源就会被立即释放,这是符合 Android 开发规范的。