Kotlin 不再使用 findViewById 的原理

时间:2024-03-26 22:52:48

在Kotlin中,一个非常好用的特性是:可以直接使用控件 ID 对控件进行操作,而不需要像 Java 中那样先声明控件,使用 findViewById() 来找到控件,然后才能操作该控件。该特性称为Static Layout Import,即静态布局引入。

举个栗子, activity_main.xml中有个TextView,其 ID 为 tv_name 的,将布局像下面这样引入进来后: 
Kotlin 不再使用 findViewById 的原理 
就可以直接使用 tv_name: 
Kotlin 不再使用 findViewById 的原理

可以看到,利用tv_name可以直接使用该 TextView 的 text、textSize 等属性或方法,甚至比著名的开源库 ButterKnife 还简洁。 
ps:text / textSize 等属性其实是 Kotlin 扩展属性,反编译查看底层的 Java 代码可以发现其实还是使用其对应的 setter 方法。

那么为什么可以直接使用控件 ID 来操作控件呢?我们先将 Kotlin 转为 Java 代码。在 Android Studio 中,点击最顶部的 Tools -> Kotlin ,然后选择 Show Kotlin Bytecode,可以在右侧面板中看到对应的字节码,然后点击 Decompile ,就可以查看 Kotlin代码对应的 Java 代码: 
Kotlin 不再使用 findViewById 的原理

可以发现,tv_name 部分的代码对应的 Java代码如下: 
Kotlin 不再使用 findViewById 的原理 
反编译后可知,这种用法的原理是 Kotlin 会自动生成类似 findViewById() 的方法:findCachedViewById(),在这个方法里面创建一个 HashMap 缓存每次查找到的 View,避免每次调用 View 的属性或方法时都会重新调用findCachedViewById()进行查找。具体查找流程是这样的:在findCachedViewById()中,会先通过缓存 HashMap 的 get 方法来获取控件, get() 中传入的 key 即控件 ID,由于第一次 get 的值为 null ,因此会调用findViewById() ,并把控件 ID作为 key 和 找到的控件 View 作为 value put 进缓存 Map 中,这样,第二次再使用该控件 ID 的时候,就直接可以从 Map 中获取到了。还是挺好理解的吧。

以上是在 activity 里面直接使用控件 ID,但是在 fragment 里面使用要注意的是,不能在onCreateView方法里用 view 的 ID,而是在 onViewCreated以后使用,不然可能会由于找不到控件而出现空指针异常的问题。正确的用法是这样的: 
Kotlin 不再使用 findViewById 的原理

再将上述 Kotlin 代码转化为对应的 Java 代码: 
Kotlin 不再使用 findViewById 的原理 
可以看到, fragment 里面跟前面的基本原理类似,同样也是在findCachedViewById()中创建缓存 Map,区别在于 fragment 里面是通过getView()来 findViewById()的,如果是在onCreateView方法里使用控件 ID,这个时候getView()会返回 null,即 var10000null,这样findCachedViewById()就返回空了。

因此,千万要注意 fragment 里面不能在onCreateView方法里用 view 的 ID。

好了,Kotlin 中不再使用 findViewById、而是直接使用控件 ID 来操作控件 的原理就说到这里。