Android 从观察者模式到DataBinding (下篇)

达芬奇密码2018-06-20 12:51

数据和事件关联原理

知道了 DataBinding 如何使用之后,就很好奇,这个DataBinding 机制是如何实现的了。但是在 Android Studio 里面怎么也没有找到 ActivityMainBinding 的代码,在 ObservableUsergetLastName 方法中设置断点,查看调用栈。我们可以发现,其实是 ActivityMainBinding.executeBindings 方法调用了 model 的 get 方法。

然而,悲剧的是,我想点击查看 executeBindings 调用情况,Android Studio 是跳到了 activity_main.xml 里去了。

好吧,还是想看源码,那就用 dex2jarjd-gui 工具查看了 class.dex 文件,果然看到了源码

当然,其他 Android Studio 上没能看到的代码,如 DataBinderMapper 等的代码也都能找到了。

直接查看 DataBindingUtil.setContentView 里面的源码吧:

  • MainActivity.onCreate

     ActivityMainBinding binding = 
             DataBindingUtil.setContentView(this, R.layout.activity_main);
    
  • 省略部分代码,直接来到 DataBindingUtil.bind

     static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent,
             View root, int layoutId) {
         return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
     }
    

    其中,bindingComponentnullrootlayoutId 都对应 activity_main.xml 中定义的 LinearLayout

  • DataBinderMapper.getDataBinder

     public ViewDataBinding getDataBinder(DataBindingComponent paramDataBindingComponent,
         View paramView, int paramInt) {
         switch (paramInt) {
             default:
               return null;
             case 2130968601:
         }
         return ActivityMainBinding.bind(paramView, paramDataBindingComponent);
     }
    
  • ActivityMainBinding.bind

     public static ActivityMainBinding bind(View paramView, DataBindingComponent paramDataBindingComponent) {
         if (!"layout/activity_main_0".equals(paramView.getTag()))
             throw new RuntimeException("view tag isn't correct on view:" + paramView.getTag());
         return new ActivityMainBinding(paramDataBindingComponent, paramView);
     }
    

    这里可以看到,返回的 ActivityMainBinding 对象是在这里被创建的了

  • ActivityMainBinding 构造函数

     public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) {
         super(paramDataBindingComponent, paramView, 1);
         paramDataBindingComponent = mapBindings(paramDataBindingComponent, paramView, 2, sIncludes, sViewsWithIds);
         this.mboundView0 = ((LinearLayout)paramDataBindingComponent[0]);
         this.mboundView0.setTag(null);
         this.mboundView1 = ((Button)paramDataBindingComponent[1]);
         this.mboundView1.setTag(null);
         setRootTag(paramView);
         invalidateAll();
     }
    

    这里还有好多疑问,mboundView0 和 mboundView1 分别对应什么控件呢?setRootTag 和 invalidateAll 都是干啥的呢?

  • ViewDataBinding.mapBindings

     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
             int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
         Object[] bindings = new Object[numBindings];
         mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
         return bindings;
     }
    
     private static void mapBindings(DataBindingComponent bindingComponent, View view,
             Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
             boolean isRoot) {
         ......
         if (view instanceof ViewGroup) {
             ......
             for (int i = 0; i < count; i++) {
                 ......
                 if (bindings[index] == null) {
                     bindings[index] = view;
                 }
                 ......
                 {    
                     bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                         layoutId);
                 }
                 ......
    
                 if (!isInclude) {
                     mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                 }
             }
         }
     }
    

    注:这里会递归的执行 mapBindings 将传入的 bindings 数据给填充好。 binding 数组里面的数据,可能是 view 也可能是 ViewDataBinding 在当期的示例程序中,bindings[0]LinearLayoutbindings[1]Button;所以,ActivityMainBinding.mboundView0 就是 layout 中定义的 LinearLayout;ActivityMainBinding.mboundView1 就是 layout 中定义的 Button。

  • ViewDataBinding.setRootTag

     protected void setRootTag(View view) {
         ......
         view.setTag(R.id.dataBinding, this);
         ......
     }
    

    ActivityMainBinding 和布局文件中的 LinearLayout 关联起来了。

  • ActivityMainBinding 构造函数

     public ActivityMainBinding(DataBindingComponent paramDataBindingComponent, View paramView) {
         ......
         invalidateAll();
     }
    
  • 跳过部分代码,调用到 ActivityMainBinding.requestRebind

     protected void requestRebind() {
         ......
         if (SDK_INT >= 16) {
             mChoreographer.postFrameCallback(mFrameCallback);
         } else {
             mUIThreadHandler.post(mRebindRunnable);
         }
     }
    

    假设当前 SDK_INT == 23,直接查看 mFrameCallback 的定义。则在下一帧的时候,调用 mRebindRunnable.run();

     mFrameCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             mRebindRunnable.run();
         }
     };
    
  • 最终执行 ActivityMainBinding.executeBindings 方法

    protected void executeBindings()
    {
        ......    
    
        Handler localHandler = this.mHandler;
        ObservableUser localObservableUser = this.mUser;
    
        ......
        while (true)
        {
            ......
    
            localObject1 = new OnClickListenerImpl();
            this.mAndroidViewViewOnCl = ((OnClickListenerImpl)localObject1);
            localObject1 = ((OnClickListenerImpl)localObject1).setValue(localHandler);
    
            localObject3 = localObject4;
    
            ......
    
            localObject3 = localObservableUser.getLastName();
            TextViewBindingAdapter.setText(this.mboundView1, (CharSequence)localObject3);
    
            this.mboundView1.setOnClickListener((View.OnClickListener)localObject1);
            return;
            ......
        }
    }
    

    注:这里 mUsermHandler 是 MainActivity.onCreate 中的设置的:

    binding.setUser(user);
    binding.setHandler(handler);
    

    这里清楚的看到调用 localObservableUser.getLastName 获取 model 中的数据,然后设置给 mboundView1 (Button)

    新建 OnClickListenerImpl 对象,处理 mboundView1 (Button)的点击事件,而最终也还是会调用到 Handler.onClickButton 方法上

    public static class OnClickListenerImpl implements View.OnClickListener {
        private Handler value;
    
        public void onClick(View paramView) {
            this.value.onClickButton(paramView);
        }
    
        ......
    }
    

