MVVM 的结构
把原来 View 中所有的数据都单独抽离到一个 ViewModel 中,这个ViewModel可以从原来的Model层去取数据
model 层每次来了数据,都由 ViewModel 去通知 UI 修改数据,同时可以将视图 view 和 model进行双向绑定
MVP 的数据 要通过中间传到 view
下面以一个实例来分析下 Databinding 的实现原理
实例
例子很简单,就是在一个页面上显示一个用户名,每过1s钟在用户名后边加个1,使用Databinding来实现这个效果
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;//这个类在我们编译时生成
User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
//
binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
user=new User("jett","123");
binding.setUser(user);
new Thread(){
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setName(user.getName()+"1");
// setUser 和 setVariable 是一样的操作
binding.setUser(user);
//当调用下面这句代码后,user 对象里面的数据就会一个一个填充到
//对应的 view 上去,数据的填充实现是在 ActivityMainBindingImpl的
// executeBindings() 方法上
binding.setVariable(BR.user,user);
}
}
}.start();
}
}
保存用户名和密码的实体类
public class User extends BaseObservable {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Bindable//apt 会读到这个注解,生成代码
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//BR 相当于和我们的 R 文件是一样的
//UI 的更新会调这个方法来完成
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
源码分析
下面的这个文件是AS编译后的文件 activity_main-layout.xml
该文件的路径如下图
activity_main-layout.xml
<!--?xml version="1.0" encoding="utf-8" standalone="yes"?-->
//modulepackage这个标签的包就是我们写的代码
<layout layout="activity_main" modulepackage="com.example.databinding_demo_20200329"
filepath="D:\AndroidFindingJobs\AndroidEnjoyLProjects\ViewModelProjects\DataBinding_
Demo_20200329\app\src\main\res\layout\activity_main.xml" directory="layout" ismerge="false">
<variables declared="true" type="com.example.databinding_demo_20200329.User" name="user">
<location startline="8" startoffset="8" endline="10" endoffset="63" />
</variables>
<targets>
//这些 tag 中的 _0、 _1 、_3 之后分别对应于 bindings 数组的下标
// 所以 bindings[0] 放的是 LinearLayout对象,bindings[1] bindings[2]放的是TextView
//target标签:属性(view)+字符串(LinearLayout)
<target tag="layout/activity_main_0" view="LinearLayout">
<expressions />
<location startline="14" startoffset="4" endline="42" endoffset="18" />
</target>
<target id="@+id/tv1" tag="binding_1" view="TextView"> //target标签,注意这里的tag标签
<expressions>
<expression text="user.name" attribute="android:text">
<location startline="24" startoffset="12" endline="24" endoffset="38" />
<twoway>
false
</twoway>
<valuelocation startline="24" startoffset="28" endline="24" endoffset="36" />
</expression>
</expressions>
<location startline="20" startoffset="8" endline="28" endoffset="55" />
</target>
<target id="@+id/tv2" tag="binding_2" view="TextView">
<expressions>
<expression text="user.password" attribute="android:text">
<location startline="36" startoffset="12" endline="36" endoffset="42" />
<twoway>
false
</twoway>
<valuelocation startline="36" startoffset="28" endline="36" endoffset="40" />
</expression>
</expressions>
<location startline="32" startoffset="8" endline="40" endoffset="55" />
</target>
</targets>
</layout>
下面是另一份AS编译后生成的 activity_main.xml 文件
文件路径如下图
activity_main.xml
这个文件和我们手写的xml文件很像,只是中间空出了一部分
<?xml version="1.0" encoding="utf-8"?>
这中间空出的一部分就是上面的activity_main-layout.xml对data进行的描述,as编译后把它们抽取分离了
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" android:tag="layout/activity_main_0" xmlns:android
="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/
res-auto" xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
//注意这里的binding_1 的标签,后面还要类似的android:tag="binding_2",
//在上面第一个文件里也有这个标签,这样就把这两个文件分离出来了,通过tag值进行绑定
android:tag="binding_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- android:text="@{user.name}" 这种写法是和 User类中的数据进行了绑定-->
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
最后看一下我们手动写的 activity_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools">
<!-- 在布局文件和数据绑定在一起,这里导入了 User类 (其中 name=“user”随便取) -->
//使用 DataBinding 会将下面的 <data></data>的部分抽走
<data>
<variable
name="user"
type="com.example.databinding_demo_20200329.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
//凡是有填这种信息的地方,这个属性就会抽取到另外一份新生成的文件中,作为一个标签来用
//只有标记了这种 @{user.name} 代码的地方才会有 tag 标签,user.name 这个字符串会生成代码,
//里面是可以添加java代码的
android:text="@{user.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- android:text="@{user.name}" 这种写法是和 User类中的数据进行了绑定-->
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.password}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</layout>下面是AS 自动生成的 BR.java
public class BR {
public static final int _all = 0;
public static final int password = 1;
public static final int name = 2;
public static final int user = 3;
}//代码中标记了@Bindable注解的,都会在BR.java 中生成相关属性
@Bindable//apt 会读到这个注解,生成代码
public String getName() {
return name;
}
@Bindable
public String getPassword() {
return password;
}下面来分析下实现原理
首先来看一个重要的抽象类,从注释就可以看出这个类是编译后生成的绑定类的基类
/**
* Base class for generated data binding classes. If possible, the generated binding should
* be instantiated using one of its generated static bind or inflate methods. If the specific
* binding is unknown, {@link DataBindingUtil#bind(View)} or
* {@link DataBindingUtil#inflate(LayoutInflater, int, ViewGroup, boolean)} should be used.
*/
public abstract class ViewDataBinding extends BaseObservable {
public abstract boolean setVariable(int variableId, @Nullable Object value);Databinding的代码写好后,build时会自动生成以下代码
//这个类是抽象类 ViewDataBinding.java 的实现类
public class ActivityMainBindingImpl extends ActivityMainBinding {
//variableId 对应 BR.java 中的 user属性,也就是BR.user,
//从上面BR.java的代码看BR.user = 3
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.user == variableId) {
setUser((com.example.databinding_demo_20200329.User) variable);
}
else {
variableSet = false;
}
return variableSet;
}
......}进入 setUser的方法
public void setUser(@Nullable com.example.databinding_demo_20200329.User User) {
updateRegistration(0, User);//更新注册
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}//observable 就是我们的 User类
protected boolean updateRegistration(int localFieldId, Observable observable) {
// CREATE_PROPERTY_LISTENER 常量是一个监听器
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}//注册的目的:使layout文件、MainActivity、ViewModel(单独写的一个类)、ViewDataBinding
//(又是一个文件)建立相互关联的引用这些文件之间,如果将来要通信,一定要把相互关联的引用建立起来
//如果有了联系,ViewDataBinding就可以操作我们写的 User 类
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return unregisterFrom(localFieldId);
}
//WeakListener 数组的下标对应了刚刚在BR文件中注册的三个整数 0、1、2、3
//每一个 WeakListener 对应 BR文件里的一个属性
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
//注册监听器,就是通过各种包装,存到数组里面
registerTo(localFieldId, observable, listenerCreator);
return true;
}
if (listener.getTarget() == observable) {
return false;//nothing to do, same object
}
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
//先到数组中去拿监听器,拿不到才会进行创建,也就是如果以前这个监听添加过了,
//就不会再重新进行创建
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
listener = listenerCreator.create(this, localFieldId);
mLocalFieldObservers[localFieldId] = listener;
if (mLifecycleOwner != null) {
listener.setLifecycleOwner(mLifecycleOwner);
}
}
listener.setTarget(observable);
}接下来继续看DataBinding的实现,在MainActivity中有以下代码
ActivityMainBinding binding;//这个类在我们编译时生成
binding= DataBindingUtil.setContentView(this,R.layout.activity_main);进入 setContentView 一步步深入
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}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);
}最终会进入 bind 方法
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent,
View[] roots, int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}public class DataBindingUtil {
private static DataBinderMapper sMapper = new DataBinderMapperImpl();getDataBinder的实现在 DataBinderMapperImpl
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component
, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
//获取activity_main-layout.xml中的 tag 标签:<target tag="layout/activity_main_0"
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYMAIN: {
//如果 tag="layout/activity_main_0" 直接new一个 ActivityMainBindingImpl
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
......
}
}
}
return null;
}public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent
bindingComponent, @NonNull View root) {
//下面的 mapBindings 方法一旦跑完,xml中定义的 view (一个LinearLayout 和两个 TextView)
//就会存到数组 bindings 中
this(bindingComponent, root,
mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}下面看一下 mapBindings 的实现,在 ViewDataBinding中
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
//读取xml文件时,有几个tag标签我们是可以知道的
Object[] bindings = new Object[numBindings];
//mapBindings 方法一旦跑完,xml中定义的 view (一个LinearLayout 和两个 TextView),
//就会存到数组 bindings 中
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
return bindings;
}方法 mapBindings 主要是解析之前AS编译后生成的两个xml文件,读取到xml进行一些列的字符拼接、判断,读取标签时,是 View 的就会往数组里面存,如果是 ViewGroup的就会继续找里面是 View 的 这样一步一步深入
private static void mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) {
final int indexInIncludes;
final ViewDataBinding existingBinding = getBinding(view);
if (existingBinding != null) {
return;
}
//从标签中读取信息,然后创建根据信息View解析的就是 <data></data> 标签生成的那个
// activity_main-layout.xml文件 和 有空白的那个activity_main.xml 文件
Object objTag = view.getTag();//获取标签
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
//如果标签是 layout 开头的,对应的 view 会放到数组 bindings 里面
if (isRoot && tag != null && tag.startsWith("layout")) {
final int underscoreIndex = tag.lastIndexOf('_');
if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
final int index = parseTagInt(tag, underscoreIndex + 1);
//将 view 存到 对应id 的 bindings 数组里,这里的view 就是layout_xml 文件里我们定义的两个TextView
if (bindings[index] == null) {
bindings[index] = view;
}
......
if (!isInclude) {//最后调用自己 进行递归
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}mapBindings方法执行完成后,返回一个存有 activity_main.xml中的View的数组
之后通过 this调用 ActivityMainBindingImpl 的构造方法,此时xml的解析已经完成
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent
bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.TextView) bindings[1]
, (android.widget.TextView) bindings[2]
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
//原来的标签不需要了,直接设置为空 null
this.mboundView0.setTag(null);
//this 就是 Activity tv1、tv2就是 我们在 xml文件中定义的TextView
this.tv1.setTag(null);
this.tv2.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}上面我们走完了DataBinding中解析xml的源码,继续往下走,看一下数据是怎么绑定到 UI 上以及当我们的数据发生改变时是怎么来通知 UI 更新的
看一下 ViewDatBinding 的静态初始化块(类一用马上就开始执行)
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);
//通过一个线程实现,数据填充到 UI
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
};
}
}看一下 mRebindRunnable 的线程
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}private void executeBindingsInternal() {
......
if (!mRebindHalted) {
executeBindings();//执行绑定
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}在ViewDataBinding中 executeBindings() 是一个抽象方法
protected abstract void executeBindings();该方法的实现在 ActivityMainBindingImpl 中
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userName = null;
com.example.databinding_demo_20200329.User user = mUser;
java.lang.String userPassword = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xbL) != 0) {
if (user != null) {
// read user.name
//调用我们自己写的User对象的方法来获取数据
userName = user.getName();
}
}
if ((dirtyFlags & 0xdL) != 0) {
if (user != null) {
// read user.password
userPassword = user.getPassword();
}
}
}
// batch finished
if ((dirtyFlags & 0xbL) != 0) {
// api target 1
//把值绑定到 View 上,这里最终会调用TextView的setText方法
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv1, userName);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userPassword);
}
}以上是将数据绑定到UI的过程,下面看一下数据更新流程
我们在调用 user的setName方法更改数据时,会去执行一个notify方法 notifyPropertyChanged(BR.name);
public void notifyPropertyChanged(int fieldId) {
......
mCallbacks.notifyCallbacks(this, fieldId, null);
}public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
mNotificationLevel++;
notifyRecurse(sender, arg, arg2);
......
}
private void notifyRecurse(T sender, int arg, A arg2) {
final int callbackCount = mCallbacks.size();
final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
......
notifyRemainder(sender, arg, arg2, remainderIndex);
......
}private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
if (remainderIndex < 0) {
notifyFirst64(sender, arg, arg2);
} else {
......
}
}private void notifyFirst64(T sender, int arg, A arg2) {
final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
}private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
final int endIndex, final long bits) {
long bitMask = 1;
for (int i = startIndex; i < endIndex; i++) {
if ((bits & bitMask) == 0) {
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}onNotifyCallback的实现在 PropertyChangeRegistry
private static final NotifierCallback<OnPropertyChangedCallback, Observable, Void>
NOTIFIER_CALLBACK = new NotifierCallback<OnPropertyChangedCallback, Observable, Void>() {
public void onNotifyCallback(OnPropertyChangedCallback callback,
Observable sender, int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
};onPropertyChanged 的实现在 ViewDataBinding的内部类 WeakPropertyListener
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
......
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
......
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}最终走到 onFieldChange 它的实现在 ActivityMainBindingImpl
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeUser((com.example.databinding_demo_20200329.User) object, fieldId);
}
return false;
}private boolean onChangeUser(com.example.databinding_demo_20200329.User User,
int fieldId) {
//这里我们在User实体类中有几个变量,就会有几个判断,变量多会改为switch 语句
if (fieldId == BR._all) {
synchronized(this) {
//通过对 mDirtyFlags 变量进行或运算,我们会知道哪一个变量需要修改数据
//最后又会调用之前讲到的用来绑定数据到UI的executeBindings()方法更新数据
mDirtyFlags |= 0x1L;
}
return true;
}
else if (fieldId == BR.name) {
synchronized(this) {
mDirtyFlags |= 0x2L;
}
return true;
}
else if (fieldId == BR.password) {
synchronized(this) {
mDirtyFlags |= 0x4L;
}
return true;
}
return false;
}时序图
最后来两张时序图理清思路
一、解析xml并绑定数据到 UI的时序图
二、更新注册及数据更新流程