当前位置: 首页 > news >正文

Android开发获取视图组件的findViewById,kotlin-android-extensions,ViewBinding三种详解

前置条件:在一个activity的布局文件中,我们通过设置一个按钮,分别通过三种方式获取,并说明各自优缺点

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> //新增的Button元素按钮<Button android:id="@+id/button1" //定义idandroid:layout_width="match_parent" //宽度和父元素一样宽android:layout_height="wrap_content" //高度自适应android:text="Button 1" /> </LinearLayout>

1.findViewById详解

        安卓开发中,activity获取xml文件中控件,最开始的方法是findViewById,那么它是如何去实现查找并绑定视图ID的。下面我们详细介绍

首先,findViewById的使用方法:

//Java代码
public class MainActivity extends AppCompatActivity {private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 手动查找并转换类型textView = findViewById(R.id.textView);textView.setText("Hello Java!");}
}
//kotlin代码
class MainActivity : AppCompatActivity() {private lateinit var textView: TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 手动查找并转换类型textView = findViewById(R.id.textView)textView.text = "Hello Kotlin!"}
}

那么其内部是如何实现

        手机布局其实就像一个一个树状图,

  • 树根:整个屏幕的最外层布局(如 DecorView
  • 树枝:有子布局的容器(如 LinearLayoutRelativeLayout 等 ViewGroup
  • 树叶:没有子布局的单个控件(如 TextViewButton 等 View

        每个节点(View/ViewGroup)都有一个唯一的 ID。

总结查找过程:其就是先从树根(屏幕布局DecorView)开始查找,然后遍历树枝,检查当前树枝节点ID是否匹配,如果匹配直接返回,如果当前布局不匹配且含有子布局(即是ViewGroup),则递归检查其所有的子布局。如果没有子布局,则对比ID,匹配返回,不匹配则返回null或检查下一个。

核心方法:ViewGroup中的findViewTraversal()方法,是通过递归的方式从树根部,再到树枝-再到树叶,逐个查找,直到找到为止。

全局查找,从 DecorView 开始遍历整个视图树,时间复杂度为 O(n)(n 为视图总数)。

注意缺陷

  • 代码冗余:每个控件都需手动调用 findViewById
  • 类型不安全:若类型转换错误(如将 TextView 转为 Button),运行时会抛出 ClassCastException
  • 空指针风险:若 ID 不存在,返回 null(Java)或 NullPointerException(Kotlin)。
  • 性能开销:每次访问控件都需遍历视图树,影响性能。

实现代码:

// ViewGroup.java
@Override
protected View findViewTraversal(int id) {// 先检查自身if (id == mID) {return this;}// 遍历子 Viewfinal View[] children = mChildren;for (int i = 0; i < childrenCount; i++) {View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {// 递归查找子 ViewView v = child.findViewTraversal(id);if (v != null) {return v; // 找到即返回,终止递归}}}return null; // 未找到
}// View.java
public View findViewById(int id) {if (id == mID) {return this;}return null; // View 没有子 View,直接返回
}

2.kotlin-android-extensions详解(注意,已废弃)

Kotlin Android Extensions 于 2016 年随 Kotlin 1.0 版本推出,作为官方解决方案,旨在提供更简洁、高效的视图绑定方式。

其核心功能:Kotlin Android Extensions 的核心是 合成属性(Synthetic Properties),它允许开发者直接通过布局文件中的 ID 访问视图,无需显式绑定:

1.使用方法:

        添加依赖:

plugins {id 'kotlin-android-extensions'
}

        直接引入空间ID

//kotlin代码
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 直接使用布局文件中的 IDtextView.text = "Hello Extensions!"// 设置点击事件button.setOnClickListener { /* ... */ }}
}

那么它是如何实现的呢?下面我们详细介绍

详解:这个插件呢其实是一个编译时插件,它的工作流程如下:

        

  1. 编译时处理:在编译阶段,插件分析布局文件(.xml),生成对应的访问器代码。
  2. 合成属性生成:为每个布局文件生成一个 Kotlin 文件(如 R.layout.activity_main 对应生成 ActivityMainKt),其中包含布局中所有视图 ID 的属性访问器。
  3. 运行时优化:访问合成属性时,插件会缓存首次查找的视图实例,避免重复调用 findViewById,提升性能。

整体来说,其不仅简化代码,高效快捷,合成属性的类型由视图文件自动推断还能保证类型安全,那么为什么废弃呢。主要是其缺陷

  • 性能隐患:虽然缓存机制避免了重复 findViewById,但首次访问仍需反射查找,在频繁创建视图的场景下(如 RecyclerView)可能影响性能。
  • 布局依赖:合成属性与布局文件强绑定,若布局文件修改但代码未同步更新,可能导致运行时异常。
  • IDE 支持有限:在复杂场景下(如动态加载布局),IDE 可能无法正确提示合成属性。

3.ViewBinding详解

Android 团队于 2019 年 Android Gradle Plugin 3.6 版本引入了 View Binding。主要是这些方案均存在不同程度的缺陷,尤其是在大型项目中维护成本高。为解决这些问题,引入ViewBinding

使用方法:

1.在 build.gradle 中启用 View Binding

android {viewBinding {enabled = true}
}

若需排除特定布局文件,可在布局文件中添加 tools:viewBindingIgnore="true"

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"tools:viewBindingIgnore="true"><!-- ... -->
</LinearLayout>

2.在Activity中使用

class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.button.setOnClickListener { /* ... */ }}
}