数据变化驱动视图改变


查看下代码,set 函数中,需要添加一句 notifyPropertyChanged 方法。其实这里对 lastName 的监听者,就是 ViewDataBinding$WeakPropertyListener,而内部调用的还是 AcitivityMainBinding.handleFieldChange 方法,最终还是调用了 AcitivityMainBinding.requestRebind。这里就已经和前面分析的过程一样,也就是说最终视图发生改变生效,走的还是消息队列。


public class ObservableUser extends BaseObservable {

    ......

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(com.netease.mvvmsample.BR.lastName);
    }
}
private static class WeakPropertyListener
        extends Observable.OnPropertyChangedCallback
        implements ObservableReference<Observable> {
    final WeakListener<Observable> mListener;

    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        ViewDataBinding binder = mListener.getBinder();
        ......
        binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
    }
}

小结


由上面的源码解析,已经知道几点


  1. 数据如何和 view 关联起来的

  2. 事件处理如何和 view 关联起来的

  3. 数据和事件处理的关联发生是扔给消息队列处理的

    • SDK_INT >= 16: mChoreographer.postFrameCallback(mFrameCallback);
    • SDK_INT < 16: mUIThreadHandler.post(mRebindRunnable);
  4. 当数据改变,通知视图改变时,走的是消息队列。因此一次数据改动,并界面可能不会立马生效

  5. 数据和视图的绑定,其实是单向的,即数据发生改变通知了视图,而视图发生并不能自动通知数据

  6. 虽然没看到 Android Studio 是如何实现代码生成,但相关的工具大家可以看下 javapoet


总结


有了 DataBinding,后面就有人玩出了 MVVM 模式了。当然啦,这里主要是抱着学习的态度在阐述 Android 里面的 DataBinding,并不是在推崇 DataBindingMVVM。这些概念有人推崇有人贬低,引用别人的一句话,希望大家对新知识都能做到:


我们需要保持的是一个拥抱变化的心,以及理性分析的态度。
在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化

本文来自网易实践者社区,经作者张云龙授权发布。