【Android】findViewById优化方案
一篇获取布局中的元素的通神文
作者AS版本:Android Studio 2021.2.1稳定版:代号“花栗鼠”
实验时间:2022/08/05
findViewById(传统方式)
大家第一次接触 Android 想获取布局中的一个控件,估计大家第一个接触的就是 findViewById() 这个传统方式绑定视图吧
配置布局的xml文件时,有时会给View配置id,而在Activity中寻找到该View的方法就是通过该id号来找到该View的。具体调用的方法就是findViewById。
这个findViewById方法顾名思义,就是根据id来找到View
1 | //代码长这样 |
注意哦:《第一行代码》中这里是:显示声明变量类型
有些网上教学是:强制类型转换
1 | val button = findViewById(R.id.button01) as Button |
但是最新版是直接用泛型写啦!!!
Android中的View结构是一个树形结构,findViewById就是自树的根节点,依次遍历其子节点,知道找到目标的id。
原理:
缺点:
- 写的太累啦
- 性能略差,树形结构查找;
- Fragment中容易使用失误;
- 增加代码耦合度,滥用了bug难找;
- NPE:一个大型项目中,控件的id经常会重复,xml中删除了一个控件,但是对应的Activity中没有删除这个控件的相关引用,编译时并不会报错,但是运营室时会报出空指针;
- 可读性不好,简直看不懂哈。
优点:
- 兼容性好,使用所有场景
- 非常灵活,适合动态加载layout文件。
ButterKnife (框架注解)
这个Java用的多,不过我们 Kotlin 用的真的不多,而且快遗弃了,这里不展开,知道有这个就行。
1
2
3
4
5
6
7
8
9
10
11
12
13
var mTestBtn:Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ButterKnife.bind(this)
mTestBtn.setOnClickListener {
Toast.makeText(this@MainActivity,"success",Toast.LENGTH_SHORT).show()
}
}
Kotlin-android-extensions(插件)
《第一行代码》中
新版本 AS 是不会默认引入的,AS4.1之后就要自己手动加了
大声BB:但是现在你再创建Android项目,就不会自动帮你依赖了,其原因就是kotlin-android-extensions这个插件已经被废弃了。
为啥?Google这新技术迭代跟玩一样啊,有kotlin-android-extensions插件我不用,我就手写,哎,就是玩儿~
其实底层原因是这个是牺牲掉一部分内存来换取的方便,在对应用性能日益严格的今天,这种做法势必会被淘汰掉。
1 | //然后就可以直接用xml中对应的id来访问视图了 |
原理:通过反编译kotlin-android-extensions的代码,你就会发现,通过kotlin-android-extensions,它会在代码中创建一个HashMap,用来存放所有的id和对应的View的缓存,如果缓存中没有需要的View,那么就通过findViewById去创建,否则就直接获取,这就是它的原理。
三宗罪:
-
耗费内存
见原理
-
资源 ID 重名
由于kotlin-android-extensions是通过view的id名直接引用的,所以多个布局间的同名id,就需要手动对import进行重命名处理,而且经常会引用错误的布局文件,导致运行崩溃。
-
仅支持 Kotlin 代码
viewBinding
最低环境要求:
Android Studio:3.6
Android Gradle Plugin 3.6.0
系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。
在大多数情况下,视图绑定会替代findViewById
。也就是说我们通过使用ViewBinding也可以轻松的解决findViewById的繁琐使用问题,同样可以实现kotlin-android-extensions插件的类似功能。
其实同时还能在java和kotlin中同时使用,解决了kotlin-android-extensions插件使用所带来的问题。另外针对使用MVVM的朋友来说ViewBinding还可以和databinding配合使用,非常的方便。
为某个模块启用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。每个绑定类均包含对根视图以及具有 ID 的所有视图的引用。系统会通过以下方式生成绑定类的名称:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词。
比如我们的是
first_layout.xml
到时候的绑定类名就是FirstLayoutBinding
1
2
3
4
5
6 <LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>此类具有两个字段:一个是名为 name 的 TextView,另一个是名为 button 的 Button。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。
每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。
1.项目引入
app 下面的 build.gradle 的如图位置加上:
1 | viewBinding { |
注意在 android {}里面哈
2. Activity 中使用
1 | class MainActivity : AppCompatActivity() { |
3.Fragment 中使用
1 | private var _binding: ResultProfileBinding? = null |
4. Adapter
1 | class DemoAdapter(val dataList: List<String>) : RecyclerView.Adapter<DemoAdapter.ViewHolder>() { |
5.希望在生成绑定类时忽略某个布局文件
官方这样说:
如果您希望在生成绑定类时忽略某个布局文件,请将
tools:viewBindingIgnore="true"
属性添加到相应布局文件的根视图中:
1
2
3
4
5 <LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
当你这样操作了发现:
解决办法:
-
变成<tools:LinearLayout>
-
前面加上一行
xmlns:tools="http://schemas.android.com/tools"
6.总结
缺陷:
- 使用比kotlin-android-extensions复杂
- 不灵活,依然有需要手动处理的场景
- 默认给所有 xml 生成 blinding 文件,冗余
优点:
- 规避了 NPE
- 可读性好了点
- 内存好了点
- 轻量级了一点
亲戚:datablinding (有兴趣了解,xml外面套了一层layout)
其实 ViewBinding 还有封装,这部分内容比较有意思,俺能力有限就请更有能力的观众朋友们下方评论区补充。
实话实说,这个 viewBinding 的使用确实不如kotlin-android-extensions方便,但是天下大势所趋,也没有办法,随着Kotlin的升级,早晚还是要切换到ViewBinding。
欢迎大家加群技术交流/群内也有很多免费资源
参考文献:
视图绑定 | Android 开发者 | Android Developers (google.cn)
废弃的kotlin-android-extensions,是时候接受ViewBinding了 - 知乎 (zhihu.com)
Google挖坑后人埋-ViewBinding(上) - 腾讯云开发者社区-腾讯云 (tencent.com)
(13条消息) Android findViewById及替代方案大全_柚子君下的博客-CSDN博客_android findviewbyid