databinding原理(databindingutil)

http://www.itjxue.com  2023-02-12 08:06  来源:未知  点击次数: 

ASP.NET数据绑定—多样的绑定方式

在这个系列的上篇中介绍了数据绑定语法的原理以及 NET中如何实现单向绑定 中篇我们简单的介绍了ASP NET 中新增的Bind语法配合DataSourceControl来实现数据的自动双向绑定 这两部分的内容相对动态抽象并且不常接触 没有很好的源代码支持很难解释清楚 要想真正弄清它们的内部原理 还需要大家亲自动手去反编译分析动态编译的程序集

在了解了数据绑定语法的原理后 我还想来谈谈我中实践过程中遇到的一些问题以及其它实用的绑定技巧 首先我们就来说说 特殊字段名的问题 我们知道在数据库当中 如果表名或字段名中包含有一些特殊的不能是合法的字符时 都会使用[]将它们引起来 以便他们能够正常使用 但是在%# Eval( )%的绑定语句当中 同时可以使用[] 但是对于字段名中包含 ( ) [ ] 这 个字符却始终运行出错 假设像我下面这样来绑定 电压(V)

%# Eval( 电压(V) )%

那么就会得到一个运行时错误

DataBinding: System Data DataRowView 不包含名为 电压 的属性

表明括号是被认为是一个特殊字符 那我们如果给字段名加上[] 如下

%# Eval( [电压(V)] )%

此时 我们会得到另一个运行时错误

电压(V 既不是表 DataTable 的 DataColumn 也不是 DataRelation

表明 即使加上[]也无法解决这个特殊字段名的问题 同时字段名中如果也存在中括号 也是会出现这样的问题的 但是这样的字段名却在GridView的自动生成列中能被正常绑定呢?问题会出现在哪里呢?分析和对比GridView的自动生成列与Eval这样的绑定语法在最终执行绑定代码上的不同 我们可以发现 GridView的自动生成列取值并不是使用DataBinder Eval这个方法 它内部有自己的取值方式 但是在实现上却是大同小异的 那究竟是在哪里出现了问题呢?我们找出DataBinder类的定义

: [AspNetHostingPermission(SecurityAction LinkDemand Level= )]

: public sealed class DataBinder

: {

: // Fields

: private static readonly char[] expressionPartSeparator = new char[] { };

: private static readonly char[] indexExprEndChars = new char[] { ] ) };

