Android绑定View的发展史
从Android系统诞生至今,在代码中findView一直是Android开发者无法绕开的一道程序。从最初的findViewbyId到如今炙手可热的ViewBinding,期间涌现出了许多findView的方式,它们让findView变得更加简单,也让我们的代码变得更加简洁。但随着Android新技术的发展,这些findView的方法也正在被一个一个的抛弃。本节内容我们就来回顾一下Android开发中findView的发展史。
findViewById
findViewById是Android开发中最原始,也是最基础的一种获取View的方法。它由Google官方提供,在Android开发生态的早期也是唯一一种能够获取View的方式。虽然它使用简单且根正苗红,贯穿古今,但由于高度重复的代码结构深受开发者诟病。在一个复杂布局的页面仅仅是findViewById的代码就很多。开发者无时无刻不想着弃用这一方案,因此后续衍生出了多种获取View的方式来简化代码。但万变不离其宗,归根结底,这些方式最终都还是通过findViewById来实现的。
ButterKnife
就在大家都在唾弃findViewById的大量重复代码时,一个插件横空出世。它通过一个BindView注解,传入一个Resource Id就能轻松获取到Id对应的View。代码如下:
1 | public class MainActivity extends AppCompatActivity { |
它就是红极一时,时至今日大家依然还在用着的ButterKnife。ButterKnife通过Java编译时注解处理器,在编译时自动生成findViewById的代码。例如,上边的例子通过ButterKnife会生成一个MainActivity_ViewBinding 类,在这个类中通过findViewById为mTextView赋值,其代码如下:
1 | public class MainActivity_ViewBinding implements Unbinder { |
这一操作省去了开发者手动编写findViewById的时间,大大简化了代码,同时提高了开发效率。在开发者看来ButterKnife不得不说是一个神器,以至于到后来成了Android项目开发的标配。
但是Android Studio更新到了4.1版本后,发现项目中使用ButterKnife注解id的代码出现了警告,警告信息如下:
Resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them as annotation attributes
从警告信息中可以看到在Gradle 5.0的插件中Resource的Id值将不会再是final类型,因此应该避免在注解属性中使用Id。这意味着当我们把Gradle插件升级到5.0版本之后ButterKnife将无法再被使用!同时,我们在ButterKnife的官方文档上也看到了ButterKnife被标注弃用的信息:
陪伴我们多年,曾经辉煌一时,不可一世的ButterKnife也要寿终正寝,即将迎来它生命的终点。
DataBinding
DataBinding是Google官方在2015年谷歌I/O大会上发布的一个数据绑定框架,它并非专为findView而生,而是作为MVVM架构的双向绑定数据的工具。findView的功能仅仅是DataBinding的一个附赠品。
开发者一般会在MVVM架构的项目中使用DataBinding来获取View。但是它也有很多诟病,比如需要修改xml的结构,在xml外部嵌套一个标签。并且很多情况下需要手动build才能生成DataBinding相关类。诸如此类问题,自然不会得到开发者的青睐。
关于DataBinding的详细使用在这里不做探讨。
Kotlin Android Extensions
2017年Google I/O开发者大会中,Google宣布Kotlin成为Android开发的一级语言,自此,Kotlin “转正”与Java并驾齐驱。而JetBrain推出的Kotlin Android Extension(以下简称KAE)插件成为了有史以来最简单的获取View的方法,简单到无需任何代码,直接通过id作为View使用。这一功能足以让所有Android开发者抓狂,纷纷感叹这才是findView的未来啊,终于可以和裹挟开发者十多年的findViewById说拜拜了! 作为一个Android开发者,不知道你是否会好奇Kotlin是如何将Id作为View的?我们不妨写一个简单的例子:
1 | class MainActivity : AppCompatActivity() { |
布局文件中TextView的id设置为“textView”,则在Activity中可以直接将textView作为一个TextView来使用。我们通过Android Studio的工具将kotlin的字节码反编译成Java代码看下。
通过上述操作,打开kotlin的字节码后,再通过Decompile反编译成Java代码,则会得到如下图所示的结果:
通过反编译得到的Java代码我们发现Kotlin的这一操作其实也是通过findViewById实现的。只是通过插件的方式让我们感觉上是用了View的Id。
通过Kotlin的扩展插件来find view,无疑是一种优秀的方案。但这一方案并不是无懈可击。它存在以下几个缺点:
- 类型安全:res下的任何id都可以被访问,有可能因访问了非当前Layout下的id而出错
- 空安全:这主要体现在配置中的对应布局不全时,运行时可能出现NPE
- 兼容性:只能在kotlin中使用,java不友好
- 局限性:不能跨module使用
也正是这几个缺点导致了KAE的大溃败。随着Google对亲儿子ViewBinding的大力推广,KAE最终也招架不住,只能缴械投降—Jetbrains在官网宣布废弃KAE,并推荐开发者使用ViewBinding。
ViewBinding
ViewBinding是Google在2019年I/O大会上公布的一款Android视图绑定工具。它的使用方式有点类似DataBinding,但相比DataBinding,ViewBinding是一个更轻量级、更纯粹的findViewById的替代方案。它具有以下几个优点:
- 类型安全: ViewBinding会基于布局中的View生成类型正确的属性。比如,在布局中放入了一个 TextView ,视图绑定就会暴露出一个 TextView 类型的属性供开发者使用。
- 空安全:ViewBinding会检测某个视图是不是只在一些配置下存在,并依据结果生成带有 @Nullable 注解的属性。所以即使在多种配置下定义的布局文件,视图绑定依然能够保证空安全。
- ViewBinding生成的绑定类是一个Java类,并且添加了Kotlin的注解,可以很好的支持 Java 和 Kotlin 两种编程语言。
同时,Google官方还给出了一个ViewBinding、ButterKnife以及KAE的对比,如下图:
ViewBinding使用详解
开启ViewBinding
Android Studio对于ViewBinding的支持是从3.6版本开始的,AS 3.6版本内置了Gradle插件。只需要在build.gradle中通过以下配置即可开启ViewBinding:
1 | // 需要 Android Gradle Plugin 3.6.0 |
在 Android Studio 4.0 中,viewBinding 变成属性被整合到了 buildFeatures 选项中,所以配置要改成:
1 | // Android Studio 4.0 |
如果,你的项目存在多个模块,则需要在每个模块的gradle中添加上述配置。完成以上配置后ViewBinding会为所有布局文件自动生成对应的绑定类。且无须修改原有布局的 XML 文件,ViewBinding会根据现有的布局自动完成所有工作。
在Activity中使用ViewBinding
首先编写activity_main.xml的布局文件,如下:
1 | <?xml version="1.0" encoding="utf-8"?> |
完成后gradle插件会自动生成一个名为ActivityMainBinding的Java类,在Activity中通过ActivityMainBinding获取Binding实例,如下:
1 | override fun onCreate(savedInstanceState: Bundle?) { |
ViewBinding与include标签
在项目开发中,通常我们会使用include标签来简化布局文件,那么在使用了include标签的布局文件中,应该如何使用ViewBinding呢?且看代码:
1 | // activity_main.xml |
上述两个布局文件会分别生成ActivityMainBinding与LayoutIncludeBinding两个Java类,并且ActivityMainBinding类中通过组合依赖了LayoutIncludeBinding类。因此,使用方式如下:
1 | override fun onCreate(savedInstanceState: Bundle?) { |
如果layout_include.xml文件位于子模块,经实践与以上代码的使用方式并无任何差异,但一定要在子模块中开启ViewBinding才行。
ViewBinding在Fragment中的使用
在Fragment中使用ViewBinding与Activity中有些差异,这里为了简便,我们使用上述中的activity_main.xml作为Fragment的布局文件,则Fragment的代码如下:
1 | private lateinit var binding: ActivityMainBinding |
ViewBinding在RecyclerView#Adapter中的使用
布局文件不再贴出,直接看Adapter的代码,如下所示:
1 | class TestAdapter : RecyclerView.Adapter<TestViewHolder>() { |
通过以上几个实例可以看到ViewBinding的使用是非常简单的。而ViewBinding的实现原理也并不难,Gradle插件会根据布局文件在项目的build目录下生成相应的ViewBinding类,并且,最终也是通过findViewById来完成View的获取的。