jetpack系列笔记
一、jetpack系列之Lifecycle
二、jetpack系列之LiveData
三、jetpack系列之ViewModel
四、jetpack系列之DataBinding
jetpack系列之DataBinding
一、DataBinding是什么?
官方定义:
数据绑定库是一个支持库,可让您使用声明性格式(而不是以程序化方式)将布局中的界面组件绑定到应用中的数据源。
二、使用步骤
1.先打开开关:
build.gradle.kts中将dataBinding 设置为true
android {
...
buildFeatures {
dataBinding true
}
}
2.修改布局文件
修改为Databinding的布局文件。可以在原有布局文件中,右键->Show Context Actions->Convert to data binding layout.
代码如下:
<?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">
<data>
<variable
name="user"
type="com.wyx.jetpack.dataBinding.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".dataBinding.DataBindingActivity">
<TextView
android:id="@+id/name_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/pwd_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.pwd}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name_text"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
variable属性中,name名字可以随意取,type是我们自定义的实体类。然后使用@{ }语法,将实体类中的变量赋值给控件,如:android:text=“@{user.name}”, 其中user是variable属性中的name。
3.创建一个实体类
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.databinding.library.baseAdapters.BR
class User() : BaseObservable() {
@Bindable
var name: String = "hello world"
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
@Bindable
var pwd: String = "123456"
set(value) {
field = value
notifyPropertyChanged(BR.pwd)
}
}
- 类需要继承BaseObservable()
- 变量需要get()方法添加@Bindble注解
- 添加notifyPropertyChanged(BR.属性名),BR为系统自动生成,可以先理解为固定用法。
注:本文使用User作为实体,APT产生的代码会有setUser()类似方法,实际使用不同的实体,会产生的名字也不同。
4.在Activity中调用
class DataBindingActivity : AppCompatActivity() {
private lateinit var binding:ActivityDataBindingBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this@DataBindingActivity, R.layout.activity_data_binding)
val user = User()
user.name = "Yoshawn"
user.pwd = "123456"
//设置整个变量
binding.user = user
//设置某个属性
user.name = "Jack"
//直接使用控件
binding.nameText.text = "David"
}
}
- 使用DataBindingUtil.setContentView(this@DataBindingActivity, R.layout.activity_data_binding) 代替原来的setContentView方法。
- 使用binding.user = user 给binding的进行赋值
三、Databinding原理
1、View绑定过程
Databinding使用了注解处理器,利用编译时技术,帮我们生成了很多代码去代替我们的手动操作。生成的文件路径为build\generated\source\kapt\debug\包路径, 可以看到,这里有我们要使用的BR文件,还有其他我们需要用到的文件。
- 重新生成了一个布局文件,布局文件路径为:build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_data_binding-layout.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="jetpack\src\main\res\layout\activity_data_binding.xml"
isBindingData="true" isMerge="false"
layout="activity_data_binding" modulePackage="com.wyx.jetpack"
rootNodeType="androidx.constraintlayout.widget.ConstraintLayout" rootNodeViewId="@+id/main">
<Variables name="user" declared="true" type="com.wyx.jetpack.dataBinding.User">
<location endLine="8" endOffset="53" startLine="6" startOffset="8" />
</Variables>
<Targets>
<Target id="@+id/main" tag="layout/activity_data_binding_0"
view="androidx.constraintlayout.widget.ConstraintLayout">
<Expressions />
<location endLine="33" endOffset="55" startLine="11" startOffset="4" />
</Target>
<Target id="@+id/name_text" tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="user.name">
<Location endLine="21" endOffset="38" startLine="21" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="21" endOffset="36" startLine="21" startOffset="28" />
</Expression>
</Expressions>
<location endLine="23" endOffset="54" startLine="17" startOffset="8" />
</Target>
<Target id="@+id/pwd_text" tag="binding_2" view="TextView">
<Expressions>
<Expression attribute="android:text" text="user.pwd">
<Location endLine="29" endOffset="37" startLine="29" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="29" endOffset="35" startLine="29" startOffset="28" />
</Expression>
</Expressions>
<location endLine="31" endOffset="65" startLine="25" startOffset="8" />
</Target>
</Targets>
</Layout>
可以看到,这个文件将我们的布局,每一个控件的属性记录了下来,此外,还增加了一个tag属性,tag属性的值都是binding_1,binding_2依次往下排。也可以看一下文件build/intermediates/incremental/debug/mergeDebugResources/stripped.dir/layout/activity_data_binding.xml,可以看到跟我们写的布局文件很相似,但是多了一个tag属性。我们可以根据tag属性,能找到该控件的所有信息。
- view的绑定过程:
binding = DataBindingUtil.setContentView(this@DataBindingActivity, R.layout.activity_data_binding)
//调用setContentView
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
//调用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方法,代替我们手动调用。这里,还从DecorView中获取到了顶层的ContentView。
//调用bindToAddedViews(bindingComponent, contentView, 0, layoutId)
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;
//如果只有一个View,直接进行绑定,如果有多个View,则对View进行遍历,然后去绑定。
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);
}
}
//调用bind(component, childView, layoutId);
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
//sMapper的定义
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
这里DataBinderMapperImpl是APT自动生成的一个类,在这个类里继续做绑定操作。
@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_ACTIVITYDATABINDING: {
if ("layout/activity_data_binding_0".equals(tag)) {
return new ActivityDataBindingBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_data_binding is invalid. Received: " + tag);
}
}
}
return null;
}
这里获取到View的tag标签。我们布局的最外层Layout的tag是layout/activity_data_binding_0,其实就是创建了一个类ActivityDataBindingBindingImpl。这个类也是编译器自动给我们生成的,看一下这个类的创建过程。
public ActivityDataBindingBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}
private ActivityDataBindingBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (androidx.constraintlayout.widget.ConstraintLayout) bindings[0]
, (android.widget.TextView) bindings[1]
, (android.widget.TextView) bindings[2]
);
this.main.setTag(null);
this.nameText.setTag(null);
this.pwdText.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
构造方法进行了一下重载。调用了三个参数的构造方法。看下第三个参数Object[] 是什么。
//apBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds)
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;
}
//mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
private static void mapBindings(DataBindingComponent bindingComponent, View view,
Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
boolean isRoot) {
...
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
//如果以tag是layout开头,说明是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);
if (bindings[index] == null) {
bindings[index] = view;
}
indexInIncludes = includes == null ? -1 : index;
isBound = true;
} else {
indexInIncludes = -1;
}
//如果以bingding_ 开头,放进View中
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[tagIndex] == null) {
bindings[tagIndex] = view;
}
isBound = true;
indexInIncludes = includes == null ? -1 : tagIndex;
} else {
// Not a bound view
indexInIncludes = -1;
}
...
//如果是ViewGroup,还要递归遍历其子View,放到bindings数组中
if (view instanceof ViewGroup) {
final ViewGroup viewGroup = (ViewGroup) view;
final int count = viewGroup.getChildCount();
int minInclude = 0;
for (int i = 0; i < count; i++) {
final View child = viewGroup.getChildAt(i);
boolean isInclude = false;
if (indexInIncludes >= 0 && child.getTag() instanceof String) {
String childTag = (String) child.getTag();
if (childTag.endsWith("_0") &&
childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
// This *could* be an include. Test against the expected includes.
int includeIndex = findIncludeIndex(childTag, minInclude,
includes, indexInIncludes);
if (includeIndex >= 0) {
isInclude = true;
minInclude = includeIndex + 1;
final int index = includes.indexes[indexInIncludes][includeIndex];
final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
int lastMatchingIndex = findLastMatching(viewGroup, i);
if (lastMatchingIndex == i) {
//在这里去递归调用
bindings[index] = DataBindingUtil.bind(bindingComponent, child,
layoutId);
} else {
...
}
}
}
}
if (!isInclude) {
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}
把所有的View对象,都保存在了bindings这个Object数组中。然后调用ActivityDataBindingBindingImpl的第二个构造方法,将View进行绑定。这个时候我们就可以在Activity中使用databinding中的view了。
2、数据驱动的过程
-
databind过程中,先介绍其中用到的几个类的作用:
-
数据驱动的过程:
首先用户调用setUser:
//ActivityViewDataBindingImpl.java
public void setUser(@Nullable com.wyx.jetpack.dataBinding.User User) {
//更新注册的User
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
//通知View数据变化
notifyPropertyChanged(BR.user);
super.requestRebind();
}
注册User的过程:
//ViewDataBinding.java
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
CREATE_PROPERTY_LISTENER 就是一个CreateWeakListener,通过WeakPropertyListener的getListener()创建一个WeakListener:
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(
ViewDataBinding viewDataBinding,
int localFieldId,
ReferenceQueue<ViewDataBinding> referenceQueue
) {
return new WeakPropertyListener(viewDataBinding, localFieldId, referenceQueue)
.getListener();
}
};
继续看注册过程:
//ViewDataBinding.java
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return unregisterFrom(localFieldId);
}
//1.先从mLocalFieldObservers看有没有缓存的WeakListener
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
//2、缓存中没有WeakListener ,进行创建并注册
registerTo(localFieldId, observable, listenerCreator);
return true;
}
if (listener.getTarget() == observable) {
return false;//nothing to do, same object
}
//3、缓存中存在WeakListener ,重新注册
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
//4、再次在缓存中检查知否存在
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
//5、调用CREATE_PROPERTY_LISTENER也创建WeakListener,其实最终调用了WeakPropertyListener 的getListener()方法。
listener = listenerCreator.create(this, localFieldId, sReferenceQueue);
//6、将WeakListener放入缓存中
mLocalFieldObservers[localFieldId] = listener;
if (mLifecycleOwner != null) {
listener.setLifecycleOwner(mLifecycleOwner);
}
}
//7、这里是将WeakPropertyListener 作为观察者,注册保存在BaseObservable中,也就是我们自定义实体的父类。
listener.setTarget(observable);
}
可以看到,WeakPropertyListener 创建并持有WeakListener对象 ,WeakListener 同时也持有WeakPropertyListener 对象。可以通过WeakListener 控制WeakPropertyListener。下面看一下第七步如何将WeakPropertyListener注册进BaseObservable中的:
//WeakListener.java
public void setTarget(T object) {
unregister();
mTarget = object;
if (mTarget != null) {
mObservable.addListener(mTarget);
}
}
//ViewDataBinding.WeakPropertyListener
@Override
public void addListener(Observable target) {
//this 为WeakPropertyListener
target.addOnPropertyChangedCallback(this);
}
//BaseObservable.java
@Override
public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
synchronized (this) {
if (mCallbacks == null) {
mCallbacks = new PropertyChangeRegistry();
}
}
mCallbacks.add(callback);
}
//CallbackRegistry.java
public synchronized void add(C callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
int index = mCallbacks.lastIndexOf(callback);
if (index < 0 || isRemoved(index)) {
mCallbacks.add(callback);
}
}
BaseObservable持有PropertyChangeRegistry, PropertyChangeRegistry的父类是CallbackRegistry。在CallbackRegistry中保存了一个List,用来存放所有的WeakPropertyListener。到这里,将WeakPropertyListener注册进了BaseObservable中。
回过头来看调用setUser的过程,观察者和被观察者已经绑定,接下来往下看:
//BaseObservable.java
public void setUser(@Nullable com.wyx.jetpack.dataBinding.User User) {
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.user);
super.requestRebind();
}
mDirtyFlags 记录了哪个属性或字段进行了改变,是BR中定义的值。mDirtyFlags |= 0x1L 代表是整个User。
notifyPropertyChanged(BR.user) 是通知整个User进行了变更。在我们的User实体中,单个变量变化,调用的也是这个方法。下面是这个调用时序,可以自己自己去读一下细节:
- BaseObservable.java : notifyPropertyChanged(int fieldId)
- BaseObservable.java : mCallbacks.notifyCallbacks(this, fieldId, null);
- CallbackRegistry.java : notifyRecurse(sender, arg, arg2);
- CallbackRegistry.java : notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
- CallbackRegistry.java : mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
mNotifier是在PropertyChangeRegistry.java中定义,并传给父类的:
private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
@Override
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
int arg, Void notUsed) {
callback.onPropertyChanged(sender, arg);
}
};
public PropertyChangeRegistry() {
super(NOTIFIER_CALLBACK);
}
继续刚才的调用:
- PropertyChangeRegistry.java : callback.onPropertyChanged(sender, arg);
- ViewDatabing.java : handleFieldChange(int mLocalFieldId, Object object, int fieldId)
看下ViewDatabing.java 中handleFieldChange的实现:
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) {
return;
}
//1、是否能找到需要变更的字段
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
//2、请求执行
requestRebind();
}
}
先看一下如何判断需要变更的字段的:
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeUser((com.wyx.jetpack.dataBinding.User) object, fieldId);
}
return false;
}
private boolean onChangeUser(com.wyx.jetpack.dataBinding.User User, int fieldId) {
if (fieldId == BR._all) {
synchronized(this) {
mDirtyFlags |= 0x1L;
}
return true;
}
else if (fieldId == BR.name) {
synchronized(this) {
mDirtyFlags |= 0x2L;
}
return true;
}
else if (fieldId == BR.pwd) {
synchronized(this) {
mDirtyFlags |= 0x4L;
}
return true;
}
return false;
}
将mDirtyFlags 与字段在BR文件中对应的数值进行或操作,mDirtyFlags 初始值为0x10L,在invalidateAll() 中进行赋值的。为什么是0x8L?这是因为实体里有1个字段,值为2的3次方。实体里有n个字段,mDirtyFlags 的值为2的 n+1次方。这样,需要修改的字段,都记录在mDirtyFlags 中。
再看下requestRebind()是如何执行的:
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
...
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
USE_CHOREOGRAPHER判断是在SDK大于16后,都交到编舞者Choreographer中执行的。编舞者Choreographer是UI绘制一个很重要的类,交给编舞这去执行,可以提高性能,减少卡顿发生。
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
最终也是执行了mRebindRunnable 的run()方法:
//ViewDataBinding
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
...
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;
}
执行了executeBindings(),executeBindings()实现是在ActivityDataBindingBindingImpl.java中。
接下来是赋值过程:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userName = null;
com.wyx.jetpack.dataBinding.User user = mUser;
boolean userSex = false;
java.lang.String userPwd = null;
java.lang.String stringValueOfUserSex = null;
if ((dirtyFlags & 0x1fL) != 0) {
if ((dirtyFlags & 0x13L) != 0) {
if (user != null) {
// read user.name
userName = user.getName();
}
}
if ((dirtyFlags & 0x15L) != 0) {
if (user != null) {
// read user.pwd
userPwd = user.getPwd();
}
}
}
// batch finished
if ((dirtyFlags & 0x13L) != 0) {
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.nameText, userName);
}
if ((dirtyFlags & 0x15L) != 0) {
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.pwdText, userPwd);
}
}
根据dirtyFlags的值,执行与操作,找到对应的字段进行赋值。如果需要修改userName值,此时dirtyFlags二进制为11,0x13L转换为二进制是10011,11 & 10011不为零,需要修改userName值。最终还是调用了View的setText方法去修改。
总结
DataViewBinding 使用了观察者模式,利用框架将观察者和被观察者进行绑定。观察者和被观察者是多对多的关系,当某个被观察者发生数据变化时,不再需要通过Map和两层for循环的方式进行回调,提高了效率。但是DataViewBinding会很占内存,而且不容易查找bug。