databinding的原理簡單分析

databinding用來實現 vm層和v層的雙向綁定關系;

主要作用

  • 取代繁瑣的findViewByid();
  • 綁定VM層和V層的關聯關系,實現雙向交互;
    雙向交互指的是:
    VM通知V做出改變,例如:修改文字顏色。
    V通知VM做出改變,例如:click事件,textChanged事件。

簡單分析

編譯時 生成新的xml布局文件

根據xml布局文件生成了 編譯后的布局文件,把所有設置id的view都添加一個 字符串tag;tag的名稱 是例如:layout/布局名字+數字

編譯時 生成對應的BindingImpl類

每個編譯后的布局文件 都生成 一個對應的BindingImpl類,持有布局文件中的view對象,id信息和設置bean對象;BindingImpl類的名稱 是例如:布局文件名稱+BindingImpl

public class ItemKDPageBindingImpl extends ItemKDPageBinding {
      static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.item_knowledge_list_detail_authorImg, 5);
     }
    //...
   public ItemKDPageBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
    }

//設置bean數據對象
    public void setKnowledgeBean(@Nullable com.brucej.module_knowledge.beans.KnowledgeBean KnowledgeBean) {
        this.mKnowledgeBean = KnowledgeBean;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.knowledgeBean);//通知 觀察者,我的屬性改變了
        super.requestRebind();//觸發 view重新賦值
    }

}

//持有view屬性,和bean數據對象;
public abstract class ItemKDPageBinding extends ViewDataBinding {
 @NonNull
  public final TextView itemKnowledgeListDetailTypeTv;
  @Bindable
  protected KnowledgeListBean.DatasBean mDataBean;
  //...
   
   protected void requestRebind() {
       //...
            if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
   }

   private final Runnable mRebindRunnable = new Runnable() {
        @Override
        public void run() {
             //...
            //最后調用
            executePendingBindings();
        }
     };

      /**
     * @hide
     */
    protected abstract void executeBindings();

}

//ViewDataBinding 
public abstract class ViewDataBinding extends BaseObservable {
    
    private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
        ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding == null) {
            //...偽代碼
            if(不是ViewGroup){
               //判斷tag,存放到bindings數組
                     bindings[id] = view;
            }else{
               //遍歷子view,判斷tag,存放到bindings數組
            }

        }
    }

}

編譯時 DataBinderMapperImpl類

生成一個DataBinderMapperImpl類,DataBinderMapperImpl類持有所有的已經生成的 BindingImpl類;
DataBindUtil持有一個 sMapper對象,這個對象就是編譯時期生成的DataBinderMapperImpl;

public class DataBinderMapperImpl extends DataBinderMapper {
    static {
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.activity_knowledge_detail, LAYOUT_ACTIVITYKNOWLEDGEDETAIL);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.fragment_kd_page, LAYOUT_FRAGMENTKDPAGE);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.fragment_knowledge, LAYOUT_FRAGMENTKNOWLEDGE);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.item_k_d_page, LAYOUT_ITEMKDPAGE);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.item_knowledge, LAYOUT_ITEMKNOWLEDGE);
  }
  
@Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYKNOWLEDGEDETAIL: {
          if ("layout/activity_knowledge_detail_0".equals(tag)) {
            return new ActivityKnowledgeDetailBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_knowledge_detail is invalid. Received: " + tag);
        }
          //...
      }
    }
    return null;
  }

}

調用setContentView或者inflate方法 實現view注入

  DataBindingUtil.setContentView(activity, layoutId);
  DataBindingUtil.inflate(inflater, layoutId, container, false);

調用setContentView或者inflate 傳入layoutId 加載布局時,調用的還是原生的加載方法,生成布局view;

//DataBindingUtil
    public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
    }
  
   public static <T extends ViewDataBinding> T inflate(
            @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
            boolean attachToParent, @Nullable DataBindingComponent bindingComponent) {
        final boolean useChildren = parent != null && attachToParent;
        final int startChildren = useChildren ? parent.getChildCount() : 0;
        final View view = inflater.inflate(layoutId, parent, attachToParent);
        if (useChildren) {
            return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
        } else {
            return bind(bindingComponent, view, layoutId);
        }
    }

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

  • 接著調用bind方法,使用DataBinderMapperImpl sMapper對象 ,根據layoutId和view的tag信息 找到 BindingImpl類,生成對象并返回;
  • 生成對象時,在BindingImpl類的構造方法中,根據傳入的view和自身的id 對其view屬性進行賦值。

調用binding對象的setXX方法 設置數據對象

見 ItemKDPageBindingImpl 的setKnowledgeBean方法。
調用binding 對象的賦值方法,
然后調用requestRebind方法然后保證切換到主線程 (使用Handler或者Choreographer.getInstance() )
最后調用executeBindings 對view進行賦值和綁定監聽。

更多

編譯時,
根據xml布局文件生成了 兩個xml文件;例如:
app/build/../layout/activity_main-layout.xml;DataBinding所需要的 bean和布局信息
app/build/../layout/activity_main.xml;  activity加載的所需要的布局文件,
    而且所有存在id的view都添加一個 字符串tag(tag的規則:layout/布局名字+數字)
根據布局文件,生成對應的BindingImpl類(類名規則:布局文件名稱+BindingImpl);
        BindingImpl類 持有布局文件中的所有view對象,持有一個map(SparseIntArray類型) 存放所有的 view的id和數字;
        BindingImpl類 存在一個set設置bean對象的方法;
        BindingImpl類 存在一個requestRebind方法,用來更新View顯示;
    同時生成一個DataBinderMapperImpl類,存放所有 生成的BindingImpl類,xxBindingImpl類;
        BindingImpl類 有一個getDataBinder(...,View view, int layoutId)   用來獲取xxBindingImpl的對象;
#
DataBindUtil類 持有一個 sMapper對象,這個對象就是編譯時生成的DataBinderMapperImpl 對象;
    調用setContentView或者inflate 傳入layoutId 加載布局時,調用的還是原始的加載方法,生成布局view;
        接著調用bind方法,調用sMapper對象的getDataBinder(...,View view, int layoutId)方法 獲取一個BindingImpl對象;
            根據layoutId,調用構造方法 xxBindingImpl(...,View root) ,生成并返回一個 xxBindingImpl對象;
                 xxBindingImpl的構造方法中,會根據root對象,獲取其子view;
                 xxBindingImpl的構造方法中,調用invalidateAll() -> requestRebind()
                     最終保證切換到主線程 (Handler或者下一幀回調 Choreographer.getInstance()),
                    調用executeBindings 對view進行賦值
補充:
如果Bean類的 set方法中,調用了notifyPropertyChanged()方法,
    那么 在生成的xxBindingImpl類中的 executeBindings中,
    會創建觀察者監聽 bean對象或者bean的屬性 的變化,然后更新相應的view對象;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容