博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
你好, View Binding! 再次再见, findViewById!
阅读量:2457 次
发布时间:2019-05-10

本文共 7906 字,大约阅读时间需要 26 分钟。

作为一个 Android 开发者, 你一定听说过 findViewById. 这个方法可以根据 ID 去匹配对应的 View. 实现了类似功能或者增强了其功能的还有:

  • Butter Knife(Kotter Knife)
  • Kotlin Android Extensions
  • Data Binding
  • View Binding
    为什么不是 findViewById/Butter Knife(Kotter Knife)/Kotlin Android Extensions ?
// findViewByIdval fab = findViewById
(R.id.fab)val toolbar = findViewById
(R.id.toolbar)setSupportActionBar(toolbar)fab.setOnClickListener { view ->}// Kotter Knifeval fab: FloatingActionButton by bindView(R.id.fab)val toolbar: Toolbar by bindView(R.id.toolbar)setSupportActionBar(toolbar)fab.setOnClickListener { view ->}// Kotlin Android Extensionsimport kotlinx.android.synthetic.main.activity_main.*setSupportActionBar(toolbar)fab.setOnClickListener { view ->}// Data Binding & View Bindingval binding = ActivityMainBinding.inflate(layoutInflater)setSupportActionBar(binding.toolbar)binding.fab.setOnClickListener { view ->}

优雅程度

可以确定的是 findViewById 和 Kotter Knife 是最不优雅的. 每初始化一个 view 都需要调用一次 findViewById 或 bindView 方法, 导致 activity 或 fragment 中充斥着许多模版代码. Kotlin Android Extensions(view cache map), Data Binding 与 View Binding (binding class)则通过生成一个中间变量的方式减少了模版代码的产生, 想象一下如果你有 20 个 view 需要初始化.

类型安全

// API 26 之前public final View findViewById(int id) {    if (id < 0) {        return null;    }    return findViewTraversal(id);}protected View findViewTraversal(int id) {    if (id == mID) {        return this;    }    return null;}// API 26 及以后@Nullablepublic final 
T findViewById(@IdRes int id) { if (id == NO_ID) { return null; } return findViewTraversal(id);}protected
T findViewTraversal(@IdRes int id) { if (id == mID) { return (T) this; } return null;}

在 API 26 之前, 初始化 view需要进行强转, 比如 val fab = findViewById(R.id.fab) as FloatingActionButton. 众所周知(并且我相信你也遇到过), 强转是有可能产生 ClassCastException 的. 尽管 Google 在 API 26 更新 findViewById 方法为泛型实现, 但是仍然存在强转的问题, 想象一下这段代码: val fab = findViewById(R.id.fab), 在编译期不会有任何的问题, 但是很明显运行时会出错. Butter Knife 和 Kotter Knife 同样存在这样的问题。

空安全

首先说说Kotlin Android Extensions存在的问题.

import kotlinx.android.synthetic.main.fragment_main.*class MainFragment : Fragment() {    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {        val rootView = inflater.inflate(R.layout.fragment_main, container, false)        text.setOnClickListener {        }        return rootView    }}

看起来没有问题对吧, 实际上运行之后:

Caused by: android.view.InflateException: Binary XML file line #24 in io.tonnyl.demo:layout/activity_main: Binary XML file line #8 in io.tonnyl.demo:layout/content_main: Error inflating class fragmentCaused by: android.view.InflateException: Binary XML file line #8 in io.tonnyl.demo:layout/content_main: Error inflating class fragmentCaused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.widget.AppCompatTextView.setOnClickListener(android.view.View$OnClickListener)' on a null object reference

让我们看看生成的代码:

public final class MainFragment extends Fragment {    private HashMap _$_findViewCache;    @Nullable    public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {       Intrinsics.checkParameterIsNotNull(inflater, "inflater");       View rootView = inflater.inflate(1300003, container, false);       ((AppCompatTextView)this._$_findCachedViewById(id.text)).setOnClickListener((OnClickListener)null.INSTANCE);       return rootView;    }    public View _$_findCachedViewById(int var1) {       if (this._$_findViewCache == null) {          this._$_findViewCache = new HashMap();       }       View var2 = (View)this._$_findViewCache.get(var1);       if (var2 == null) {          View var10000 = this.getView();          if (var10000 == null) {             return null;          }          var2 = var10000.findViewById(var1);          this._$_findViewCache.put(var1, var2);       }       return var2;    }}

问题就出在 _$_findCachedViewById 方法 this.getView() 这一行, 调用时 onCreateView() 方法还没有返回值, 所以 this.getView() 返回 null, 在 onCreateView 中调用 text.setOnClickListener {} 不会有任何的错误提示, 因为 text 在这里会被认为是非空的.

当然, 上面的问题还是可以被解决的.

import kotlinx.android.synthetic.main.fragment_main.view.*class MainFragment : Fragment() {    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {        val rootView = inflater.inflate(R.layout.fragment_main, container, false)        rootView.text.setOnClickListener {}        return rootView    }}

注意导入的类的变化. 再看看生成的代码的变化:

public final class MainFragment extends Fragment {    @Nullable    public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {       Intrinsics.checkParameterIsNotNull(inflater, "inflater");       View rootView = inflater.inflate(1300003, container, false);       Intrinsics.checkExpressionValueIsNotNull(rootView, "rootView");       ((AppCompatTextView)rootView.findViewById(id.text)).setOnClickListener((OnClickListener)null.INSTANCE);       return rootView;    }}

这里就不再通过 _$_findViewCache 而是直接通过 findViewById 实现了.

Kotlin Android Extensions 还存在一些其他的问题, Google 内部也不再使用, 具体原因可以参考 Why kotlinx synthetic is no longer a recommended practice.

