Jetpack 之 DataBinding,2021Android 最新大厂面试真题
<TextViewandroid:text="@{sparse[index]}" />
<TextViewandroid:text="@{list[index]}" />
<TextViewandroid:text="@{map[key]}" />
<TextViewandroid:text='@{map["慕课 jetpack"]}' />
<TextViewandroid:text='@{set.contains("xxx")?"慕课 jetpack":key}' /></LinearLayout></layout>
DataBinding 在 xml 中数据绑定支持的语法表达式也是非常丰富的,支付在布局文件中使用一下运算符、表达式和关键字:
算术运算符:+ - * / %;
字符串连接运算符:+;
逻辑运算符:&& ||;
二元运算符:& | ^;
一元运算符: + - ! ~;
位移运算符: >> >>> <<;
比较运算符: == > < >= <= (需要被转义);
判断是否是类的实例:instanceof;
分组运算符:();
字面量运算符 - 字符,字符串、数据、null;
类型转换、方法调用;
字段访问;
数组访问: [];
三元运算符:?:;
不支持以下操作:this super new 显示泛型调用。
4.BataBinding 如何扩展 View 属性
我们知道,以前想要给 ImageView 增加几个属性,必须要写个自定义的 ImageView 在构造函数中一顿解析。那看看使用 DataBinding 如何扩展 View 属性。
public class HiImageView extends ImageView{
//需要使用 BindingAdapter 注解并标记在 public static 方法上。//value 中的字段随意添加和方法参数一一对应即可。@BindingAdapter(value = {"image_url", "isCircle"})public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle) {view.setImageUrl(view, imageUrl, isCircle, 0);}//requireAll = false 代表是否以下三个属性在 xml 中同时使用才会调用到该方法//为 false 的话,只要有一个属性被使用就能调用到该方法 @BindingAdapter(value = {"image_url", "isCircle", "radius"}, requireAll = false)public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle, int radius) {......}}
//在布局文件中如下使用,便能实现图片圆角和资源 Url 绑定的功能<org.devio.as.main.HiImageView.......app:image_url ="@{user.avatar}"app:radius="@{50}"></org.devio.as.main.HiImageView>
5.DataBinding 使用的建议
如 fragment_layout_my.xml 布局,在编译时会生成 FragmentLayoutMyImpl.java 实现类,我们可以搜索这种类 debug 跟进解决问题。
不建议在列表中乱用,因为 DataBinding 数据绑定是延迟一帧的,如果列表中的 ItemView 的宽高需要计算后才能正确展示,不建议使用 DataBinding 操作。否则会看到列表 ItemView 明显的撑开动画,体验不好。
此处可以使用 dataBinding.executePendingBindings()快速渲染布局解决
实体类配合 BaseObservable 可以友好的解决数据双向绑定的问题。
6.ViewBinding 又是什么?
Android Studio 更新到 3.6 之后,多了一个 ViewBinding 的功能,看到这个名字就感觉和 DataBinding 很相似,那么它们有什么区别呢?
DataBinding 可以将 View 和界面上的数据进行双向绑定,ViewBinding 不行,也就是不能再 xml 中绑定数据,若要使用则需要在 Gradle 中开启如下配置:
viewBinding {enabled = true}
如果你想要实现双向数据绑定,那么可以选择 DataBinding;
ViewBinding 主要是帮我们省却了 findViewById 的过程,但是它在编译阶段比 DataBinding 耗时更短;
如果你已经使用了 Kotlin,那其实 ViewBinding 就没必要使用了。
7.DataBinding 源码分析
布局文件的加载确认
XML 分离后 XML 文件位置
开发者编写的布局
<TextViewandroid:textSize="50sp"android:id="@+id/tv1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.name}"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" />
<TextViewandroid:textSize="50sp"android:id="@+id/tv2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.pwd}"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></LinearLayout>
</layout>
绑定了 @{}的 View 添加一个 tag
app/build/imtermediates/data_binding_layout_info_type_merge/debug/activity_main-layout.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?><Layout directory="layout" filePath="app\s
rc\main\res\layout\activity_main.xml"isBindingData="true" isMerge="false" layout="activity_main"modulePackage="com.example.databindingdemo_20210117" rootNodeType="android.widget.LinearLayout"><Variables name="user" declared="true" type="com.example.databindingdemo_20210117.User"><location endLine="7" endOffset="62" startLine="5" startOffset="8" /></Variables><Targets><Target tag="layout/activity_main_0" view="LinearLayout"><Expressions /><location endLine="35" endOffset="18" startLine="9" startOffset="4" /></Target><Target id="@+id/tv1" tag="binding_1" view="TextView"><Expressions><Expression attribute="android:text" text="user.name"><Location endLine="19" endOffset="38" startLine="19" startOffset="12" /><TwoWay>false</TwoWay><ValueLocation endLine="19" endOffset="36" startLine="19" startOffset="28" /></Expression></Expressions><location endLine="23" endOffset="55" startLine="14" startOffset="8" /></Target><Target id="@+id/tv2" tag="binding_2" view="TextView"><Expressions><Expression attribute="android:text" text="user.pwd"><Location endLine="30" endOffset="37" startLine="30" startOffset="12" /><TwoWay>false</TwoWay><ValueLocation endLine="30" endOffset="35" startLine="30" startOffset="28" /></Expression></Expressions><location endLine="34" endOffset="55" startLine="25" startOffset="8" /></Target></Targets></Layout>
app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" 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">
<TextViewandroid:textSize="50sp"android:id="@+id/tv1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:tag="binding_1"
app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" />
<TextViewandroid:textSize="50sp"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>
DataBindingUtil 类
setContentView
DataBindingUtil.setContentView(Activity activitiy,int layoutId);---> bindToAddedViews(bindingComponent,contentView,startChildren,layoutId)9;--->bind(compent,children,layoutId); //使用 DataBindingUtil.inflate 也是一样走到这里//sMapper 的实现类是 APT 生成的 DataBinderMapperImpl 类--->sMapper.getDataBinder(dindingComponent,root,layoutId);
APT 生成的 DataBinderMapperImpl 类
在 app/build/generated/source/kapt/debug/com.xxx.xxx/DataBinderMapperImpl 下,他是 sMapper.getDataBinder(......)的实现。
其中可以根据 layoutId,拿到每个布局的 Binding 实现。
//DataBinderMapperImpl@Overridepublic 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_ACTIVITYMAINTEST: {
if ("layout/activity_main_test_0".equals(tag)) {
return new ActivityMainTestBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main_test is invalid. Received: " + tag); }}}
布局的 Binding 的 Java 实现
在 app/build/generated/source/kapt/debug/com.xxx.xxx/databinding/ActivityMainTestBindingImpl,它是布局页面activity_main_test.xml
的具体实现,但是在调用的时候我们用的是ActivityMainTestBinding
。
public class ActivityMainTestBindingImpl extends ActivityMainTestBinding {
public ActivityMainTestBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));}
private ActivityMainTestBindingImpl(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];this.mboundView0.setTag(null);this.tv1.setTag(null);this.tv2.setTag(null);setRootTag(root);// listenersinvalidateAll();}}
ViewDataBinding
//在这里对 XML 文件信息读取,并存入数组中 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;}
源码分析
核心原理从 setVariable(id,value)开始分析
//ActivityMainTestBindingImpl@Overridepublic boolean setVariable(int variableId, @Nullable Object variable) {boolean variableSet = true;if (BR.user == variableId) {setUser((com.nearme.plugin.pay.activity.User) variable);}else {variableSet = false;}return variableSet;}
public void setUser(@Nullable com.nearme.plugin.pay.activity.User User) {//1.注册监听器 updateRegistration(0, User);
this.mUser = User;synchronized(this) {mDirtyFlags |= 0x1L;}//2.调用监听器回调 notifyPropertyChanged(BR.user);super.requestRebind();}
1.注册监听
//ViewDataBinding/**
@hide*/protected boolean updateRegistration(int localFieldId, Observable observable) {//更新观查者模式发通知需要相关的信息,CREATE_PROPERTY_LISTENER 是一个回调接口 return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);}
//注册监听 private boolean updateRegistration(int localFieldId, Object observable,CreateWeakListener listenerCreator) {if (observable == null) {return unregisterFrom(localFieldId);}WeakListener listener = mLocalFieldObservers[localFieldId];if (listener == null) {registerTo(localFieldId, observable, listenerCreator);return true;}if (listener.getTarget() == observable) {return false;}//1.删除监听器//调用 WeakPropertyListener 的 unregister 方法,然后把刚才建立的联系取消掉 unregisterFrom(localFieldId);//2.注册监听器//listenerCreator 就是之前的那个 CREATE_PROPERTY_LISTENERregisterTo(localFieldId, observable, listenerCreator);//3.注册完成 return true;}
//注销监听 protected boolean unregisterFrom(int localFieldId) {//调用 WeakPropertyListener 的 unregister 方法,然后把刚才建立的联系取消掉 WeakListener listener = mLocalFieldObservers[localFieldId];if (listener != null) {return listener.unregister();}return false;}
//注册监听 protected void registerTo(int localFieldId, Object observable,CreateWeakListener listenerCreator) {if (observable == null) {return;}WeakListener listener = mLocalFieldObservers[localFieldId];if (listener == null) {//调用 create 方法就是调用 return new WeakPropertyListener(viewDataBinding, localFieldId).getListener()listener = listenerCreator.create(this, localFieldId);mLocalFieldObservers[localFieldId] = listener;if (mLifecycleOwner != null) {listener.setLifecycleOwner(mLifecycleOwner);}}//688listener.setTarget(observable);}
public void setTarget(T object) {unregister();mTarget = object;if (mTarget != null) {//1404mObservable.addListener(mTarget);}}
private interface ObservableReference<T> {WeakListener<T> getListener();//1379//- 1446 WeakPropertyListener 中的 target.addOnPropertyChangedCallback(this);//- 40 Observable 中的 addOnPropertyChangedCallback(this);//- 32 BaseObservable 中 addOnPropertyChangedCallback//- 38 mCallbacks.add(callback);ViewDataBinding 保存在一个 List 中 void addListener(T target);void removeListener(T target);void setLifecycleOwner(LifecycleOwner lifecycleOwner);}
2.调用监听器回调
1.通知回调 notifyPropertyChanged
//BaseObservablepublic void notifyPropertyChanged(int fieldId) {synchronized (this) {if (mCallbacks == null) {return;}}//1.mCallbacks.notifyCallbacks(this, fieldId, null);}
//PropertyChangeRegistrypublic synchronized void notifyCallbacks(T sender, int arg, A arg2) {mNotificationLevel++;//2.notifiRecurse()->1.notifyRemainder()->notifyFirst64()-> notifyCallbacks()//->2.mNotifier.onNotifyCallback()notifyRecurse(sender, arg, arg2);mNotificationLevel--;if (mNotificationLevel == 0) {if (mRemainderRemoved != null) {for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {final long removedBits = mRemainderRemoved[i];if (removedBits != 0) {removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);mRemainderRemoved[i] = 0;}}}if (mFirst64Removed != 0) {removeRemovedCallbacks(0, mFirst64Removed);mFirst64Removed = 0;}}}
评论