Databinding:单双向数据绑定
Databinding:动态数据更新
Lifecycle讲解
livedata源码分析
ViewModel源码+最终组合使用
前言: 一般使用MVVM模式,大多基于databinding的,因此专门分析了一下源码,有助于开发。之前项目用到的是MVP框架,其实MVP也能实现V和M的解耦,只是MVVM在解耦的基础上实现了V和M的双向绑定,达到自动更新同步数据,使开发更加便。
话不多说,开始分析,使用网上很多了,这里不说了:
APT技术:
databinding我们用起来很简单,越简单表示框架帮我们完成了越多的东西,毫无疑问自动生成的代码是通过apt技术完成的,这个我已经在Arouter源码分析中讲过了,这里附上链接,不再赘述:APT技术
我们看看都生成了哪些文件就行了:
生成同名activity_db.xml布局
文件路径:build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_db.xml,给根布局和databinding表达式标签添加了tag属性,如下:
<Button
android:id="@+id/first_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_1"/>
生成activity_db-layout.xml布局
文件路径:build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_db-layout.xml,这个根据xml文件生成的一个layout布局文件,主要是将原布局activity_db.xml的属性补充完整,属性,是否双向绑定等等。方便java文件中遍历提取标签。如下:
<Target id="@+id/edit_text" tag="binding_3" view="EditText">
<Expressions>
<Expression attribute="android:afterTextChanged" text="user.afterTextChange">
<Location endLine="38" endOffset="61" startLine="38" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="38" endOffset="59" startLine="38" startOffset="40" />
</Expression>
<Expression attribute="android:text" text="user.editText">
<Location endLine="40" endOffset="43" startLine="40" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="40" endOffset="41" startLine="40" startOffset="29" />
</Expression>
</Expressions>
<location endLine="40" endOffset="45" startLine="34" startOffset="8" />
</Target>
这里不得不吐槽一句,一个布局文件,就给生成俩副本,这他妈,一个完成大项目,要都用这个框架得生成多少副本。。。不知道是否划算。
生成ActivityDbBinding.java 抽象类
文件路径:
build/generated/data_binding_base_class_source_out/debug/out/com/example/myapplication/databinding/ActivityDbBinding.java,因为xml文件名为activity_db.xml,所以java文件名为(ActivityDb+Binding),是一个抽象类,实体类是下面这个。
生成ActivityDbBindingImpl.java文件
文件路径:
build/generated/ap_generated_sources/debug/out/com/example/myapplication/databinding/ActivityDbBindingImpl.java,这个文件是核心类,主要负责文件装箱工作:就是获取布局文件,然后设置点击事件,发布观察者消息等等核心工作都是通过这个类完成,分析时候详解。
生成DataBinderMapperImpl.java文件
这个文件一共有两个,一个是系统的,一个是自己包名下的,通过系统mapper创建出自己的mapper,再由自己的mapper创建出上面那个XXXbindingImpl核心类。
生成BR.java文件
这个是一个静态常量文件,在Bean类中添加了@Bindable注释后的get方法,返回的字段都会在该文件中添加。
单双向数据绑定
首先来看一段简单的启动代码:
ActivityDbBinding dbBinding = DataBindingUtil.setContentView(this, R.layout.activity_db);
User user = new User("Jiang", "tao", "111");
dbBinding.setUser(user);
user.setFirstName("Sun");
DataBindingUtil的启动
DataBindingUtil是一个静态类,启动的时候有个重要的静态变量
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
-----------------------DataBinderMapperImpl类-----------------------
DataBinderMapperImpl() {
//之前讲过,这个类有两个,一个系统的,一个包名下的,这个就是系统的,然后new一个包名下的
addMapper(new com.example.myapplication.DataBinderMapperImpl());
}
-------------------------addMapper方法--------------------------------
//这是个递归方法,目的就是为了将DataBinderMapperImpl填充到mMappers这个集合中。
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);
}
}
XXXXBindingImpl核心类的创建
首先通过DataBindingUtil.setContentView开始设置布局:
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
------------setContentView方法-------------------------------
//本质上还是setContent方法,所以通过findViewById来获取布局控件也是可以的。
activity.setContentView(layoutId);
//这个是用来获取phoneWindow的根布局DecorView,id是android.R.id.content
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
------------bindToAddedViews方法----------------------------
//获取xml布局中layouet标签中的根布局,继续绑定工作
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
------------bind方法--------------------------------------
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
------------sMapper.getDataBinder方法------------------------
//这个就是从之前得到的mapper集合中获取
ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
------------mapper.getDataBinder方法------------------------
//关键的来了,这里进来new了一个ActivityDbBindingImpl核心类。
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYDB: {
if ("layout/activity_db_0".equals(tag)) {
return new ActivityDbBindingImpl(component, view);
}
}
}
}
XXXXBindingImpl核心类的初始化
类初始化工作主要做了两个工作,获取布局、注册
public ActivityDbBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
//这里有个mapBindings方法
this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
}
获取布局view
这个方法很长,其实目的只有一个,就是将xml布局中带有databinding表达式的标签填充到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;
}
//第一次进来肯定都是没绑过的,这个tag就是编译时帮忙添加的tag。
Object objTag = view.getTag();
final String tag = (objTag instanceof String) ? (String) objTag : null;
boolean isBound = false;
if (isRoot && tag != null && tag.startsWith("layout")) {
//这里添加根布局,根布局的tag是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;
}
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
//这里添加有databinding表达式的标签,都是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;
if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
bindings[index] == null) {
bindings[index] = view;
}
}
}
//这里主要目的是继续处理include标签中的布局文件,如果没有include,递归处理当前布局
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 {
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);
}
}
}
}
代替findviewById赋值
这个方法结束就获取到了所有带databinding表达式的bindings布局数组集合。然后继续初始化工作。
private ActivityDbBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
//这里将XML中带有ID的标签都进行赋值操作,传给父类ActivityDbBinding,这里就是代替了findviewById方法。
super(bindingComponent, root, 1
, (android.widget.EditText) bindings[3]
, (android.widget.Button) bindings[2]
, (android.widget.TextView) bindings[4]
);
//这里将XML中没有ID的,但是有databinding表达式的在当前类中赋值。
this.editText.setTag(null);
this.lastName.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.Button) bindings[1];
this.mboundView1.setTag(null);
setRootTag(root);
// listeners
mCallback1 = new com.example.myapplication.generated.callback.OnClickListener(this, 1);
invalidateAll();
}
单向数据绑定
在xml布局中通过@{}其实就是单向数据绑定,@={}就是双向数据绑定,使用起来非常方便,
怎么实现的呢,我们分析一下:
接着上面部分代码继续跳转
invalidateAll();
----------->requestRebind();
//这个方法是关键,调用requestRebind方法将会启动run任务
----------->mRebindRunnable.run();
----------->executePendingBindings();
----------->executeBindingsInternal();
----------->executeBindings(); //最终调用这个方法,实现界面刷新。
run任务的触发机制:
1、当ViewDataBinding进行类加载的时候,当sdk>=19的时候,将会创建attachStateChange监听器,看过系统绘制源码的都知道,这个监听在界面绘制的时候,也就是ViewRootImpl中的performTraversales()方法中开始执行。
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) {
}
};
}
}
2、当ViewDataBinding类执行构造函数时,当api>16的时候将会创建Choreographer监听器,当一下帧执行的时候,讲执行run方法。
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount){
...............
if (USE_CHOREOGRAPHER) {
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
} else {
mFrameCallback = null;
mUIThreadHandler = new Handler(Looper.myLooper());
}
}
通过以上两点,使用databinding框架应该会存在界面多次渲染的问题。
继续分析executeBindings()方法,这个也就是实现数据绑定的方法,我们在XML中直接使用user.firstName获取数据,这里其实帮我们实现了get方法,我们修改M层数据后,调用这个方法,其实就是执行了setText方法。实现其实还是这么实现,就是不用自己写了。
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String userFirstName = null;
com.example.myapplication.bean.User user = mUser;
com.example.myapplication.bean.UserAction action = mAction;
java.lang.String userEditText = null;
if ((dirtyFlags & 0x3dL) != 0) {
if ((dirtyFlags & 0x25L) != 0) {
if (user != null) {
userFirstName = user.getFirstName();
}
}
if ((dirtyFlags & 0x31L) != 0) {
if (user != null) {
userEditText = user.getEditText();
}
}
}
if ((dirtyFlags & 0x31L) != 0) {androidx.databinding.adapters.TextViewBindingAdapter.setText(this.editText, userEditText);
}
if ((dirtyFlags & 0x25L) != 0) TextViewBindingAdapter.setTextWatcher(this.editText, (BeforeTextChanged)null, (OnTextChanged)null, (AfterTextChanged)uAfterTextChanged, TextAttrChanged);
}
if ((dirtyFlags & 0x26L) != 0) {androidx.databinding.adapters.TextViewBindingAdapter.setText(this.firstName, userFirstName);
}
}
双向数据绑定
当我们在XML布局中使用@={}时,系统偷偷做了这么几件事:
1、当我们在xml中使用了AfterTextChanged时,这里添加了一个实现类AfterTextChangedImpl;其中value.afterTextChange(arg0)方法是自定义value类中的自定义afterTextChange方法,arg0是参数。
public static class AfterTextChangedImpl implements androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged{
private com.example.myapplication.bean.User value;
public AfterTextChangedImpl setValue(com.example.myapplication.bean.User value) {
this.value = value;
return value == null ? null : this;
}
@Override
public void afterTextChanged(android.text.Editable arg0) {
this.value.afterTextChange(arg0); //
}
}
2、然后在get方法中创建了这个实例,傻逼系统的命名很长,我这里缩减了一下,没有就创建,有的话就是调用serValue方法,间接调用了我们在自定义类中的自定义方法。
mAfterTextChanged = (((mAfterTextChanged== null) ? (mAfterTextChanged = new AfterTextChangedImpl()) : mAfterTextChanged).setValue(user));
3、最后,通过TextViewBindingAdapte的setTextWatcher方法,其实就是调用了TextWatcher方法中对应的方法,中转了一下,最终调用了步骤1中的afterTextChanged方法,将值抛了出去。
TextViewBindingAdapter.setTextWatcher(this.editText, (TextViewBindingAdapter.BeforeTextChanged)null, (TextViewBindingAdapter.OnTextChanged)null, (TextViewBindingAdapter.AfterTextChanged)mAfterTextChanged , editTextandroidTextAttrChanged);