DataBinding原理分析
[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为例:
- build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_main-layout.xml
- incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
先看后者,几乎是原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.pngUser中使用的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);