: private static readonly char[] indexExprStartChars = new char[] { [ ( };

:?

: // Methods

: public static object Eval(object container string expression)

: {

: if (expression == null)

: {

: throw new ArgumentNullException( expression );

: }

: expression = expression Trim();

: if (expression Length == )

: {

: throw new ArgumentNullException( expression );

: }

: if (container == null)

: {

: return null;

: }

: string[] expressionParts = expression Split(expressionPartSeparator);

: return Eval(container expressionParts);

: }

:?

: private static object Eval(object container string[] expressionParts)

: {

: object propertyValue = container;

: for (int i = ; (i expressionParts Length) (propertyValue != null); i++)

: {

: string propName = expressionParts[i];

: if (propName IndexOfAny(indexExprStartChars) )

: {

: propertyValue = GetPropertyValue(propertyValue propName);

: }

: else

: {

: propertyValue = GetIndexedPropertyValue(propertyValue propName);

: }

: }

: return propertyValue;

: }

:?

: public static string Eval(object container string expression string format)

: {

: object obj = Eval(container expression);

: if ((obj == null) || (obj == DBNull Value))

: {

: return string Empty;

: }

: if (string IsNullOrEmpty(format))

: {

: return obj ToString();

: }

: return string Format(format obj );

: }

:?

: public static object GetDataItem(object container)

: {

: bool flag;

: return GetDataItem(container out flag);

: }

:?

: public static object GetDataItem(object container out bool foundDataItem)

: {

: if (container == null)

: {

: foundDataItem = false;

: return null;

: }

: IDataItemContainer container = container as IDataItemContainer;

: if (container != null)

: {

: foundDataItem = true;

: return container DataItem;

: }

: string name = DataItem ;

: PropertyInfo property = container GetType() GetProperty(name BindingFlags Public | BindingFlags Instance | BindingFlags IgnoreCase);

: if (property == null)

: {

: foundDataItem = false;

: return null;

: }

: foundDataItem = true;

: return property GetValue(container null);

: }

:?

: public static object GetIndexedPropertyValue(object container string expr)

: {

: if (container == null)

: {

: throw new ArgumentNullException( container );

: }

: if (string IsNullOrEmpty(expr))

: {

: throw new ArgumentNullException( expr );

: }

: object obj = null;

: bool flag = false;

: int length = expr IndexOfAny(indexExprStartChars);

: int num = expr IndexOfAny(indexExprEndChars length + );

: if (((length ) || (num )) || (num == (length + )))

: {

: throw new ArgumentException(SR GetString( DataBinder_Invalid_Indexed_Expr new object[] { expr }));

: }

: string propName = null;

: object obj = null;

: string s = expr Substring(length + (num length) ) Trim();

: if (length != )

: {

: propName = expr Substring( length);

: }

: if (s Length != )

: {

: if (((s[ ] == ) (s[s Length ] == )) || ((s[ ] == \ ) (s[s Length ] == \ )))

: {

: obj = s Substring( s Length );

: }

: else if (char IsDigit(s[ ]))

: {

: int num ;

: flag = int TryParse(s NumberStyles Integer CultureInfo InvariantCulture out num );

: if (flag)

: {

: obj = num ;

: }

: else

: {

: obj = s;

: }

: }

: else

: {

: obj = s;

: }

: }

: if (obj == null)

: {

: throw new ArgumentException(SR GetString( DataBinder_Invalid_Indexed_Expr new object[] { expr }));

: }

: object propertyValue = null;

: if ((propName != null) (propName Length != ))

: {

: propertyValue = GetPropertyValue(container propName);

: }

: else

: {

: propertyValue = container;

: }

: if (propertyValue == null)

: {

: return obj ;

: }

: Array array = propertyValue as Array;

: if ((array != null) flag)

: {

: return array GetValue((int) obj );

: }

: if ((propertyValue is IList) flag)

: {

: return ((IList) propertyValue)[(int) obj ];

: }

: PropertyInfo info = propertyValue GetType() GetProperty( Item BindingFlags Public | BindingFlags Instance null null new Type[] { obj GetType() } null);

: if (info == null)

: {

: throw new ArgumentException(SR GetString( DataBinder_No_Indexed_Accessor new object[] { propertyValue GetType() FullName }));

: }

: return info GetValue(propertyValue new object[] { obj });

: }

:?

: public static string GetIndexedPropertyValue(object container string propName string format)

: {

: object indexedPropertyValue = GetIndexedPropertyValue(container propName);

: if ((indexedPropertyValue == null) || (indexedPropertyValue == DBNull Value))

: {

: return string Empty;

: }

: if (string IsNullOrEmpty(format))

: {

: return indexedPropertyValue ToString();

: }

: return string Format(format indexedPropertyValue);

: }

:?

: public static object GetPropertyValue(object container string propName)

: {

: if (container == null)

: {

: throw new ArgumentNullException( container );

: }

: if (string IsNullOrEmpty(propName))

: {

: throw new ArgumentNullException( propName );

: }

: PropertyDescriptor descriptor = TypeDescriptor GetProperties(container) Find(propName true);

: if (descriptor == null)

: {

: throw new HttpException(SR GetString( DataBinder_Prop_Not_Found new object[] { container GetType() FullName propName }));

: }

: return descriptor GetValue(container);

: }

:?

: public static string GetPropertyValue(object container string propName string format)

: {

: object propertyValue = GetPropertyValue(container propName);

: if ((propertyValue == null) || (propertyValue == DBNull Value))

: {

: return string Empty;

: }

: if (string IsNullOrEmpty(format))

: {

: return propertyValue ToString();

: }

: return string Format(format propertyValue);

: }

:?

: internal static bool IsNull(object value)

: {

: if ((value != null) !Convert IsDBNull(value))

: {

: return false;

: }

: return true;

: }

: }

其中我们可以发现有三个静态只读变量

private static readonly char[] expressionPartSeparator = new char[] { }; private static readonly char[] indexExprEndChars = new char[] { ] ) }; private static readonly char[] indexExprStartChars = new char[] { [ ( };

OK 我们先不看代码 就应该知道问题就出在这个地方 当我们分析哪里用到indexExprEndChars时分找到这个方法

public static object GetIndexedPropertyValue(object container string expr)

我们不需要阅读里面的代码 通过下面的expr参数注释我们就可以很快得到答案

expr???? 从 container 对象到要放置在绑定控件属性中的公共属性值的导航路径 此路径必须是以点分隔的属性或字段名称字符串 如 C# 中的 Tables[ ] DefaultView [ ] Price 或 Visual Basic 中的 Tables( ) DefaultView ( ) Price 它告诉我们 我们不仅可以使用字段名的方式 同时还可以使用索引下标的方式来绑定字段值(C#和VB 分别使用[]和()来取索引值) 正因为如此 我们才不可以在字段名中使用括号和中括号 如上我们假设 电压(V) 字段的索引下标是 那么我们可以像下面这样绑定 来解决特别字段名带来的问题 td%# Eval( [ ]) )%/td

上面的注释同时还告诉 我们是可以通过一个对象的导航路径如 对象 属性 子属性 的方式来绑定一个数据项的间接属性 这个我们可以通过对expressionPartSeparator静态字段的使用 得以验证

: public static object Eval(object container string expression)

: {

: if (expression == null)

: {

: throw new ArgumentNullException( expression );

: }

: expression = expression Trim();

: if (expression Length == )

: {

: throw new ArgumentNullException( expression );

: }

: if (container == null)

: {

: return null;

: }

: string[] expressionParts = expression Split(expressionPartSeparator);

: return Eval(container expressionParts);

: }

: private static object Eval(object container string[] expressionParts)

: {

: object propertyValue = container;

: for (int i = ; (i expressionParts Length) (propertyValue != null); i++)

: {

: string propName = expressionParts[i];

: if (propName IndexOfAny(indexExprStartChars) )

: {

: propertyValue = GetPropertyValue(propertyValue propName);

: }

: else

: {

: propertyValue = GetIndexedPropertyValue(propertyValue propName);

: }

: }

: return propertyValue;

: }

前面的那个Eval重载 把expression表达式用expressionPartSeparator字符分隔开 然后调用内部的Eval(object string[])重载 在这个重载中 按顺序去一级一级递归遍历属性值 最终找到最后的那个绑定字段值 所以我们是可以绑定跨级的间接属性和关联DataRowRelation行的值

还想在再来说说其它的绑定方式 李涛在它的博客浅谈 NET中的数据绑定表达式(二)中提到了绑定数据的七种方式 分别为

%#Container DataItem%%#GetDataItem()%%#Eval( 字段名 )%%#DataBinder Eval(Container DataItem 字段名 )%%#((DataRowView)Container DataItem)[ 字段名 ] %%#((Type)Container DataItem) 成员 %%#((Type)GetDataItem()) 成员 %

如果按要我来分的话 我只会分成两类 强类型绑定和反射绑定 不论是Container DataItem还是GetDataItem() 都是得到当前的正在绑定的上下文数据对象 然后转换成他们的原始类型 使用索引或强类型的方式来绑定字段值 而Eval就是使用反射的方式来进行通用化的绑定 这样我们就完全没有必要关心被绑定的数据源是什么类型 在很多场合下这是非常有益的

从性能方式来考虑 强类型绑定肯定要比反射绑定性能来得好 这其中的原因就不多作解释了 但是对于强类型来说是使用Container DataItem还是GetDataItem的方式来取得上下文数据对象 性能应该差别不大的 我们在前面已经提到到 在Page的作用域内 会把所有的被绑定(遍历的数据项或整个集合)保存在一个堆栈 方面我们来读取 我们只需要读取堆栈的顶部元素就可以方便的得到当前正在被绑定数据行项 而Container而更像是一个动态的 关键字作用的变量 因为你在绑定不同对象时Container的类型是不一样的 假设你当前正在绑定Repeater那么它的类型是RepeaterItem 只是为了方便我们强类型取得当前Repeater行对象而产生的动态属性 其实它并不是Page的一个公有或私有属性 所以我认为两种取得DataItem的方式在性能上实际是没有多大区别的

当然我们在选择是使用强类型绑定还是反射绑定时 主要还是取决你的需要 我个人认为 为了使用解决方案通用化 而不必在关心绑定的数据类型是什么类型 应尽量使用Eval的方式来绑定字段 在实践当中 绑定字段的消费上还不是非常多的 为了灵活和通用这点性能损失我认为是值得的 另外就是如上的特殊字段的情况 我当然也可以使用强类型绑定的方式来解决

%#((System Data DataRowView)Container DataItem)[ 电压(a) ]%

特殊字段的解决之道有很多 比如我们还可以重写Page的Eval方法达到我们的目的 选择哪种方案 就是取决于我们实际需要了

上面我们从特殊字段名出发 分析了DataBinder在反射取得字段值时所做的一些特殊处理 进而引出我们平常可能会被忽略的一些非常有用的绑定方式 如 索引下标绑定和间接字段绑定 而这些对于我们解决一些疑难问题会有很大的帮助 特别跨级的字段绑定 如果我们没有了解的话 可能就需要在服务器代码中做很多的类型转换和处理 最后我们还讨论了其它的几种绑定方式 以及它们各种的性能和应用场合

三天 用篇文章来分析了ASP NET在数据绑定的一个原理 其中很多内容并不是我们平常数据绑定时所需要掌握的知识 但是掌握了它们却对我们在数据绑定时 有更多的把握 正因为内容的动态性 和过于抽象 而本人又无法找到一种最为合适的语言来组织和解释这些知识 代码太多 全部贴出来又感觉找不到重点 贴重要的部分 又感觉跨度太大 所以三篇下来很多要领解释的不是很清楚 大家权当它是一个引子 更多的原理还需要大家自己亲自去分析和阅读代码

lishixinzhi/Article/program/net/201311/12894

Jetpack入门(三)viewModel介绍及dataBinding原理

在使用LifeData时,少不了observe(),在JetPack入门(二)这篇文章介绍过,在数据发生改变,如果需要更新UI,还需给数据增加一个监听,显然不够优雅,这当然不是我们希望看到的,我们希望的是数据发生改变之后UI能够自动更新,这就要用到dataBinding了,本文先从dataBinding原理开始介绍,再引出viewModel原理。

解析入口:DataBindingUtil.setContentView(this,R.layout.activity_main);

首先解析XML时会把分离成两个XML文件,目录在app/build/imtermediates/data_binding_layout_info_type_mege/对应的布局文件名称,app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/对应的布局文件名称。

前者生成的格式:

后者生成的格式和常见的格式差不多,会引用到前者的tag值。

核心原理从setVariable(id,value)开始分析。

id则是BR文件的内容。关于BR文件的生成,有如下三种情况:

1.xml中设置variable的name属性

2.viewModel继承自BaseObservable,将某个成员变量加上@Bindable注解

3.viewModel继承自BaseObservable,开头的方法加上@Bindable注解。

setUser()是自己定义在XML文件当中的,自定义的name。

注释①updateRegistration(),注册监听器。

接着去看里面做了什么处理

注释②:传进来的为空,则删除监听器。

注释③:监听器为空,mLocalFieldObservers[localFieldId]中找不到这个listener,则去创建这个listener。

接下来去看看如何注册监听的:

调用listenerCreator.create(this, localFieldId),会调用到下面的方法来创建监听器。

这边小结一下:

mLocalFieldObservers储存的是weakListener对象,在调用CreateWeakListener 时,会持有viewDataBinding的引用,也持有viewModel的引用,还会持有WeakPropertyListener的引用,registerTo()最后调用 listener.setTarget(observable),也就是WeakPropertyListener的addListener方法,里面又调用了viewModel的addOnPropertyChangedCallBack();

到这里,viewModel就持有了PropertyChangeRegistry的引用,也就建立了和ViewDataBinding的联系。

注册完成后:

注册完成后,从 notifyPropertyChanged(BR.user)来完成数据的更新。

经过notifyPropertyChanged()-notifyCallbacks-notifyRecurse-notifyRemainder-notifyFirst64-

notifyCallbacks-onNotifyCallback-PropertyChangeRegistry。这个看起来好熟悉,没错就是在注册监听时的那个类。

onNotifyCallback()在viewdataBinding中有实现。然会会回调handleFieldChange()方法。

接着 requestRebind() - mUIThreadHandler.post(mRebindRunnable) -executePendingBindings()-executeBindingsInternal() - executeBindings()【抽象方法,具体在ActivityMainBindingImpl中】;

这里就是一些二进制的移位运算,来判断控件的内容是否发生了改变,如果改变了重新设置一下控件的值。

1.XML分离成两份

2.注册监听

3.notifyPropetyChanged方法来完成界面的更新。

Android-DataBinding原理分析

在MainActivity中,调用下面的方法:

app\build\intermediates\data_binding_layout_info_type_merge\debug\out

可以看到,这里定义了多个Target标签,这些Target的定义,其实就是定义对应的tag,将tag与activity_main.xml布局中的对应的View的id对应起来

经过DataBinding变化后的布局,会多出tag。

app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml

其实DataBindingUtil的setContentView()方法,主要就是调用activity的setContentView设置布局,并且绑定添加对应的View

这里的sMapper是一个DataBinderMapper对象,其实现类是DataBinderMapperImpl

DataBinderMapperImpl是通过apt注解处理器生成的。

这里的sMapper.getDataBinder()其实就是调用的MergedDataBinderMapper的getDataBinder()方法

而sMapper中的数据,其实就是DataBinderMapperImpl的构造器中调用其父类MergedDataBinderMapper 的addMapper()方法添加的对象

在DataBinding中有两个DataBinderMapperImpl类,一个是上面这个在androidx.databinding包下,继承了MergedDataBinderMapper的,另一个是在com.example.databindingdemo应用包下,直接继承DataBinderMapper。其实MergedDataBinderMapper也是继承自DataBinderMapper

这里要注意两点,就是如果是布局的顶层View,比如tag为layout/activity_main_0,那么就会new一个ActivityMainBindingImpl对象。这个tag,其实可以从前面看到的app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml布局中的LinearLayout的tag知道

在new出ActivityMainBindingImpl对象后,则进行一些View的绑定操作,将通过tag取出的View与ActivityMainBindingImpl中对应的View属性进行绑定。

在这里,会调用了一个mapBindings方法,第三个参数是一个3,这个3的意思,就是activity_main.xml布局文件中有3个节点

mapBindings就会返回一个Object[] bindings数组。

这里的主要工作,就是将布局中的View保存在对应的bindings数组中,然后取出这个数组中的数据赋值给ActivityMainBindingImpl中的View

ActivityMainBindingImpl的父类ActivityMainBinding是在Eapp\build\generated\data_binding_base_class_source_out\debug\out\com\example\databindingdemo\databinding包下

BR的作用: 其实BR的作用,就用BR中的属性值来标记不同的操作需要的监听在mLocalFieldObservers数组中的位置

这里的localFieldId=0,这个id其实就BR文件中的id,就是BR文件中对应的静态final属性的值。而第二个就是观察者对象,比如传入的ViewModel对象。

这里通过WeakListener监听器中的ObservableReference对象保存观察者与被观察者,当被观察者发生改变的时候,就会找到对应的WeakListener监听器,然后通知观察者做修改。

而ObservableReference方法的实现,有多个,比如:WeakPropertyListener。

这里让WeakListener.setTarget()其实就是通过WeakPropertyListener给被观察者添加callback,然后当被观察者数据发生改变的时候,被观察者通过遍历其内部的PropertyChangeRegistry中的OnPropertyChangedCallback回调(其实就是WeakPropertyListener),然后通过WeakPropertyListener监听通知给ViewDataBinding以及其实现类ActivityMainBindingImpl具体进行数据的处理和设置。

// 这里的mTarget其实是一个泛型T对象,而这个泛型是在WeakPropertyListener初始化WeakListener的时候传入的一个Observable,这个是databinding中的Observable,其子类实现就是BaseObservable

WeakPropertyListener中的addListener方法,就会给Observable添加一个callback回调,向Observable这个被观察者中添加callback的目的,就是在Observable数据发生变化的时候,遍历Observable中的mCallbacks这个callback集合,通知观察者进行修改。

从这第三步可以知道:

而WeakPropertyListener和WeakListener是相互持有的对方的引用。

在完成监听的相互绑定关系,并且给Observable添加了回调之后,就会回到ActivityMainBindingImpl的setUser()方法继续执行notifyPropertyChanged()方法。

但是这里的例子有个问题,就是监听是添加在User这个BaseObservable的子类中的,但是更新的时候,并不是通过这个User来进行通知,而是根据ActivityMainBindingImpl这个BaseObservable来通知,那么这个时候并不会通过ActivityMainBindingImpl的调用notifyPropertyChanged()最终拿到User中的PropertyChangeRegistry对象mCallbacks,所以起作用的并不是这句话。而最终ActivityMainBindingImpl在设置User起刷新作用,是因为super.requestRebind()的调用也触发了mRebindRunnable任务的执行,其实就是没有通过PropertyChange来触发requestRebind()

这里其实就是调用的BaseObservable的notifyPropertyChanged()方法,因为ActivityMainBindingImpl是ViewDataBinding的子类,而ViewDataBinding继承了BaseObservable类

这里的mNotifier.notifyCallback其实就会调用到下面的PropertyChangeRegistry中定义的NOTIFIER_CALLBACK 属性中的onNotifyCallback实现,而这里的callback其实就是WeakPropertyListener,因为WeakPropertyListener是OnPropertyChangedCallback的子类,这里其实会回调给mLocalFieldObservers数组中所有的WeakListener

从mListener中取出target,而这里的mListener其实就是,WeakListener,而每个被观察者,其实都是有一个对应的LocalFieldId,这个id就是BR文件中定义的,刚才的流程中,我们传入的是0,所以这里的 mLocalFieldId=0

这里的onFieldChange的方法的实现,就是在ActivityMainBindingImpl.java中

这里因为fieldId=0,所以会进入第一个if条件if (fieldId == BR._all),所以会返回true,所以就会返回到ViewDataBinding.java中的handleFieldChange方法中,继续执行requestRebind()

这里最终都会执行mRebindRunnable的run()方法。只不过SDK版本大于等于16的时候,会采用Choreographer编舞者来处理,而之前的版本则是采用Handler来执行。

在这里最终就会执行到executeBindings()方法,而该方法的实现,又是在ActivityMainBindingImpl.java中

如果自定义类继承了BaseObservable类,则会更新注册监听。即BaseObservable保存PropertyChangeRegistry对象,该对象中会保存WeakPropertyListener监听,而WeakPropertyListener监听会持有WeakListener,WeakListener也会持有WeakPropertyListener,并且持有一个BaseObservable的target,这个target就是自定义的BaseObservable子类实现对象,在设置target的时候就会将WeakPropertyListener监听给保存在这个target中的PropertyChangeRegistry对象中,当使用自定义的BaseObservable进行更新的时候,就可以通过监听回调的方式通知到ActivityMainBindingImpl这些ViewDataBinding中,然后向ActivityMainBindingImpl解析得到的View实体中设置对应的数据。

通过WeakListener监听器中的ObservableReference对象保存观察者与被观察者,当被观察者发生改变的时候,就会找到对应的WeakListener监听器,然后通知观察者做修改。

而ObservableReference方法的实现,有多个,比如:WeakPropertyListener。

这里让WeakListener.setTarget()其实就是通过WeakPropertyListener给被观察者添加callback,然后当被观察者数据发生改变的时候,被观察者通过遍历其内部的PropertyChangeRegistry中的OnPropertyChangedCallback回调(其实就是WeakPropertyListener),然后通过WeakPropertyListener监听通知给ViewDataBinding以及其实现类ActivityMainBindingImpl具体进行数据的处理和设置。

其实就是向ViewModel或者自定义的Observable(是databinding中的Observable)的子类实现中的mCallbacks中添加监听WeakPropertyListener,用于数据变化回调。而在WeakPropertyListener中的WeakListener对象会保存这个Observable为target,用于在更新的时候取出。

比如在xml布局的data中直接使用Boolean、Integer、String等类型

如果ActivityMainBindingImpl中设置的是比如ViewModel,那么就需要看是否使用了LiveData修饰的属性,如果没有使用LiveData的,则并不会去更新注册监听信息,而只是重新保存ViewDataBinding中保存的数据实体,并且直接调用ActivityMainBinding中保存的View实体进行设置新的数据

如果是使用了LiveData的话,则会在调用LiveDataListener(这是一个Observer子类与上面的WeakPropertyListener类似)的addListener的时候,就会给LiveData注册观察者LiveDataListener,然后在LiveDataListener中的onChanged实现中通过调用ViewDataBinding的handleFieldChange方法触发数据变化修改,进而更新View显示的数据

45. Databinding原理

/Users/renzhenming/AndroidStudioProjects/RetrofitDemo/app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml

注意,这个tag很有用,源码中通过tag来遍历布局

在Activity中使用的时候,先绑定布局文件,那么这个操作都做了什么?我们从源码中找答案

在这个方法中,注意contentView是系统布局DecorView中的content节点,layoutId是当前Activity的布局id

这里childrenAdded正常情况下必须是1,因为parent是系统的content节点,而使用databing,我们自己写的布局的根结点需要是一个layout

继续往下就进入到了com.项目路径.DataBinderMapperImpl类中,在getDataBinder中可以看到,返回了ActivityMainBindingImpl

而在构造ActivityMainBindingImpl的时候可以看出,在mapBindings方法中,将布局中的子节点保存在了数组中,于是通过binding对象就可以取到这些节点了如: binding.tv1

进入ActivityMainBindingImpl中

Lifecycle+liveData+DataBinding三部曲

对Lifecycle liveData的理解:

System已经做了:Fragment/Actvity已经实现了LifecycleOwner 的接口:

Userdo:1--viewMode(或者IPresenter )implements LifecycleObserver

2--只需在Fragment/Activity中调用:getLifecycle().addObserver(viewModel);

3--viewModel.dataList.observe(owner, Observer);

1--2---原理:

Fragment/Activity的构造方法中已经调用了下列方法:

getLifecycle()返回的对象mLifecycleRegistry,在LifecycleOwner( Activity/Fragment)的生命周期方法中都做了监听,这就方便了我们Observer(viewMode或者Presenter)对LifecycleOwner生命周期的感知

--3--LiveData原理:

viewModel.dataList.observe(owner, Observer)------ LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);它实现了GenericLifecycleObserver,而GenericLifecycleObserver继承了LifecycleObserver接口。当组件(Fragment/Activity)生命周期变化时会通过onStateChanged()方法回调过来。Observer接口就是观察者,其中定义了LiveData数据变化的回调方法onChanged()。

因此我们就可以使用三部曲搞定 data对ui的生命周期的监控

Databinding就更简单了,直接跟着撸码就ok了,Fragment、Activity通过DataBindingUtil.inflate()就可以联系起来,而且可以双向绑定哦。

Lifecycle+liveData+DataBinding才是最爽的编码方式。

具体原理:[ ]

databinding的recyclerview怎么适配数据

可以。RecyclerView与ListView原理是类似的:都是仅仅维护少量的View并且可以展示大量的数据集。RecyclerView用以下两种方式简化了数据的展示和处理:使用LayoutManager来确定每一个item的排列方式。为增加和删除项目提供默认的动画效果。

(责任编辑:IT教学网)

更多

推荐新手入门文章