再说说 findViewById 和 Butter Knife(Kotter Knife) 的问题. 考虑下面的代码:

val fab = findViewById
(View.NO_ID)val fab2: FloatingActionButton by bindView(0)

我们都希望在编译期就可以发现问题, 而不是运行时. 但不幸的是上面的代码正好在编译期不会有任何问题而运行时会出错.

view binding 会直接创建对 view 的引用, 所以不用担心因为无效的 view ID 而产生的空指针错误. 并且, 如果一个 view 只出现在部分配置的布局中, 那么, binding class 中包含改引用的字段会被标记为 @Nullable.

为什么不是 Data Binding ?

只有布局文件的根标签是时, Data Binding 才会生成对应的 binding class, View Binding 没有这样的要求;

Data Binding 会影响构建的速度. Data Binding 底层其实是通过 annotation processor 实现的, 对构建速度是有负面影响的. 而 View Binding 并不是通过 annotation processor 实现的, 因此解决了 Data Binding 的性能问题.
什么是 View Binding ?

View Binding 是一项使你能更轻松地编写与视图交互的代码的功能. 在模块中启用 View Binding 后, 它会为该模块中存在的每一个 XML 文件生成一个对应的绑定类(binding class). 绑定类的实例包含了对应布局中所有具有 ID 的 view 的直接引用.

大多数情况下, View Binding 可以替换 findViewById.
来自 Android Developers.

View Binding 第一次出现是在 2019 年的 Google I/O 大会 What’s New in Android (Google I/O’19) 演讲.

如何使用 View Binding ?

使用要求

你至少需要使用 Android Studio 3.6 Canary 11 及以上版本 才可以开启 View Binding.

设置指南

View Binding 可以逐模块(module)开启. 比如我们的项目由 2 个模块(A 和 B)组成, 我们可以选择只在模块 A 启用 View Binding 而不会对模块 B 产生影响. 在模块中启用 View Binding, 首先需要在该模块的 build.gradle 文件中添加以下代码:

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

如果想要在生成 binding class 时忽略某个布局文件, 我们需要在该布局文件的根元素添加 tools:viewBindingIgnore=“true” 属性.

...

使用方法

和 Data Binding 一样, View Binding 会将 XML 文件的下划线风格的名称转换生成一个驼峰风格并以 Binding 结尾的 binding class.

例如我们有个 result_profile.xml 布局文件:

生成的 binding class 的名称就是 ResultProfileBinding. 这个类有两个字段: 一个叫 name 的 TextView 和一个叫 button 的 Button, 布局文件中的 ImageView 因为没有 ID, 所以 binding class 中没有对其的引用.

每一个 binding class 都包含了一个 getRoot() 的方法, 提供了一个相应布局文件的根 view 的直接引用. 在上面的例子中, ResultProfileBinding 类中的 getRoot()方法返回了根 view LinearLayout.

我们可以调用 inflate() 静态方法来获取生成的 binding class 的实例. 通常来说, 你需要调用 setContentView() 方法, 将生成类的根 view 作为参数传递, 作为屏幕上的内容. 在上面的例子中, 我们可以在 activity 中调用 ResultProfileBinding.inflate().

private lateinit var binding: ResultProfileBinding@Overridefun onCreate(savedInstanceState: Bundle) {  super.onCreate(savedInstanceState)  binding = ResultProfileBinding.inflate(layoutInflater)  setContentView(binding.root)}

然后就可以通过 binding class 引用任何 view:

binding.name.text = viewModel.namebinding.button.setOnClickListener { viewModel.userClicked() }

结语

没错, 我们 又 要和 findViewById 说再见了, View Binding 确实足够简单, 也足够强大. Jake Wharton 也把 Kotter Knife 标记为了「废弃」, 并且推荐使用 View Binding.

有机会的话, 推荐你尝试一下吧.

文章最后我给大家推荐一个最简单也是最有效的学习提升方法:脑图 + 视频 + 资料

在这也分享一份自己收录整理的 ,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

如果你有需要的话,可以点赞+评论关注我加Vx:q1607947758(备注简书,需要进阶资料)

转载地址:http://eachb.baihongyu.com/

你可能感兴趣的文章
react 监听组合键_投资组合中需要的5个React项目
查看>>
css 选择器 伪元素_CSS伪元素-解释选择器之前和之后
查看>>
机器学习数据拆分_解释了关键的机器学习概念-数据集拆分和随机森林
查看>>
snapd_snapd使管理Nextcloud变得轻而易举
查看>>
devops和docker_通过免费的2小时Docker课程学习DevOps基础知识
查看>>
python构建json_如何使用Python构建JSON API
查看>>
桌面应用程序 azure_如何开始使用Microsoft Azure-功能应用程序,HTTP触发器和事件队列...
查看>>
矩阵奇异值分解特征值分解_推荐系统中的奇异值分解与矩阵分解
查看>>
异步JavaScript的演变:从回调到承诺,再到异步/等待
查看>>
自己写一个微型数据库_“最国际化的微型机构:”两名伦敦训练营的毕业生如何建造了一个远程…...
查看>>
构建静态服务器_为静态网站构建无服务器联系表
查看>>
塞尔达传说顺序_编码《塞尔达传说》克隆图例
查看>>
spring vertx_如何在Spring设置Vertx
查看>>
在Unity中创建3D直升机游戏
查看>>
编码和编码格式一样吗?_学习如何像专业人士一样编码
查看>>
CSS-in-JS的权衡
查看>>
ssh框架实现数据库_自顶向下介绍SSH及其如何实现安全的数据共享
查看>>
测试驱动开发 测试前移_测试驱动开发简介
查看>>
css 网格布局_我从CSS网格布局中学到的东西
查看>>
快速了解Kubernetes微服务中的通信
查看>>