-
概述
-
本例分以DataBinding在Java,Kotlin两门语言的使用入手,浅谈了DataBinding运行机制;是如何实现双向绑定?为什么Kotlin中不能使用注解?DataBinding在哪里消耗了内存?等问题
-
源码分析:DataBinding_Java版本
-
Model层中的BR文件:在其中会为每维护的字段添加整数标记,内部借助此标记完成数据流动
-
示意图:BR文件源代码
-
示意图:可以在Model层发送BR.字段时,使用整数标记做替代;不推荐,使用BR.字段可以减少容错性
-
-
DataBinding是如何完成View层与Model牵手的:作为中间层,充当VM层角色
-
VM层与View层绑定:
final ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main); 复制代码
-
VM 层与Model层绑定
//模拟Model数据:假设是从网络上过来的json数据,解析成了JavaBean User user; user = new User("wasbry","123456"); binding.setUser(user);//建立与DataBinding的绑定关系,否则没有任何效果 复制代码
细节:user = new User(“wasbry”,”123456″);会为View层提供默认值
android:text="@{user.name}" android:text="@{user.pwd}" 复制代码
-
-
数据驱动UI细节:
-
当VM层建立起双向绑定后
- 数据驱动UI:仅需要开启一个线程对Model层数据修改—>UI更新
- UI修改数据:通过日志形式打印用户输入内容
-
代码展示:
//Model--->View new Thread(new Runnable() { @Override public void run() { for(int i = 0;i < 10;i++){ try { Thread.sleep(1000); user.setName("第"+i+"次改变用户名"); user.setPwd("第"+i+"次改变用户名"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); 复制代码
-
-
数据流动顺序:为什么不再需要findViewById
-
在VM层中更新字段
user.setName("第"+i+"次改变用户名"); 复制代码
-
跳转到Model层
notifyPropertyChanged(BR.name); // APT又是主接处理器技术 BR文件 复制代码
-
跳转到View层
android:text="@{user.name}" 复制代码
-
-
布局拆分细节:
-
流程分析:DataBinding读取交给其管理的布局,通过里面的tag属性找到拆出的另外一半(Android View绘制的)
-
activity_main.xml:交给DataBinding进行管理
<?xml version="1.0" encoding="utf-8"?> <!--DataBinding 管理了整个布局文件,但是将其拆分成了两部分--> <!--拿给DataBinding内部使用--> <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.wasbry.myapplication.User" /> </data> <!-- 拿给Android做View绘制使用--> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}" android:textSize="50sp" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.pwd}" android:textSize="50sp"/> </LinearLayout> </layout> 复制代码
-
activity_main-layout.xml:DataBinding内部管理的布局
-
路径:build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml
-
细节:使用Tag,拿到布局中的控件(布局越复杂,拆出给DataBinding管理的布局越复杂)
<Targets> <Target tag="layout/activity_main_0" view="LinearLayout"> <Expressions /> <location endLine="35" endOffset="17" startLine="15" startOffset="3" /> </Target> <Target id="@+id/tv1" tag="binding_1" view="TextView"> <Expressions> <Expression attribute="android:text" text="user.name"> <Location endLine="24" endOffset="38" startLine="24" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="24" endOffset="36" startLine="24" startOffset="28" /> </Expression> </Expressions> <location endLine="26" endOffset="13" startLine="20" startOffset="8" /> </Target> <Target id="@+id/tv2" tag="binding_2" view="TextView"> <Expressions> <Expression attribute="android:text" text="user.pwd"> <Location endLine="32" endOffset="37" startLine="32" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="32" endOffset="35" startLine="32" startOffset="28" /> </Expression> </Expressions> <location endLine="33" endOffset="36" startLine="28" startOffset="8" /> </Target> </Targets> 复制代码
-
-
activity_main.xml:原布局拆分出,交给Android绘制UI使用的
-
路径:build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
-
细节:为每个控件增添了tag,与从原布局拆出,交给DataBinding管理的里面的tag属性相呼应
<TextView …… android:tag="binding_1" /> <TextView …… android:tag="binding_2" /> 复制代码
-
-
-
-
为什么可以废弃setContentView
-
使用ActivityMainBinding指代布局文件:
final ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main); 复制代码
-
证明指代:点击它(CTRL+H鼠标左键),发现直接都将绘制的布局文件自动全选
-
-
废除原有的setContentView语句
// setContentView(R.layout.activity_main); 复制代码
-
实际上还是在内部调用了的
-
-
setContentView分析:调用bindToAddedViews
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity, int layoutId, @Nullable DataBindingComponent bindingComponent) { //根据布局id,加载布局 activity.setContentView(layoutId); //有Activity得到根布局Window,方便遍历 View decorView = activity.getWindow().getDecorView(); ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); //深层调用 return bindToAddedViews(bindingComponent, contentView, 0, layoutId); } 复制代码
-
bindToAddedViews:
-
无论是有多少个子View都会调用到:bind
//一个孩子 return bind(component, childView, layoutId); //多个孩子 return bind(component, children, layoutId); 复制代码
-
-
bind分析
-
将布局信息:DataBindingComponent bindingComponent, View root,int layoutId 传入
-
调用:getDataBinder
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); 复制代码
-
-
getDataBinder:抽象函数
public abstract ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId); 复制代码
-
点一下左边行号绿色接口标志,选择第一个进行跳转
-
示意图:
-
这里是有一个坑的:提示源码不全,需要下载(这个是不会成功的,因为下载的是一个arr,其包含了jar包)
-
由于源码不全,所以不再分析这个代码,需要跳转:
- 路径:package androidx.databinding.library.baseAdapters;
- 文件名:DataBinderMapperImpl .class
-
需要跳转到同名的文件:源码不全
- 因为aar包裹的代码会有一份APT代码在build/generated/ap_generated_sources/debug/out
- 路径:build/generated/ap_generated_sources/debug/out/com/wasbry/myapplication/DataBinderMapperImpl.java
- 文件名:DataBinderMapperImpl .java
-
所以说抽象函数在跳转后会调用到DataBinderMapperImpl .java
-
-
-
DataBinderMapperImpl.java分析
-
首先会获取到tag对应的布局(原布局拆出拿给Android绘制View的):为了拿到里面的所有控件
case LAYOUT_ACTIVITYMAIN: { if ("layout/activity_main_0".equals(tag)) { //将信息包裹后发送出去 return new ActivityMainBindingImpl(component, view); } 复制代码
-
-
-
ActivityMainBindingImpl分析
-
构造函数一:深挖到对象数组
- 为什么是3,因为这个有3个控件的tag,10个控件就是10
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds)); } 复制代码
-
构造函数二:触发静态代码块
- ActivityMainBindingImpl会继承ViewDataBinding的,只是说看不到而已
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { //就是这个super super(bindingComponent, root, 2 , (android.widget.EditText) bindings[1] , (android.widget.EditText) bindings[2] ); 复制代码
-
mapBindings分析:将布局中所有控件打包成对象数组(一个控件就是一个对象),带来内存消耗
-
其实比findViewById慢
protected static Object[] mapBindings 复制代码
-
寻找layout 开头的,就是找根布局,
-
-
静态代码块(第二个非常消耗内存,Model—>View数据驱动UI):为每一个控件添加了监听,十分消耗内存
static { if (VERSION.SDK_INT < VERSION_CODES.KITKAT) { ROOT_REATTACHED_LISTENER = null; } else { //为每一个控件添加了监听,十分消耗内存 ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() { 复制代码
- 最终会开启线程,不断回调,最后拿到了tag对应的控件,调用了setText,就是跟离谱的只是说变成了实时监听
- 调用到excute,这个函数再回调到回去,在里面发现居然是setText
-
第三个消耗内存的点(View—>Model)
-
在xml中:就是这个等号
<EditText …… android:text="@={user.nameF}" /> 复制代码
-
会生产一个textWatcher监听,每个控件都有一个监听,这个是以控件的tag作区别的
-
-
-
为什么View触发Model,Model触发View不会导致死循环?
- 里面在user.setName(java.lang.String):就将这个事件消耗了,类似于责任链模式
- 因为是APT技术,会动态生成代码的
-
DataBinding的核心
- 使用APT技术,扫描布局文件,生成了两个布局文件的;
源码分析:DataBinding实现数据驱动UI(Kotlin 版本)
-
Model层中为什么不能使用注解@Bindable
-
因为注解是拿给方法或者字段使用的
-
而kotlin中的每个字段是有默认的get/set方法的,也可以进行重写字段的get/set
var name : String ?= null get() { return field } set(value : String ?) { field = value } 复制代码
-
但是重写出来的get/set方法只是一个表达式而已,根本就不适用
-
-
Kotlin配合DataBInding使用何种方法维护Model层数据
-
ObservableField+泛型+懒加载
-
代码:
class User{ //继承BaseObservable()失效,这个在java里面可以,但是在kotlin中就不行了 val nameF : ObservableField<String> by lazy { ObservableField<String>() } val pwdF : ObservableField<String> by lazy { ObservableField<String>() } } 复制代码
-
-
Kotlin版本中是如何建立起双向绑定的?
-
Model—>View:
//模拟Model层数据(json解析后得到的javaBean) private val user = User() val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main) binding.user = user 复制代码
-
View—>Model:注意玩双向绑定一定是等号
//xml中 <EditText …… android:text="@={user.nameF}" /> <EditText …… android:text="@={user.pwdF}" /> //VM层中 Log.d("MainActivity","用户输入第一个字段为${user.nameF.get()}," + "用户输入第二个字段为${user.pwdF.get()}") 复制代码
所谓数据驱动UI,UI驱动数据;不过是操作绑在DataBinding上的Model层数据
-