Kotlin-属性委托

kotlin 复制代码
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 的一个强大特性,它允许你将属性的 gettersetter 逻辑委托给另一个对象处理,而不是在类中直接编写这些逻辑。

1. 核心概念

当你声明一个委托属性时:

kotlin 复制代码
val binding by viewBinding(ActivityMainBinding::inflate)

编译器不会生成普通的字段,而是生成一个隐藏的委托对象。当你访问 binding 时,实际上是在调用委托对象的 getValue 方法。

2. 代码中的具体实现

FragmentBindingDelegate 类实现了属性委托:

  • 接口实现:它实现了 ReadOnlyProperty<Fragment, VB> 接口。这意味着它是一个只读属性委托,专门用于 Fragment,返回类型是 VB (ViewBinding)
  • getValue 方法:
    1. 这是委托的核心。当你在 Fragment 中第一次访问 binding 时,这个方法会被调用。
    2. 它通过 thisRef.requireView().getBinding(bind) 完成实际的 ViewBinding 初始化。
    3. 上述代码中还处理了生命周期观察,确保在 Fragment 销毁时将 binding 置为 null,防止内存泄漏。
3. 为什么要用属性委托?

Android 开发中,属性委托非常有用,原因如下:

  1. 简化代码:你不需要在每个 Fragment 中都手动写 private var _binding: ... val binding get() = _binding!! 这种样板代码。

  2. 统一管理逻辑:如上述代码所示,你可以把"如何初始化"、"如何处理生命周期"、"如何防止空指针"等复杂逻辑全部封装在 FragmentBindingDelegate 里。

  3. 延迟初始化:像上述代码的 viewBinding 函数使用了 lazy(Kotlin 内置的委托),确保 ViewBinding 只有在第一次使用时才创建,避免在 onCreateView 之前访问导致崩溃。

4. 委托对象的生命周期

当你在 Fragment 中写:

kotlin 复制代码
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 开发规范的。

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio