DataBinding原理分析

2019-10-16  本文已影响0人  vpractical

[TOC]

结论放在前面:
整个过程就是将xml拆成2个xml,然后读取xml中带tag控件在ActivityMainBindingImpl创建时制作控件副本,User属性改变时notify了,会触发监听器重新获取属性值,前直接给副本设置属性,页面刷新

1.简单Demo(GitHub)

定义了一个user实体,不停变化user的属性和显示的内容

1.1 MainActivity

public class MainActivity extends AppCompatActivity {
    public static List<User> list = new ArrayList<>();
    static {
        list.add(new User("赛利亚",25,"https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1911428589,1373321293&fm=26&gp=0.jpg"));
        list.add(new User("云幂",23,"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3231129193,917533735&fm=26&gp=0.jpg"));
        list.add(new User("莎兰",28,"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=653736840,2449474724&fm=26&gp=0.jpg"));
    }
    int index = 0;
    User user = new User();
    Handler handler = new Handler();
    Runnable r = new Runnable() {
        @Override
        public void run() {
            User u = list.get(index++);
            user.setNick(u.getNick());
            user.setAge(u.getAge());
            user.setAvatar(u.getAvatar());
            if(index >= list.size()){
                index = 0;
            }
            handler.postDelayed(r,6 * 1000);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        r.run();
        ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        mainBinding.setUser(user);
        mainBinding.btnSec.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handler.removeCallbacks(r);
                r.run();
            }
        });
    }

}

1.2User

public class User extends BaseObservable {
    private String nick;
    private int age;
    private String avatar;
    public User(){}
    public User(String nick, int age, String avatar) {
        this.nick = nick;
        this.age = age;
        this.avatar = avatar;
    }
    @Bindable
    public String getNick() {
        return nick;
    }
    public void setNick(String nick) {
        this.nick = nick;
        notifyPropertyChanged(BR.nick);
    }
    @Bindable
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        notifyPropertyChanged(BR.age);
    }
    @Bindable
    public String getAvatar() {
        return avatar;
    }
    public void setAvatar(String avatar) {
        this.avatar = avatar;
        notifyPropertyChanged(BR.avatar);
    }
    @BindingAdapter("avatar")
    public static void getAvatar(ImageView iv,String url){
        Picasso.with(iv.getContext()).load(url).into(iv);
    }
    public void changeUser(View view){
        setAge(++age);
    }
}

1.3布局文件activity-main

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >
    <data>
        <variable
            name="user"
            type="com.y.mj.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal"
        >
        <Button
            android:id="@+id/btnSec"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="二级页面"
            />
        <Button
            android:id="@+id/btnUser"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="改变User"
            android:onClick="@{user.changeUser}"
            />
        <ImageView
            android:layout_marginTop="30dp"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:scaleType="centerCrop"
            app:avatar="@{user.avatar}"
            />

        <TextView
            android:layout_marginTop="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.nick + `   ` + user.age}"
            />
    </LinearLayout>
</layout>

2.源码分析

2.1布局文件

databinding是用layout标签,将数据data和原本的layout包起来,编译时会拆分成2个,以activity_main为例:

先看后者,几乎是原layout布局,在跟布局和使用了databinding代码的控件里都增加了一个tag属性

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center_horizontal"
         android:tag="layout/activity_main_0" 
xmlns:android="http://schemas.android.com/apk/res/android" 
xmlns:app="http://schemas.android.com/apk/res-auto">

        <Button
            android:id="@+id/btnSec"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="二级页面"
            />
        <Button
            android:id="@+id/btnUser"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:text="改变User"
            android:tag="binding_1"             
            />

        <ImageView
            android:layout_marginTop="30dp"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:scaleType="centerCrop"
            android:tag="binding_2"    
            />

        <TextView
            android:layout_marginTop="10dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_3"                       
            />
    </LinearLayout>

再看前者,Layout+Variables+Target记录了所有databinding代码

<?xml version="1.0" encoding="utf-8"?>

<Layout layout="activity_main" modulePackage="com.y.mj" absoluteFilePath="E:\GitHub\MVVMjetpack\app\src\main\res\layout\activity_main.xml" directory="layout" isMerge="false">
    <Variables declared="true" type="com.y.mj.User" name="user">
        <location startLine="7" startOffset="8" endLine="9" endOffset="34"/>
    </Variables>
    <Targets>
        <Target tag="layout/activity_main_0" view="LinearLayout">
            <Expressions/>
            <location startLine="12" startOffset="4" endLine="47" endOffset="18"/>
        </Target>
        <Target id="@+id/btnUser" tag="binding_1" view="Button">
            <Expressions>
                <Expression text="user.changeUser" attribute="android:onClick">
                    <Location startLine="30" startOffset="12" endLine="30" endOffset="47"/>
                    <TwoWay>false</TwoWay>
                    <ValueLocation startLine="30" startOffset="31" endLine="30" endOffset="45"/>
                </Expression>
            </Expressions>
            <location startLine="25" startOffset="8" endLine="31" endOffset="13"/>
        </Target>
        <Target tag="binding_2" view="ImageView">
            <Expressions>
                <Expression text="user.avatar" attribute="app:avatar">
                    <Location startLine="38" startOffset="12" endLine="38" endOffset="38"/>
                    <TwoWay>false</TwoWay>
                    <ValueLocation startLine="38" startOffset="26" endLine="38" endOffset="36"/>
                </Expression>
            </Expressions>
            <location startLine="33" startOffset="8" endLine="39" endOffset="13"/>
        </Target>
        <Target tag="binding_3" view="TextView">
            <Expressions>
                <Expression text="user.nick + `   ` + user.age" attribute="android:text">
                    <Location startLine="45" startOffset="12" endLine="45" endOffset="57"/>
                    <TwoWay>false</TwoWay>
                    <ValueLocation startLine="45" startOffset="28" endLine="45" endOffset="55"/>
                </Expression>
            </Expressions>
            <location startLine="41" startOffset="8" endLine="46" endOffset="13"/>
        </Target>
        <Target id="@+id/btnSec" view="Button">
            <Expressions/>
            <location startLine="19" startOffset="8" endLine="24" endOffset="13"/>
        </Target>
    </Targets>
</Layout>

2.2User

image.png

User中使用的BR.attr,BR是编译器生成的类似R文件的一个东西,属性名作为id名

public class BR {
  public static final int _all = 0;
  public static final int nick = 1;
  public static final int avatar = 2;
  public static final int user = 3;
  public static final int age = 4;
}

2.3MainActivity

    ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
    mainBinding.setUser(user);

从DataBindingUtil的setContentView方法一直找进去,会看到

    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);
    }

在这里调用了activity的setContentView,然后调用

private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        final int endChildren = parent.getChildCount();
        final int childrenAdded = endChildren - startChildren;
        if (childrenAdded == 1) {
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            final View[] children = new View[childrenAdded];
            for (int i = 0; i < childrenAdded; i++) {
                children[i] = parent.getChildAt(i + startChildren);
            }
            return bind(component, children, layoutId);
        }
    }
...
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
...
private static DataBinderMapper sMapper = new DataBinderMapperImpl();

然后从DataBinderMapperImpl找getDataBinder方法

  @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_ACTIVITYMAIN: {
          if ("layout/activity_main_0".equals(tag)) {
            return new ActivityMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

if里layout/activity_main_0这个标签看起来眼熟吧,就在拆分后的2个xml中后者根布局的tag属性,找到这个,MainActivity就得到它的ActivityMainBinding对象。

public class ActivityMainBindingImpl extends ActivityMainBinding  {

   @Nullable
   private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
   @Nullable
   private static final android.util.SparseIntArray sViewsWithIds;
   static {
       sIncludes = null;
       sViewsWithIds = new android.util.SparseIntArray();
       sViewsWithIds.put(R.id.btnSec, 4);
   }
   @NonNull
   private final android.widget.LinearLayout mboundView0;
   @NonNull
   private final android.widget.ImageView mboundView2;
   @NonNull
   private final android.widget.TextView mboundView3;
   private OnClickListenerImpl mUserChangeUserAndroidViewViewOnClickListener;
   private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
       this.btnUser.setTag(null);
       this.mboundView0 = (android.widget.LinearLayout) bindings[0];
       this.mboundView0.setTag(null);
       this.mboundView2 = (android.widget.ImageView) bindings[2];
       this.mboundView2.setTag(null);
       this.mboundView3 = (android.widget.TextView) bindings[3];
       this.mboundView3.setTag(null);
       setRootTag(root);
       // listeners
       invalidateAll();
   }
...

ActivityMainBindingImpl 中,制作了每个tag控件的副本,并移除了控件的tag属性,这里也是mvvm非常耗内存的原因

ActivityMainBindingImpl 继承自ViewDataBinding,这里面有个static代码块

    static {
        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
            ROOT_REATTACHED_LISTENER = null;
        } else {
            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
                @TargetApi(VERSION_CODES.KITKAT)
                @Override
                public void onViewAttachedToWindow(View v) {
                    // execute the pending bindings.
                    final ViewDataBinding binding = getBinding(v);
                    binding.mRebindRunnable.run();
                    v.removeOnAttachStateChangeListener(this);
                }
                @Override
                public void onViewDetachedFromWindow(View v) {
                }
            };
        }
    }

这里生成了一个监听器ROOT_REATTACHED_LISTENER,在User中setNick()时调用的notifyPropertyChanged(BR.nick)会触发这个监听器,从runnable方法找到executePendingBindings(),然后一路找到ActivityMainBindingImpl的executeBindings()。
这里获取到notify后改变的User中的属性值设置给控件的副本

@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userAvatar = null;
        java.lang.String userNickJavaLangString = null;
        int userAge = 0;
        com.y.mj.User user = mUser;
        java.lang.String userNick = null;
        java.lang.String userNickJavaLangStringUserAge = null;
        android.view.View.OnClickListener userChangeUserAndroidViewViewOnClickListener = null;
        if ((dirtyFlags & 0x1fL) != 0) {
            if ((dirtyFlags & 0x13L) != 0) {
                    if (user != null) {
                        // read user.avatar
                        userAvatar = user.getAvatar();
                    }
            }
            if ((dirtyFlags & 0x1dL) != 0) {

                    if (user != null) {
                        // read user.age
                        userAge = user.getAge();
                        // read user.nick
                        userNick = user.getNick();
                    }
                    // read (user.nick) + ("   ")
                    userNickJavaLangString = (userNick) + ("   ");
                    // read ((user.nick) + ("   ")) + (user.age)
                    userNickJavaLangStringUserAge = (userNickJavaLangString) + (userAge);
            }
            if ((dirtyFlags & 0x11L) != 0) {
                    if (user != null) {
                        // read user::changeUser
                        userChangeUserAndroidViewViewOnClickListener = (((mUserChangeUserAndroidViewViewOnClickListener == null) ? (mUserChangeUserAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mUserChangeUserAndroidViewViewOnClickListener).setValue(user));
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0x11L) != 0) {
            // api target 1

            this.btnUser.setOnClickListener(userChangeUserAndroidViewViewOnClickListener);
        }
        if ((dirtyFlags & 0x13L) != 0) {
            // api target 1
            com.y.mj.User.getAvatar(this.mboundView2, userAvatar);
        }
        if ((dirtyFlags & 0x1dL) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, userNickJavaLangStringUserAge);
        }
    }

关于点击事件,也是编译器生成的代码实现的,自己定义一个监听器,触发时通过user对象调用成员方法

    private OnClickListenerImpl mUserChangeUserAndroidViewViewOnClickListener;

    public static class OnClickListenerImpl implements android.view.View.OnClickListener{
        private com.y.mj.User value;
        public OnClickListenerImpl setValue(com.y.mj.User value) {
            this.value = value;
            return value == null ? null : this;
        }
        @Override
        public void onClick(android.view.View arg0) {
            this.value.changeUser(arg0); 
        }
    }

    userChangeUserAndroidViewViewOnClickListener = (((mUserChangeUserAndroidViewViewOnClickListener == null) ? 
        (mUserChangeUserAndroidViewViewOnClickListener = new OnClickListenerImpl()) : 
        mUserChangeUserAndroidViewViewOnClickListener).setValue(user));

    this.btnUser.setOnClickListener(userChangeUserAndroidViewViewOnClickListener);

关于imageview加载图片

    app:avatar="@{user.avatar}"

    @BindingAdapter("avatar")
    public static void getAvatar(ImageView iv,String url){
        Picasso.with(iv.getContext()).load(url).into(iv);
    }

    com.y.mj.User.getAvatar(this.mboundView2, userAvatar);
上一篇下一篇

猜你喜欢

热点阅读