最近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也要寿终正寝,即将迎来它生命的终点。借这个机会,我们不妨来回顾一下Android开发中findView的发展史,以及展望下findView的未来。

一、Android绑定View的发展史

从Android系统诞生至今,在代码中findView一直是Android开发者无法绕开的一道程序。从最初的findViewbyId到如今炙手可热的ViewBinding,期间涌现出了许多findView的方式,它们让findView变得更加简单,也让我们的代码变得更加简洁。但随着Android新技术的发展,这些findView的方法也正在被一个一个的抛弃。本节内容我们就来回顾一下Android开发中findView的发展史。

1.findViewById

findViewById是Android开发中最原始,也是最基础的一种获取View的方法。它由Google官方提供,在Android开发生态的早期也是唯一一种能够获取View的方式。虽然它使用简单且根正苗红,贯穿古今。但由于高度重复的代码结构深受开发者诟病。在一个复杂布局的页面仅仅是findViewById的代码往往就能达到数十行。开发者无时无刻不想着弃用这一方案,因此后续衍生出了多种获取View的方式来简化代码。但万变不离其宗,归根结底,这些方式最终都还是通过findViewById来实现的。虽然它是最不被开发者认可的一种的方式,但时至今日开发者也无法摆脱它笼罩着的阴影。一脸你看不惯我你打我呀的表情!

2.ButterKnife

就在大家都在唾弃findViewById的大量重复代码时,一个插件横空出世。它通过一个BindView注解,传入一个Resource Id就能轻松获取到Id对应的View。代码如下:

public class MainActivity extends AppCompatActivity {@BindView(R.id.text_view)TextView mTextView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);}}

它就是红极一时,时至今日大家依然还在用着的ButterKnife。ButterKnife通过最前沿的Java技术(最初的版本可能是反射,未加考究)–Java编译时注解处理器,在编译时自动生成findViewById的代码。例如,上边的例子通过ButterKnife会生成一个MainActivity_ViewBinding 类,在这个类中通过findViewById为mTextView赋值,其代码如下:

public class MainActivity_ViewBinding implements Unbinder {private MainActivity target;@UiThreadpublic MainActivity_ViewBinding(MainActivity target) {this(target, target.getWindow().getDecorView());}@UiThreadpublic MainActivity_ViewBinding(MainActivity target, View source) {this.target = target;target.mTextView = Utils.findRequiredViewAsType(source, R.id.text_view, "field 'mTextView'", TextView.class);}@Override@CallSuperpublic void unbind() {MainActivity target = this.target;if (target == null) throw new IllegalStateException("Bindings already cleare");this.target = null;target.mTextView = null;}
}

这一操作省去了开发者手动编写findViewById的时间,大大简化了代码,同时提高了开发效率。在当时的开发者看来ButterKnife不得不说是一个神器,以至于到后来成了Android项目开发的标配。

后来,随着Android Studio的诞生,Eclipse开发Android项目逐渐淡出历史舞台。Android studio的出现,带来了全新的技术,模块化风靡一时。大概在这个时候,Google官方似乎就已经有了改造R类的想法。在Android项目的library模块中,生成R类中的成员变量就已经改为了非final修饰。同时,Google官方也不再建议在app模块的代码中使用像:switch(view.getId())这样的代码。

正如Android studio官网文档《Non-constant Fields in Case Labels》上给出的原因:

In other words, the constants are not final in a library project. The reason for this is simple: When multiple library projects are combined, the actual values of the fields (which must be unique) could collide. Before ADT 14, all fields were final, so as a result, all libraries had to have all their resources and associated Java code recompiled along with the main project whenever they were used. This was bad for performance, since it made builds very slow. It also prevented distributing library projects that didn’t include the source code, limiting the usage scope of library projects.

这一改变直接致使ButterKnife无法在Android项目的library模块中使用。而此时,ButterKnife正是如日中天,追随的开发者不计其数。为了能够让ButterKnife运行在library模块,ButterKnife的作者Jake Wharton大佬曲线救国,通过生成R2类让ButterKnife在library模块中复活,并且得以发展壮大。但不得不说,此时的ButterKnife就已经埋下了深深的隐患,并导致了其最终的溃败。

3.DataBinding

DataBinding是Google官方在2015年谷歌I/O大会上发布的一个数据绑定框架,它并非专为findView而生,而是作为MVVM架构的双向绑定数据的工具。findView的功能仅仅是DataBinding的一个附赠品。

开发者一般会在MVVM架构的项目中使用DataBinding来获取View。但是它也有很多诟病,比如需要修改xml的结构,在xml外部嵌套一个标签。并且很多情况下需要手动build才能生成DataBinding相关类。诸如此类问题,自然不会得到开发者的青睐。

关于DataBinding的详细使用在这里不做探讨。

4.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的?我们不妨写一个简单的例子:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)textView.text = "Test"}
}

布局文件中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而出错
  • 空安全:这主要体现在Configuration中的对应布局不全时,运行时可能出现NPE
  • 兼容性:只能在kotlin中使用,java不友好
  • 局限性:不能跨module使用

