基本使用
关于DataBinding的使用可以直接参考官网数据绑定库介绍,此处就直接忽略。本篇主要是阐述DataBinding是如何实现liveData的数据更新到对应xml布局
准备Demo
直接clone如下demo
git clone https://github.com/xiaobaoyihao/AndroidDataBindingDemo.git
该样例就是实现最简单的DataBinding
绑定类
绑定类是用于访问布局的变量和视图,所有绑定类都是继承ViewDataBinding抽象类的。
demo中引入了数据绑定和视图绑定,所以系统会为每个布局文件生成一个绑定类。默认情况下,类名称基于布局文件的名称,它会转换为 Pascal 大小写形式并在末尾添加 Binding 后缀。以本demo中fragment_main.xml为例生成的对应类为 FragmentMainBinding。关于绑定类的使用可以直接参考官网使用文档
我们以本例中FragmentMainBinding为例进行分析
我们先看MainFragment代码
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private lateinit var viewModel: MainViewModel
private lateinit var mBinding: FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
mBinding = FragmentMainBinding.inflate(layoutInflater, container, false)
return mBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mBinding.lifecycleOwner = viewLifecycleOwner
mBinding.fvm = viewModel
}
}
MainFragment执行了执行了绑定类的inflate静态方法返回了绑定类对象,而其中root成员变量就是对应布局文件的根视图
对应布局
<?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="fvm"
type="com.dbs.databinding.demo.ui.main.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.dbs.databinding.demo.ui.main.MainFragment">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@{String.valueOf(fvm.MClickedCount)}"
android:textSize="25dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="0" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:onClick="@{fvm::onClickBtn}"
android:text="button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/message" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
viewModel也非常简单
class MainViewModel : ViewModel() {
var mClickedCount = MutableLiveData(0)
fun onClickBtn(view: View) {
// mCLickCount++
mClickedCount.value = mClickedCount.value?.inc()?: 0
}
}
主要实现就是通过按钮点击来实现TextView文本修改;为了有针对性分析,我们以问题形式进行展开分析说明
Q1:FragmentMainBinding.inflate返回了什么?
首先我们从build/data_binding_base_class_source_out目录下找到这个FragmentMainBinding
可以看到MainFragmentBinding类为抽象类,该类引用了对应布局文件的view(带有id的)以及在布局文件中声明的viewModule成员变量和bind、inflate相关方法,我们思路回到inflate方法中
// FragmentMainBinding.java
@NonNull
public static FragmentMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
}
@NonNull
@Deprecated
public static FragmentMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) {
return ViewDataBinding.<FragmentMainBinding>inflateInternal(inflater, R.layout.fragment_main, root, attachToRoot, component);
}
// ViewDataBinding.java
protected static <T extends ViewDataBinding> T inflateInternal(
@NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
boolean attachToParent, @Nullable Object bindingComponent) {
return DataBindingUtil.inflate(
inflater,
layoutId,
parent,
attachToParent,
checkAndCastToBindingComponent(bindingComponent)
);
}
public class DataBindingUtil {
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
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);
}
}
这里面的sMapper类型为androidx.databinding.DataBinderMapperImpl,需要提下每个模块都有同名的DataBinderMapperImpl,app模块存在二个同名但路径不一样;sMapper可以理解为映射表入口类它引用了appl模块中DataBinderMapperImp
可以清楚看到DataBinderMapperImpl构造器中调用了addMapper方法;我们继续看getDataBinder方法
@Override
public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
int layoutId) {
for(DataBinderMapper mapper : mMappers) {
ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
if (result != null) {
return result;
}
}
if (loadFeatures()) {
return getDataBinder(bindingComponent, view, layoutId);
}
return null;
}
可以看到绑定类的查找是依次遍历项目中所有DataBinderMapperImpl类来搜查绑定类;那么项目中的所有模块的DataBinderMapperImpl是如何添加到mapper列表中呢?我们从上图中看到构造器中调用了addMapper方法
public void addMapper(DataBinderMapper mapper) {
Class<? extends DataBinderMapper> mapperClass = mapper.getClass();
if (mExistingMappers.add(mapperClass)) {
mMappers.add(mapper);
final List<DataBinderMapper> dependencies = mapper.collectDependencies();
for(DataBinderMapper dependency : dependencies) {
addMapper(dependency);
}
}
}
上面代码可以看到mMappers维护了apk所有依赖的DataBinderMapper映射表;先是添加app模块的映射表,然后将其依赖的映射表依次添加到mMappers中;本demo中最终调用了com.example.demo.DataBinderMapperImpl.getDataBinder方法
public class DataBinderMapperImpl extends DataBinderMapper {
private static final int LAYOUT_FRAGMENTMAIN = 1;
static {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.demo.R.layout.fragment_main, LAYOUT_FRAGMENTMAIN);
}
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
// 通过布局资源id以及根view的tag来进行指定绑定类
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_FRAGMENTMAIN: {
if ("layout/fragment_main_0".equals(tag)) {
return new FragmentMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for fragment_main is invalid. Received: " + tag);
}
}
}
return null;
}
}
我们反编译下apk
每个绑定布局的根view的tag都以layout/{layout_xml_name}_int来命名的,绑定类的查找是通过xml布局名称以及tag为条件
总结下FragmentMainBinding.inflate调用轨迹总结如下
至此我们明白了FragmentMainBinding.inflate返回的其实是FragmentMainBindingImpl对象而非FragmentMainBinding(抽象类)
Q2:绑定类是如何解析并持有view?
我们先来看下FragmentMainBinding结构
可以清楚看到它持有了布局中的带id的view,以及vm;所以绑定类就是view和vm的通讯桥梁;view在什么时候解析并被赋值?我们从绑定类的真正实现类FragmentMainBindingImpl入手,先看下构造器
public FragmentMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}
private FragmentMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.Button) bindings[2]
, (androidx.constraintlayout.widget.ConstraintLayout) bindings[0]
, (android.widget.TextView) bindings[1]
);
this.btn.setTag(null);
this.main.setTag(null);
this.message.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
可以看到内部是调用mapBindings方法返回view的数组,然后将该数组的view赋值到绑定类相应的成员变量当中
我们继续看下mapBindings方法
// ViewDataBinding.java
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;
}
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;
}
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
// 如果是根view,则判断根view的tag是否以layout开头
if (isRoot && tag != null && tag.startsWith("layout")) {
final int underscoreIndex = tag.lastIndexOf('_');
//解析根view的tag,将view放到数组的指定位置(位置由xml中的tag后缀数字确定)
if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
final int index = parseTagInt(tag, underscoreIndex + 1);
if (bindings[index] == null) {
bindings[index] = view;
}
// 确定绑定布局文件存在include元素布局所在位置
indexInIncludes = includes == null ? -1 : index;
isBound = true;
} else {
indexInIncludes = -1;
}
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
// 如果tag以binding_开头,说明需要绑定类持有,所以赋值
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;
}
if (!isBound) {
final int id = view.getId();
if (id > 0) {
int index;
// 如果xml中view声明了id,绑定类也是需要持有该view
if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
bindings[index] == null) {
bindings[index] = view;
}
}
}
// 下面的依次递归遍历,逻辑和上面类似
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();
// include布局根view的tag格式:layout/${layout_name}_0
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 {
final int includeCount = lastMatchingIndex - i + 1;
final View[] included = new View[includeCount];
for (int j = 0; j < includeCount; j++) {
included[j] = viewGroup.getChildAt(i + j);
}
bindings[index] = DataBindingUtil.bind(bindingComponent, included,
layoutId);
i += includeCount - 1;
}
}
}
}
if (!isInclude) {
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}
我们看下apk中fragment_main.xml中view的tag是否是我们预期那样?
至此我们明白了绑定类是如何解析并持有view,它是通过解析布局中view的tag携带的信息存放到数组指定位置中,绑定类构造器就是通过正确读取该数组view,赋值到绑定类的成员变量中,赋值成功后对于子view擦除tag,对于根view则将绑定类绑定到tag中,后面是对view的重绘了
Q3:VM中点击事件是如何绑定到视图当中?
因为绑定类构造器中有视图重绘,最终调用invalidateAll,其调用链路如下
我们看下executeBindings部分代码
可以看到执行绑定是会对按钮进行事件绑定,这个事件监听器是OnClickListenerImpl
public static class OnClickListenerImpl implements android.view.View.OnClickListener{
private com.dbs.databinding.demo.ui.main.MainViewModel value;
public OnClickListenerImpl setValue(com.dbs.databinding.demo.ui.main.MainViewModel value) {
this.value = value;
return value == null ? null : this;
}
@Override
public void onClick(android.view.View arg0) {
this.value.onClickBtn(arg0);
}
}
因为布局中引用了vm.onClickBtn方法,所以这个事件监听器顺其自然地持有vm;因此绑定类的点击事件绑定最终通过一个包装类来实现事件的传导来实现的
Q4:LiveData数据变化是如何更新到view上?
要弄清楚这个问题,我们需要先看看MutableLiveData.setValue
// LiveData.java
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
因为dispatchingValue传入了null所以最终执行considerNotify通知所有观察者,我们来看下considerNotify
private void considerNotify(ObserverWrapper observer) {
// 如果LiveData处于非活动状态,不进行通知,当视图至少是onStarted时才会通知视图
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
可以看到最终livedata将变化的数据通知给了所有的观察者的onChanged方法这个方法相信大家都很熟悉了;
这里的观察者observer.mObserver类型其实为LifecycleBoundObserver,通常我们都是调用liveData.observe方法来进行监听data变化
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
可以清楚看到observe方法将我们传入的观察者包装成LifecycleBoundObserver对象,它订阅了Lifecycle所以有了感知生命周期的能力
总结下基本调用轨迹
到这里我们基本明白了liveData的数据更新是如何通知到所有观察者了。那么绑定类内部是在什么时机订阅LiveData呢?我们注意到demo中有这样一行代码
class MainFragment : Fragment() {
...
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
//重点代码
mBinding.lifecycleOwner = viewLifecycleOwner
mBinding.fvm = viewModel
}
}
注意点:对于fragment中建议使用viewLifecycleOwner赋值给对应绑定类,而对于activity可以直接使用this,因为activity中mLifecycleRegistry其实是通过弱引用持有activity,所以没有内存泄漏风险
/**
* Sets the {@link LifecycleOwner} that should be used for observing changes of
* LiveData in this binding. If a {@link LiveData} is in one of the binding expressions
* and no LifecycleOwner is set, the LiveData will not be observed and updates to it
* will not be propagated to the UI.
*
* @param lifecycleOwner The LifecycleOwner that should be used for observing changes of
* LiveData in this binding.
*/
@MainThread
public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
if (mLifecycleOwner == lifecycleOwner) {
return;
}
if (mLifecycleOwner != null) {
mLifecycleOwner.getLifecycle().removeObserver(mOnStartListener);
}
mLifecycleOwner = lifecycleOwner;
if (lifecycleOwner != null) {
if (mOnStartListener == null) {
mOnStartListener = new OnStartListener(this);
}
lifecycleOwner.getLifecycle().addObserver(mOnStartListener);
}
for (WeakListener<?> weakListener : mLocalFieldObservers) {
if (weakListener != null) {
weakListener.setLifecycleOwner(lifecycleOwner);
}
}
}
方法注释上写的比较清楚:因为demo中存在binding表达式,如果不调用该方法会导致livedata数据无法更新到view上
其实从上面代码可以看到,首次会创建了OnStartListener对象并注册到lifecycle中,这样单lifecycle进入ON_START生命周期时会自动进行数据绑定,相关实现见下图
紧接找为绑定类中所有的表达式变量的观察者设置lifecycleOwner,这里面的观察者其实就是LiveDataListener.mListener;从哪里可以看出,这里面有点复杂,
// FragmentMainBindingImpl.java
protected void executeBindings() {
...
if ((dirtyFlags & 0x7L) != 0) {
if (fvm != null) {
// read fvm.MClickedCount
fvmMClickedCount = fvm.getMClickedCount();
}
updateLiveDataRegistration(0, fvmMClickedCount);
...
}
}
protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) {
mInLiveDataRegisterObserver = true;
try {
return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER);
} finally {
mInLiveDataRegisterObserver = false;
}
}
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
// 如果livedata为空,卸载之前的旧监听器
if (observable == null) {
return unregisterFrom(localFieldId);
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
// 如果对应的表达式变量监听器为空,创建一个,并注册
registerTo(localFieldId, observable, listenerCreator);
return true;
}
// 如果绑定的livedata是同一个,不需要做处理
if (listener.getTarget() == observable) {
return false;//nothing to do, same object
}
// 如果不是同一个,则需要先卸载,并重新注册
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}
/**
* @hide
*/
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);
}
最终调用了LiveDataListener.mListener的setLifecycleOwner、setTarget方法,最终又会调用LiveDataListener的setLifecycleOwner、addListener方法,贴下LiveDataListener
private static class LiveDataListener implements Observer,
ObservableReference<LiveData<?>> {
final WeakListener<LiveData<?>> mListener;
LifecycleOwner mLifecycleOwner;
public LiveDataListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener(binder, localFieldId, this);
}
@Override
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
// 主要是卸载,重新注册二个功能
LifecycleOwner owner = (LifecycleOwner) lifecycleOwner;
LiveData<?> liveData = mListener.getTarget();
if (liveData != null) {
if (mLifecycleOwner != null) {
liveData.removeObserver(this);
}
if (lifecycleOwner != null) {
liveData.observe(owner, this);
}
}
mLifecycleOwner = owner;
}
@Override
public WeakListener<LiveData<?>> getListener() {
return mListener;
}
@Override
public void addListener(LiveData<?> target) {
if (mLifecycleOwner != null) {
// 监听liveData数据变化
target.observe(mLifecycleOwner, this);
}
}
@Override
public void removeListener(LiveData<?> target) {
target.removeObserver(this);
}
@Override
public void onChanged(@Nullable Object o) {
ViewDataBinding binder = mListener.getBinder();
if (binder != null) {
// 注意此处最后参数为0,这样后面的onFieldChange返回值必定为true,所以必定会执行requestRebind方法
binder.handleFieldChange(mListener.mLocalFieldId, mListener.getTarget(), 0);
}
}
}
上述的二个操作完成后,每当livedata变化时都会通知到LiveDataListener.onChanged方法,改方法中会对mDirtyFlags做相关修改,并进行重新调用绑定方法,以实现绑定表达式的更新
// ViewDataBinding.java
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver) {
// We're in LiveData registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
// 修改mDirtyFlags位
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
requestRebind后会最终又会调用executeBindings这样就实现了livedata数据变化及时更新到view中;Q4问题比较绕,需要仔细揣摩下,至此Android-DataBindging基本的几个问题搞清楚了,我们对Andorid-DataBinding有更深入理解,后面的其他延伸的东西都可以触旁类推了