最近断断续续地把项目的界面部分的代码由 JAva 改成了 Kotlin 编写, 并且如果应用了 kotlin-Android-extensions 插件, 一个显而易见的好处是再也不用写 findViewById() 来实例化你的控件对象了, 直接操作你在布局文件里的 id 即可, 这一点我感觉比 https://github.com/JakeWharton/butterknife 做的还简洁友好.
- Activity
- import Android.support.v7.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)
- textview.text="hello world"
- }
- }
其中 kotlinx.Android.synthetic.main.activity_main.* 是 kotlin-Android-extensions 插件自动生成的. 下面我们来解析下原理. 因为 kotlin 也是一门 JVM 语言, 最近也会和 java 一样编译成 class 字节码, 所以我们直接来反编译看看生成的 java 文件.
选择 Decompile, 解析出来的代码如下
- public final class MainActivity extends AppCompatActivity {
- private HashMap _$_findViewCache;
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- this.setContentView(2131296284);
- TextView var10000 = (TextView)this._$_findCachedViewById(id.textview);
- Intrinsics.checkExpressionValueIsNotNull(var10000, "textview");
- var10000.setText((CharSequence)"hello world");
- }
- 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(), 不过获取 View 对象直接调用的是 findCachedViewById, 并且创建一个 HashMap 进行 View 对象的缓存, 避免每次调用 View 时都会重新调用 findViewById() 进行查找.
Fragment
再来看下 Fragment 中的使用:
- import Android.os.Bundle
- import Android.support.v4.App.Fragment
- import Android.view.LayoutInflater
- import Android.view.View
- import Android.view.ViewGroup
- import kotlinx.Android.synthetic.main.fragment_blank.*
- class BlankFragment : Fragment() {
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.fragment_blank, container, false)
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- textview_fra.text="hello world"
- }
- }
反编译后代码如下
- public final class BlankFragment extends Fragment {
- private HashMap _$_findViewCache;
- @Nullable
- public View onCreateView(@NotNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- Intrinsics.checkParameterIsNotNull(inflater, "inflater");
- return inflater.inflate(2131296285, container, false);
- }
- public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {
- Intrinsics.checkParameterIsNotNull(view, "view");
- super.onViewCreated(view, savedInstanceState);
- TextView var10000 = (TextView)this._$_findCachedViewById(id.textview_fra);
- Intrinsics.checkExpressionValueIsNotNull(var10000, "textview_fra");
- var10000.setText((CharSequence)"hello world");
- }
- public View _$_findCachedViewById(int var1) {
- if (this._$_findViewCache == null) {
- this._$_findViewCache = new HashMap();
- }
- View var2 = (View)this._$_findViewCache.get(var1);
- if (var2 == null) {
- View var10000 = this.getView();
- if (var10000 == null) {
- return null;
- }
- var2 = var10000.findViewById(var1);
- this._$_findViewCache.put(var1, var2);
- }
- return var2;
- }
- public void _$_clearFindViewByIdCache() {
- if (this._$_findViewCache != null) {
- this._$_findViewCache.clear();
- }
- }
- // $FF: synthetic method
- public void onDestroyView() {
- super.onDestroyView();
- this._$_clearFindViewByIdCache();
- }
- }
可以看到最终是通过调用 getView().findViewById() 来进行控件的实例化. 看下 getView() 源码
- @Nullable
- public View getView() {
- return this.mView;
- }
再看下 mView 成员变量的赋值时机:
- void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- if (this.mChildFragmentManager != null) {
- this.mChildFragmentManager.noteStateNotSaved();
- }
- this.mPerformedCreateView = true;
- this.mViewLifecycleOwner = new LifecycleOwner() {
- public Lifecycle getLifecycle() {
- if (Fragment.this.mViewLifecycleRegistry == null) {
- Fragment.this.mViewLifecycleRegistry = new LifecycleRegistry(Fragment.this.mViewLifecycleOwner);
- }
- return Fragment.this.mViewLifecycleRegistry;
- }
- };
- this.mViewLifecycleRegistry = null;
- this.mView = this.onCreateView(inflater, container, savedInstanceState);
- if (this.mView != null) {
- this.mViewLifecycleOwner.getLifecycle();
- this.mViewLifecycleOwnerLiveData.setValue(this.mViewLifecycleOwner);
- } else {
- if (this.mViewLifecycleRegistry != null) {
- throw new IllegalStateException("Called getViewLifecycleOwner() but onCreateView() returned null");
- }
- this.mViewLifecycleOwner = null;
- }
- }
可以看到 mView 其实就是 onCreateView() 的返回值, 所以我们不能在 onCreateView() 方法里操作控件 ID 的方式操作 View 对象, 会产生空指针异常. 建议在 onViewCreated() 方法里使用.
其他
比较遗憾的是 kotlin-Android-extensions 貌似还不支持各种 Adapter 等动态布局内的直接使用控件 ID 方法. 这一点就不如 butterknife 可以在 Adapter 中手动注入父 view 来的灵活了.
来源: https://juejin.im/post/5c161975f265da612b137e83