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

发布于:2025-06-29 ⋅ 阅读:(15) ⋅ 点赞:(0)

前置条件:在一个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" //定义id
        android: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;
    
    @Override
    protected 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: TextView
    
    override 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;
    }
    
    // 遍历子 View
    final View[] children = mChildren;
    for (int i = 0; i < childrenCount; i++) {
        View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            // 递归查找子 View
            View 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)
        
        // 直接使用布局文件中的 ID
        textView.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"

<LinearLayout
    xmlns: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: ActivityMainBinding

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

网站公告

今日签到

点亮在社区的每一天
去签到