引子
使用Kotlin引用控件的时候,不用写findViewById,直接用id即可,比如
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_demo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_demo.text = "123"
}
}
这样的代码异常清爽优雅,不用声明,不用findviewById.不过kotlin是怎么做到的呢,下面就一窥究竟
原理
最好的办法就是查看kt文件的字节码,然后再反编译下,android studio 有个功能能实现这个过程。
然后再点击Decompile 按钮,就完成了反编译,完整的代码如下
package com.example.myapplication;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.example.myapplication.R.id;
import java.util.HashMap;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.Nullable;
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001a\u00020\u00042\b\u0010\u0005\u001a\u0004\u0018\u00010\u0006H\u0014¨\u0006\u0007"},
d2 = {"Lcom/example/myapplication/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_debug"}
)
public final class MainActivity extends AppCompatActivity {
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(1300009);
TextView var10000 = (TextView)this._$_findCachedViewById(id.tv_demo);
Intrinsics.checkExpressionValueIsNotNull(var10000, "tv_demo");
var10000.setText((CharSequence)"123");
}
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(var1, var2);
}
return var2;
}
public void _$_clearFindViewByIdCache() {
if (this._$_findViewCache != null) {
this._$_findViewCache.clear();
}
}
}
相信看到这里大家就恍然大悟了,原来最后还是得通过findViewById来寻找空间,把这个过程封装一下,也就是findCachedViewById()方法的逻辑,然后实际的对象并不是tv_demo,而是var1000,用HashMap存储,如果其他地方也用tv_demo.xxxx()调用,tv_demo的控件实例对象是从_$_findViewCache获取的.反编译代码也写的比较鲁棒,可以看到这句代码 Intrinsics.checkExpressionValueIsNotNull(var10000, “tv_demo”);就是为了防止控件实例是空的情况,如果var10000是null的情况下,则直接抛出异常,一般情况下不会出现null.
总结
对使用了HashMap作为控件对象的存储器,表示有点遗憾,因为key正好是int,非常适合SparseArray,后面kotlin的版本也许更新这个问题.不过瑕不掩瑜,kotlin的这个操作让我有种解放思想的感觉,在原生时代,避免findViewById的方法差不多都会用Butterknife等注解框架,可是免去了调用findViewById,却要加注解,注解多了,也显的不优雅,变成了另外一种重复代码(样本代码).通过这个功能,我个人的想法kt文件只是一套规则,在这个规则之下看起来突破了以前java语法的束缚,比如tv_demo根本没有声明就能使用,但编译器编译的后的代码还是得遵循java 规范.kt文件生成的class文件还得运行在java虚拟机.目前短期内不会有变化.所以说,最好自己对kotlin的语法糖进行反编译看下,在java代码层是怎么实现的,知其然,知其所以然.