也正是这几个缺点导致了KAE的大溃败。随着Google对亲儿子ViewBinding的大力推广,KAE最终也招架不住,只能缴械投降—Jetbrains在官网宣布废弃KAE,并推荐开发者使用ViewBinding.

5.ViewBinding

到这里,以上提到的多种findView方案都已经被废弃,唯独只剩Google官方正在大力推广的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的使用方法。

二、ViewBinding使用详解

1.开启ViewBinding

Android Studio对于ViewBinding的支持是从3.6版本开始的,AS 3.6版本内置了Gradle插件。只需要在build.gradle中通过以下配置即可开启ViewBinding:

android {buildFeatures {viewBinding = true}
}

如果,你的项目存在多个模块,则需要在每个模块的gradle中添加上述配置。完成以上配置后ViewBinding会为所有布局文件自动生成对应的绑定类。且无须修改原有布局的 XML 文件,ViewBinding会根据现有的布局自动完成所有工作。

2.在Activity中使用ViewBinding

首先编写activity_main.xml的布局文件,如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><TextViewandroid:id="@+id/text_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

完成后gradle插件会自动生成一个名为ActivityMainBinding的Java类,在Activity中通过ActivityMainBinding获取Binding实例,如下:

 override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.textView.text = "Hello World"}

3.ViewBinding与include标签

在项目开发中,通常我们会使用include标签来简化布局文件,那么在使用了include标签的布局文件中,应该如何使用ViewBinding呢?且看代码:

// activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><includeandroid:id="@+id/include"layout="@layout/layout_include" /></androidx.constraintlayout.widget.ConstraintLayout>// layout_include.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/tv_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

上述两个布局文件会分别生成ActivityMainBinding与LayoutIncludeBinding两个Java类,并且ActivityMainBinding类中通过组合依赖了LayoutIncludeBinding类。因此,使用方式如下:

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)//  从ActivityMainBinding中获取LayoutIncludeBindingval include = binding.include// 通过LayoutIncludeBinding为TextView赋值include.tvText.text = "Hello World"}

如果layout_include.xml文件位于子模块,经实践与以上代码的使用方式并无任何差异,但一定要在子模块中开启ViewBinding才行。

4.ViewBinding在Fragment中的使用
在Fragment中使用ViewBinding与Activity中有些差异,这里为了简便,我们使用上述中的activity_main.xml作为Fragment的布局文件,则Fragment的代码如下:

    private lateinit var binding: ActivityMainBindingoverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {binding = ActivityMainBinding.inflate(inflater, container, false)return binding.root}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)binding.textView.text="Hello World"}

5.ViewBinding在RecyclerView#Adapter中的使用

布局文件不再贴出,直接看Adapter的代码,如下所示:

 class TestAdapter : RecyclerView.Adapter<TestViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {val binding =ItemTestBinding.inflate(LayoutInflater.from(parent.context))return TestViewHolder(binding)}override fun onBindViewHolder(holder: TestViewHolder, position: Int) {holder.binding.textView.text = "Hello World"}override fun getItemCount(): Int {return 10}class TestViewHolder(var binding: ItemTestBinding) :RecyclerView.ViewHolder(binding.root)
}

通过以上几个实例可以看到ViewBinding的使用是非常简单的。而ViewBinding的实现原理也并不难,Gradle插件会根据布局文件在项目的build目录下生成相应的ViewBinding类,并且,最终也是通过findViewById来完成View的获取的。具体实现代码不再贴出,感兴趣的同学可以自行查看。

三、展望与总结

时代在发展,Android获取View的方式仍在变化。ViewBinding无疑是一个优秀的组件,但它真的是Android开发中获取View的最优方案吗?显然,并不是!因为ViewBinding归根结底还是通过findViewById实现,且需要插件生成相关的Binding类,虽然省去了手动编写,但是ViewBinding仍然没能解决代码冗余的问题。那什么才是findViewById的未来呢?大概最好的findView就是没有findView吧!目前Google正在朝着这一方向努力,正在开发的Jetpack Compose库就是要取代Android的布局文件,彻底消除findViewById。相信在未来某一天,随着Jetpack Compose库的普及,这个旷日持久的findViewById之争也最终会画上一个圆满的句号。

参考&推荐阅读

Non-constant Fields in Case Labels

The future of Kotlin Android Extensions

Kotlin Android Extensions遭废弃,官方推荐使用ViewBinding

使用视图绑定替代 findViewById

