写点什么

Jetpack 之 DataBinding,2021Android 最新大厂面试真题

用户头像
Android架构
关注
发布于: 18 小时前

<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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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;}}}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Jetpack之DataBinding,2021Android最新大厂面试真题