深入理解Jetpack——ViewBinding

视图绑定的发展历史

在这里插入图片描述

如上图所示,视图绑定的发展历史经过了 findViewById --> ButterKnife --> Kotlin的扩展插件 --> ViewBinding 的变化。

findViewById 是原生获取对应View的接口,缺点是View的获取和绑定太麻烦。因此出现了很多框架来解决这个问题。最开始是 ButterKnife,它通过 APT 运行时注解生成的方式获取 View,但是它对组件化的支持不友好,目前该框架已经不再维护。

在 Google 主推 Kotlin 后,视图绑定一般会使用 Kotlin 的扩展插件,但是Kotlin 的扩展插件无法跨模块操作,对不同的资源文件存在相同id时,在引用来源时可能出错。在1.4版本该插件就被废弃了,目前推荐的就是这篇文章讲的 ViewBinding 了。

ViewBinding 会在编译时扫描 layout 文件生成对应的 ViewBinding 绑定类。它的优势是确保了获取对应 View 的空安全和类型安全。

ViewBinding 的使用

ViewBinding 的使用很简单,只需要在 build.gradle 中设置启用 viewBinding 就可以了。代码如下:

android {
    ...
    viewBinding {
        enabled = true
    }
    ...
}

在配置完成后,该模块中的每个 layout 文件都会生成一个对应的绑定类。该绑定类的命名就是 layou 文件的名称转换为驼峰形式,并在末尾添加 Binding 一词。以 activity_main.xml 为例,其对应的绑定类名称为 ActivityMainBinding。

生成的绑定类一般可以在 build/generated/data_binding_base_class_source_out/debug/out 目录下的 your/package/name/databinding 中找到

启用 ViewBinding 功能的配置会为整个模块的所有布局文件生成对应的绑定类。如果某个布局文件不需要的话,可以使用 tools:viewBinding-Ignore 属性来设置,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:viewBinding-Ignore="true"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</androidx.constraintlayout.widget.ConstraintLayout>

除此之外,我们还可以使用 tools:viewBindingType 属性来设置绑定的类型,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/edit_text"
        tools:viewBindingType="android.widget.TextView" //设置 editText 类型为 TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

ViewBinding 的注意事项

在 Fragment 中使用 ViewBinding 时,需要注意的一点是,开发者需要在 onDestroyView 方法中将绑定类实例赋值为 null。这是因为 Fragment 的存在时间比其视图时间长,所以开发者需要在 onDestroyView 方法中清除对绑定类实例的所有引用,否则可能存在内存泄漏的风险。代码示例如下:

private var _binding: ResultProfileBinding? = null
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
}

不过这种通过人工的方式来设置为 null 太麻烦了,很多人可能一下子就忘了。要解决这个问题,我可以使用 Kotlin 的委托,代码示例如下所示。

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
    private var binding: T? = null

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

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        val binding = binding
        if (binding != null) {
            return binding
        }

        val lifecycle = fragment.viewLifecycleOwner.lifecycle
        if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
    }
}

//Fragment 扩展方法 viewBinding
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
    FragmentViewBindingDelegate(this, viewBindingFactory)
    
class MyFragment: Fragment() {

    val binding by viewBinding(FragmentMyBinding::bind)
    
}       

这样我们就可以通过 viewBinding 扩展方法来处理 ViewBinding 的置 null 了,避免了手动操作时程序员忘记的问题。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值