ButterKnife被弃用,ViewBinding才是findView的未来?相关推荐

  1. BCH或许才是真正的未来

    首先让我们回顾17年BCH前进的步伐 1)8月份运行暴露算力震荡问题: 2)9月初达成修改难度调整算法共识: 3)9月29日第一个方案代码完成: 4)10月初达成2x分叉前升级难度算法共识: 5)10 ...

  2. 不做在线电商,或许才是永辉超市的未来

    文 | 陈曦 来源|螳螂财经(ID:TanglangFin) 中国的商业模式很奇怪,明明是亏钱,而且是亏大钱,但大家前仆后继.不敢停下来,都想着烧钱跑马圈地. 圈好地怎么办?还没想好. 生鲜电商就是一 ...

  3. 李彦宏纳猛将,技术才是百度的未来

    5月17日,百度公司正式成立百度硅谷实验室,并宣布任命前谷歌大脑(Google Brain)项目负责人吴恩达为百度首席科学家,全面负责百度研究院. 百度研究院 百度深度学习研究院(IDL) 成立于20 ...

  4. 虚拟机才是 Kubernetes 的未来?

    Kubernetes 的未来到底在哪里?本文作者一一为你解析. 作者 | Paul Czarkowski 译者 | 弯月 责编 | 屠敏 出品 | CSDN(ID:CSDNNews) 凝视水晶球 今年 ...

  5. 互联网+来袭,消费者更挑剔,什么才是零售业的未来?

    尽管距离双11还有两个多月,然而许多在线零售商已经紧锣密鼓的为这个全民购物节做起了准备.电商平台发展今天并改变了零售业的发展模式有其独特的因素.这对传统零售带来的变化是,顾客无需踏进商店,便可随心所欲 ...

  6. 数据才是车联网的未来

    "每一年都是中国车联网的元年." 看到有关车联网的报道,不由发出这样的感慨.这当然不是什么好的评价.因为这意味着每年都在推倒重来.从08年国内开始提到车联网,到2010年后各大车厂 ...

  7. java加厚_我的世界:为何说基岩版才是MC的未来?五个对比让你弄清其中原因

    原标题:我的世界:为何说基岩版才是MC的未来?五个对比让你弄清其中原因 籽岷大大曾说:基岩版将是Minecraft的未来.对于这句话,图酱曾单纯地理解为移动端玩家基数大,MC从端游转向手游是理所当然的 ...

  8. 讨论丨深度学习已经成为过去?迁移学习才是真正的未来?

    大牛吴恩达曾经说过:做AI研究就像造宇宙飞船,除了充足的燃料之外,强劲的引擎也是必不可少的.假如燃料不足,则飞船就无法进入预定轨道.而引擎不够强劲,飞船甚至不能升空.类比于AI,深度学习模型就好像引擎 ...

  9. 贴牌是赢在当下,创牌才会更有未来

    作者/易牟 来源 | 螳螂财经(ID:TanglangFin) 贴牌和创牌,到底谁才是沧桑正道? 在中国,贴牌与创牌的优劣在很长一段时间内都无法定论.毕竟,贴牌代工的投入低,回报快,是很多中小企业和出 ...

  10. 融云韩迎:中国技术型公司出海才刚开始,未来有很大发展空间

    技术型公司出海是很好的选择,日前,WISEx新出海行业峰会上融云CEO韩迎指出,未来这将有很大的发展空间. 作为IM云服务提供商,融云最初是以APP内社交.直播互动.企业IM.商务沟通四大场景起步,先 ...

最新文章

  1. java 根据类名示例化类_Java类类getEnclosingClass()方法及示例
  2. python新手入门总结_写给已有编程经验的 Python 初学者的总结
  3. Facebook的秘密服务器,竟藏着互联网的军事根源?
  4. 2016网络安全犯罪形势预测
  5. 微信认证小程序如何绑定微信支付商户平台
  6. 中国智慧消防产业需求现状及十四五发展趋向分析报告2021-2027年版
  7. VS.Net 2005 下载地址
  8. 【C语言】'\0'、'0'、' '、“0”、0详解
  9. [vue-ts]ts版本问题合集
  10. 2022中国大学python语言程序设计测试六答案(北理工嵩天 、黄天羽 、礼欣)
  11. ACM--过沼泽--模拟--HDOJ 5477--A Sweet Journey
  12. mysql查询最高分学生年龄_MySQL练习题student_score
  13. a或b search vim_vim入门,进阶与折腾
  14. HTML+css简单实现手机邮箱导航网页
  15. 1.amdahl定律(加速比)
  16. 麦克风阵列声源定位实现
  17. chdir: No such file or directory (chdir()函数、strtok()函数)
  18. K8S 故障处理经验积累(网络)
  19. matlab语音加入正弦噪声,基于Matlab的语音信号去噪声处理 毕业论文.doc
  20. java rsa_RSA Java

热门文章

  1. 腾讯android一键root工具,腾讯一键Root工具
  2. steam安装维护服务器,安装steam无法连接服务器 | 手游网游页游攻略大全
  3. 初创公司需不需要产品经理?
  4. Android TV 认证简介
  5. NOIP提高组【JZOJ4816】label
  6. 1.6 SSH免密登录
  7. C++游戏编程-走迷宫详解
  8. BTA12A-ASEMI高效mos管BTA12A
  9. 三层交换机原理及实验操作
  10. android像素密度转厘米,Android 屏幕适配-像素密度适配