3.在fragment中使用

class MyFragment : Fragment(R.layout.fragment_my) {private var _binding: FragmentMyBinding? = nullprivate val binding get() = _binding!!override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)_binding = FragmentMyBinding.bind(view)binding.textView.text = "Hello from Fragment"}override fun onDestroyView() {super.onDestroyView()_binding = null // 防止内存泄漏}
}

4.RecyclerView Adapter使用

class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {class ViewHolder(private val binding: ItemMyBinding) : RecyclerView.ViewHolder(binding.root) {fun bind(item: MyItem) {binding.textView.text = item.name}}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val binding = ItemMyBinding.inflate(LayoutInflater.from(parent.context), parent, false)return ViewHolder(binding)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.bind(items[position])}
}

在设计ViewBinding时,它的初衷就是

  • 类型安全:编译时验证视图 ID 是否存在,避免运行时 NullPointerException
  • 性能优化:直接访问视图引用,无需反射或缓存,提升运行效率。
  • 零配置:无需编写注解或额外代码,自动生成绑定类。
  • 兼容性:支持与 Data Binding、Kotlin Android Extensions 共存。

那么其工作原理:通过 编译时生成绑定类 实现视图访问。对于每个布局文件(如 activity_main.xml),系统会自动生成对应的绑定类(如 ActivityMainBinding)。该类包含:布局中所有具有 ID 的视图的直接引用。根视图的引用。创建绑定实例的静态方法

与 Kotlin Android Extensions 的对比

特性Kotlin Android ExtensionsView Binding
类型安全部分支持(布局修改可能导致运行时崩溃)完全支持(编译时验证)
性能首次访问需反射,后续缓存直接访问,无反射
布局依赖强依赖(布局修改需同步代码)弱依赖(绑定类自动更新)
空安全不支持(可能返回 null)支持(非空类型或可空类型)
IDE 支持有限(动态布局提示不足)完善(自动补全、类型提示)
数据绑定支持不支持支持(与 Data Binding 集成)
Fragment/RecyclerView需要额外处理生命周期内置生命周期管理

相关文章:

  • PyWavelets
  • 分布式系统ID生成方案深度解析:雪花算法 vs UUID vs 其他主流方案
  • 航天VR赋能,无人机总测实验舱开启高效新篇​
  • 鸿蒙OS开发IoT控制应用:从入门到实践
  • 基于JavaWeb的校园失物招领系统设计与实现
  • 机器学习2——贝叶斯理论下
  • 概述-2-MySQL安装及启动-1-Dcoker安装MySQL
  • 那些不应该的优化
  • Hall 定理 学习笔记
  • 【Redis】解码Redis中的list类型,基本命令,内部编码方式以及适用的场景
  • Ai大模型 - ocr图像识别形成结构化数据(pp-ocr+nlp结合) 以及训练微调实现方案(初稿)
  • Prompt Engineering For LLMs
  • 【Linux基础知识系列】第三十二篇 - Shell 历史与命令编辑
  • eSearch识屏 · 搜索 v15.0.1 官方版
  • 使用 Vcpkg 安装 Qt 时的常见问题与解决方法
  • 【论文阅读】Video-R1: Reinforcing Video Reasoning in MLLMs
  • 安卓端某音乐类 APP 逆向分享(四)NMDI参数分析
  • 智能体记忆原理-prompt设计
  • swagger访问不了的解决方案 http://localhost:8080/swagger-ui/index.html
  • .NetCore+Vue快速生产框架开发详细方案