自从谷歌推出DataBinding框架之后,MVVM开发模式也在android的端慢慢的兴起,用DataBinding框架可以省去UI和数据绑定的不少功夫,通俗的讲就是少写很多代码,并且结构看起来清晰,并利于维护,就是降低耦合,反正程序员最终的道路就是解放双手提高生产力,当然最终双手解放了,生产力上去了,也就提高了失业率。那么问题来了,android-databinding怎么帮我们进行数据绑定的呢?本文探讨重点就是寻找android-databinding到底怎么绑定的,怎么完成ViewModel(MVVM中三种对象之一(Model、ViewModel、View))的工作的.。

ok,看源码之前,先来了解一下这个框架怎么用。源码地址

首先,如果你要使用数据绑定类,得先经过工厂模式创建一个绑定类,如下

mDataBinder = DataBindingFactory.createDataBinder(this, R.raw.db_main, false);
创建完绑定类之后,直接绑定数据就ok了,如下
 mDataBinder.bind(R.id.bt, true, mUser = new User("heaven7", false));//bind onClick event and onLongClick event and not cache any datamDataBinder.bind(R.id.bt0, false, mUser, new MainEventHandler(mDataBinder));//bind a data to multi views. but not cachemDataBinder.bind(new User("joker", true, "xxx_joker"));
如果某个控件你所绑定的数据发生变化的话,直接更改你所绑定的对象里面的属性值,然后通知一下,如下
   Util.changeUserName(mUser,"traditional_onClick");mDataBinder.notifyDataSetChanged(R.id.bt);

当然,别忘了配置文件

<DataBindingxmlns = "http://schemas.android.com/heaven7/android-databinding/1"version="1.0"><data><variable name="user"  classname="com.heaven7.databinding.demo.bean.User"  type="bean"/><variable name="mainHanlder" classname="com.heaven7.databinding.demo.callback.MainEventHandler" type="callback"/><import classname="android.view.View" alias="View"/> <!-- this type of alias  can hide (but must uppercase) --></data><bind id="bt"><property name="text" referVariable="user" >@{user.username}</property><property name="textColor" referVariable="user" >user.male ? {@color/red} : {@color/random}</property><property name="backgroundColor" referVariable="user" >#00ff00</property></bind>z<bind id="bt0">zbz<property name="onClick" referVariable="user,mainHanlder" >mainHanlder.onClickChangeUsername(user)</property><property name="onLongClick" referVariable="user,mainHanlder" >mainHanlder.onLongClickChangeUsername(user)</property></bind><bind referVariable="user"><property id ="bt2" name="text" >@{user.username}</property><property id ="bt3" name="text" >user.getNickname()</property></bind></DataBinding>

规则和谷歌的绑定库类似,当然规则可以由自己定义,此处,你只需要把它当做xml文件就可以了,有关该框架的详细用法,请参考框架带的例子。

研究某个框架,我们要明确要研究的是什么,要学习它里面的哪些点,当然,比较熟练的点就不需要研究了,本文重点意在探讨框架怎么帮我们解放控件变化的代码的,也就是ViewModel怎么讲过Model层处理数据之后,而不经过接口回调给View,让它更新自己的控件,也就是说View层(这里可以理解为Activity或Frament)告诉ViewModel哪一个控件和哪种数据绑定后,后续的更改直接通过ViewModel实现,不需要我们关心里面怎么实现的。这里的DataBinding就充当着ViewModel的角色。

ok,开始源码的探讨,既然此将数据绑定的规则写在xml中,那么首当其冲的是将规则xml解析出来(即将文件映射为对象),然后在绑定数据的时候,进行解析出来的映射对象和绑定的数据、控件id做匹配,匹配成功后,将ViewModel(这里指DataBinding)中保存的View加上数据规则,从而实现数据的动态绑定(也就是说在Activity、Frament中我们没有看见View改变自身样式的一点影子)。

好,首先来看看规则文件的解析,也就是下面这句代码

   mDataBinder = DataBindingFactory.createDataBinder(this, R.raw.db_main, false)

da_main文件就是要解析的规则文件

public static IDataBinder createDataBinder(Activity activity,int bindsRawResId,boolean cacheXml){return createDataBinder(activity.getWindow().getDecorView(), bindsRawResId, cacheXml);}
public static IDataBinder createDataBinder(View root,int bindsRawResId,boolean cacheXml){return createDataBinder(new ViewHelper(root), bindsRawResId, cacheXml);}

将布局根控件传给 ViewHelper,然后继续调用,注意这里的ViewHelp类的作用是保存根控件、保存需要绑定数据的子控件(即可理解为缓存view的帮助类),最终这个简单工厂类帮我们创建了一个DataBinder类,即功能和ViewModel层的类似

public static IDataBinder createDataBinder(ViewHelper vp ,int bindsRawResId,boolean cacheXml){return new DataBinder(vp, bindsRawResId,cacheXml);}

继续跟进代码,如下

 DataBinder(ViewHelper vp ,int bindsRawResId,boolean cacheXml){//文件idthis.mBindRawResId = bindsRawResId;this.mDataBindParser = new DataBindParser(vp, new BaseDataResolver());//开始解析parseXml(vp.getContext(), bindsRawResId, cacheXml);}

在 DataBinder类中创建了DataBindParser(这个类用来解析映射数据的),最后调用parseXml开始解析xml数据

private void parseXml(Context context, int bindsRawResId,boolean cacheXml) {DataBindingElement dbe = new DataBindingElement(XmlElementNames.DATA_BINDING);//添加解析监听器dbe.addElementParseListener(mDataBindParser.getElementParserListener());if(mCacheXml!=null){dbe.parse(mCacheXml);dbe.clearElementParseListeners();return;}// parse bind xmlInputStream in = context.getResources().openRawResource(bindsRawResId);//是否开启缓存try {if (!cacheXml){dbe.parse(new XmlReader().parse(in));}else{mCacheXml = new XmlReader().parse(in);dbe.parse(mCacheXml);}dbe.clearElementParseListeners();} catch (IOException e) {throw new DataBindException(e);}finally{try {in.close();} catch (IOException e) {//ignore}}}

这里分为有缓存解析,和无缓存解析(即是否将解析数据放入缓存,保证第二次加载的时候比较快),注意这里加载 addElementParseListener的监听器用来将解析完的数据映射为对象数据。

ublic XmlReader.Element parse(Reader reader) throws IOException {try {char[] data = new char[1024];int offset = 0;while(true) {int length = reader.read(data, offset, data.length - offset);if(length == -1) {XmlReader.Element var7 = this.parse(data, 0, offset);return var7;}if(length == 0) {char[] newData = new char[data.length * 2];System.arraycopy(data, 0, newData, 0, data.length);data = newData;} else {offset += length;}}} catch (IOException var10) {throw new SerializationException(var10);} finally {StreamUtils.closeQuietly(reader);}

最后通过 XmlReader先将xml数据映射为Element ,当然这里的XmlReader作者是用了xml解析的开源库,当然解析Xml还是推荐童鞋们用android的中的Pull解析,或阿帕奇的Sax解析,Dom解析不推荐,映射为Element 之后就是将Element 转化为自己需要的对象数据了,当然该上面的监听接口起作用的了。注意上面的代码有点小问题,在扩容的data字符集合的时候,判断应该是length ==data.length。

 public boolean parse(XmlReader.Element root) {XmlReader.Element dataEle = root.getChildByName(XmlElementNames.DATA);DataElement dataElement = new DataElement(XmlElementNames.DATA);//parse var and importparseVariableAndImport(dataEle, dataElement);setDataElement(dataElement);//parse all custom bindparseBindElements(root);Array<XmlReader.Element> adapterEles = root.getChildrenByName(XmlElementNames.BIND_ADAPTER);if(adapterEles != null && adapterEles.size > 0){BindAdapterElement bae;XmlReader.Element e;for(int i=0,size = adapterEles.size ; i < size ; i++){e = adapterEles.get(i);bae = new BindAdapterElement(XmlElementNames.BIND_ADAPTER);bae.parse(e);addBindAdapterElement(bae);}}handleCallback();return true;}

首先解析的是Data标签下的VariableImport属性,如下

private void parseVariableAndImport(XmlReader.Element dataEle, DataElement dataElement) {VariableElement ve;for (XmlReader.Element e : dataEle.getChildrenByName(XmlElementNames.VARIABLE)) {// System.out.println("parseVariableAndImport(): "+e.toString());ve = new VariableElement(XmlElementNames.VARIABLE);String classname = e.getAttribute(XmlKeys.CLASS_NAME,null);checkEmpty(classname,XmlKeys.CLASS_NAME);ve.setClassname(classname.trim());String name = e.getAttribute(XmlKeys.NAME,null);checkEmpty(name,XmlKeys.NAME);ve.setName(name.trim());String type = e.getAttribute(XmlKeys.TYPE,null);// System.out.println("parseVariableAndImport(): type = "+ type);checkEmpty(type, XmlKeys.TYPE);ve.setType(type.trim());//  System.out.println("parseVariableAndImport(): type = " + ve.getType());//null? why? treemap bugdataElement.addVariableElement(ve);}ImportElement ie ;for (XmlReader.Element e : dataEle.getChildrenByName(XmlElementNames.IMPORT)) {ie = new ImportElement(XmlElementNames.IMPORT);String classname = e.getAttribute(XmlKeys.CLASS_NAME,null);checkEmpty(classname,XmlKeys.CLASS_NAME);ie.setClassname(classname.trim());String alias = e.getAttribute(XmlKeys.ALIAS,null);//can be null, eg: if android.widget.View ,  alias is Viewie.setAlias(alias == null ? classname.substring(classname.lastIndexOf(".") + 1) : alias.trim());dataElement.addImportElement(ie);}}

也就是xml文件中的下面这个标签里面的所有属性通通映射为对象和对象的属性

data><variable name="user"  classname="com.heaven7.databinding.demo.bean.User"  type="bean"/><variable name="mainHanlder" classname="com.heaven7.databinding.demo.callback.MainEventHandler" type="callback"/><import classname="android.view.View" alias="View"/> <!-- this type of alias  can hide (but must uppercase) --></data>

然后将它加到dataElement,之后将dataElement交给 DataBindingElement类管理,接下来解析Bind标签,如下

private void parseBindElements(XmlReader.Element root) {BindElement be;PropertyElement pe ;ImagePropertyElement ipe;/***  <bind id="bt"><property name="text" referVariable="user" valueType="string">@{user.username}</property><property name="textColor" referVariable="user" >user.male ? {@color/red} : {@color/green}</property></bind>  means oneView = true<bind variable="user"><property id ="bt1" name="text"  valueType="string">@{user.username}</property><property id ="bt2" name="text" >user.nickname</property></bind>means oneView = false*/boolean oneView ;final Array<XmlReader.Element> array = root.getChildrenByName(XmlElementNames.BIND);Array<XmlReader.Element> propArray ;XmlReader.Element bindEle;XmlReader.Element propEle;for( int i=0,size = array.size ; i<size ;i++){be = new BindElement(XmlElementNames.BIND);bindEle =  array.get(i);String id = bindEle.getAttribute(XmlKeys.ID, null);String refVariable = bindEle.getAttribute(XmlKeys.REFER_VARIABLE,null);if(TextUtils.isEmpty(id)){if(TextUtils.isEmpty(refVariable)){throw new RuntimeException("in BindElement attr id and refVariable can't be empty at the same time!");}oneView = false;be.setReferVariable(refVariable.trim());}else{oneView = true;be.setId(id.trim());}propArray = bindEle.getChildrenByName(XmlElementNames.PROPERTY);for( int j=0,size2 = propArray.size ; j<size2 ;j++){propEle =  propArray.get(j);pe = new PropertyElement(XmlElementNames.PROPERTY);String name = propEle.getAttribute(XmlKeys.NAME, null);checkEmpty(name,XmlKeys.NAME);pe.setName(name.trim());//check referVariable and idString referVariable = propEle.getAttribute(XmlKeys.REFER_VARIABLE, null);String propId = propEle.getAttribute(XmlKeys.ID, null);if(oneView) {checkEmpty(referVariable, XmlKeys.REFER_VARIABLE);pe.setReferVariable(referVariable.trim());}else{checkEmpty(propId, XmlKeys.ID);pe.setId(propId);}String text = propEle.getText();//text can be null?if(TextUtils.isEmpty(text)){throw new RuntimeException("text content expression can't be null in <property> element." );}pe.setText(text);be.addPropertyElement(pe);}propArray = bindEle.getChildrenByName(XmlElementNames.IMAGE_PROPERTY);for( int j=0,size2 = propArray.size ; j<size2 ;j++){propEle =  propArray.get(j);ipe = new ImagePropertyElement(XmlElementNames.IMAGE_PROPERTY);ipe.parse(propEle);if(ipe.getId() == null){ipe.setId(id);}ipe.setReferVariable(mergeReferVariable(ipe.getReferVariable(), refVariable));if(ipe.getId() ==null && ipe.getReferVariable() == null)throw new RuntimeException("view id and referVariable can't be empty at the same time");be.addPropertyElement(ipe);}if(oneView) {addBindElement(be);}else{addVariableBindElement(be);}}}

如下Bind标签

<bind id="bt"><property name="text" referVariable="user" >@{user.username}</property><property name="textColor" referVariable="user" >user.male ? {@color/red} : {@color/random}</property><property name="backgroundColor" referVariable="user" >#00ff00</property></bind>z<bind id="bt0">zbz<property name="onClick" referVariable="user,mainHanlder" >mainHanlder.onClickChangeUsername(user)</property><property name="onLongClick" referVariable="user,mainHanlder" >mainHanlder.onLongClickChangeUsername(user)</property></bind><bind referVariable="user"><property id ="bt2" name="text" >@{user.username}</property><property id ="bt3" name="text" >user.getNickname()</property></bind>

一个xml中,data标签只能有一个,而bind标签可以有多个,里面的子标签最重要的属性就是referVariable匹配Data标签当中的name属性,id必须和布局文件中的id匹配起来,否则框架找不到。如果指定id最终保存在mBindEles集合中,如果没有标志id则将id保存在mVariableBindElements集合中。最终映射的数据都交给
DataBindingElement管理,暂且叫它享元类,即享元模式,利用内存换速度。接下来是解析bindAdapter标签,方式和上两种一样,这里不再介绍,最后调用监听器的回调方法

private void handleCallback(){if(mListeners!=null){for(IElementParseListener l : mListeners){l.onParseDataElement(getDataElement());l.onParseBindElements(getBindElements());l.onParseVariableBindElements(getVariableBindElements());l.onParseBindAdapterElements(getBindAdapterElements());}}}

先调用data解析回调方法 onParseDataElement,先看看这个回调方法干了啥,

  public void onParseDataElement(DataElement e) {if(e.getImportElements()!=null){for (ImportElement ie : e.getImportElements()){doWithImportElement(ie);}}if(e.getVariableElements()!=null){for (VariableElement ie : e.getVariableElements()){doWithVariableElement(ie);}}}

遍历DataElement下面的子属性,然后对它干点啥,如下

private void doWithImportElement(ImportElement ie) {String alias = ie.getAlias();String classname = ie.getClassname();//full class nameif(!classname.contains(".")) throw new RuntimeException("class name must be full name.");if(TextUtils.isEmpty(alias)){alias = classname.substring(classname.lastIndexOf(".")+1);}mDataResolver.putClassname(alias, classname);}
private void doWithVariableElement(VariableElement ve) {if(sDebug){System.out.println("doWithVariableElement(): " + ve.toString());}final String type = ve.getType();if(VariableType.CALLBACK.equals(type)){//eventmVariableCallbakMap.put(ve.getClassname(),ve.getName());mDataResolver.addEventHandlerVariable(ve.getName());}else {mVariableBeanMap.put(ve.getClassname(), ve.getName());}}

当解析import标签的时候,获得它的别名,获取它的完整类名,最后将它加入到DataBindParser中的集合中,而Variable属性判断它的type是不是callback,只有bean和callback这两种类型,如果是callback事件,那么将它加入集合mVariableBeanMap和DataBindParser类中去。接下来对BindElement 做一些事情当然还是在回调方法里做的

 private void doWithBindElement(BindElement be) {int id = ResourceUtil.getResId(getContext(), be.getId(), ResourceUtil.ResourceType.Id);List<PropertyElement> propEles = be.getPropertyElements();if(propEles!=null && propEles.size()>0){SparseArray<Array<PropertyBindInfo>> mBindMap = this.mBindMap_viewId;final Array<PropertyBindInfo> infos = new Array<>(8);mBindMap.put(id,infos);convert2BindInfos(getContext(), propEles, infos);}}

将 PropertyElement转化为 PropertyBindInfo,然后将它加入到 mBindMap集合中,这里记住这个集合,因为在数据绑定的时候多次用到这个集合,如下它们之间的转化

public static void convert(DataBindParser.PropertyBindInfo outInfo, PropertyElement inElement) {String var;//referVariablesvar = inElement.getReferVariable();if(var!=null){outInfo.referVariables = var.trim().split(",");}outInfo.expression = inElement.getText().trim();outInfo.propertyName = inElement.getName();// outInfo.expressionValueType = inElement.getValueType();try {//根据表达式expression,判断它最终选择处理数据的模式IExpressionoutInfo.realExpr = ExpressionParser.parse(outInfo.expression);} catch (ExpressionParseException e) {throw new DataBindException("can't parse the expression of " + outInfo.expression);}}

从绑定文件xml中,可以看到绑定数据的时候,标签text中可以是五花八门,但是真正显示在View的时候,我们需要将他们转化为View想要的数据,那么ExpressionParser.parse(outInfo.expression)方法将为你选择最佳的返回数据的方式,这个框架采用了策略模式来实现这个方式,实现方式如下

public static IExpression parse(String str) throws ExpressionParseException {str = str.trim();//starts with @if(str.charAt(0) == ExpressionParser.AT)str = str.substring(1);//starts with { , end with }if(str.charAt(0) == ExpressionParser.BRACKET_BIG_LEFT &&str.charAt(1) != AT &&str.charAt(str.length()-1) == ExpressionParser.BRACKET_BIG_RIGHT ) {str = str.substring(1,str.length()-1);}if (sDebug)System.out.println("begin parse : " + str);
//xxx ? {android:anim/xxx} : xxx2// parse  ? :int index_problem = str.indexOf("?");//may have : {android:anim/xxx}, may cause bug./** just test '? :' with nested "{@android:color/holo_red_light}" :IDataResolver resolver = new BaseDataResolver();resolver.setCurrentBindingView(findViewById(R.id.bt100));color = (int) ExpressionParser.parse("{ true ? {@android:color/holo_red_light} : {@color/c_eb4e7b} }").evaluate(resolver);Logger.i("Test","test", " color = " + color);mDataBinder.getViewHelper().setTextColor(R.id.bt3, color);color = (int) ExpressionParser.parse("{ false ? {@android:color/holo_red_light} : {@android:color/holo_red_light} }").evaluate(resolver);Logger.i("Test", "test", " color = " + color);*///index of ' : 'int index_colon = -1;//find all pair of '{ }' nest '? : ' musy  use  big quote '{ }'//mean: resolved: how to differentiate ' ? :' with '{@android:color/xxx}'   ?List<int[]> bigList = findAllBigQuote(str);if(bigList != null && bigList.size() >0){int[] arr; //big quote index '{ } 'int offset;loop_out:for(int i = 0, size = bigList.size() ; i<size ;i++){arr = bigList.get(i);offset = 0;while ( (index_colon = str.indexOf(":",offset)) != -1 ){if(index_colon > arr[0] && index_colon < arr[1] ){offset = index_colon + 1;}else{//findbreak loop_out;}}}}else {index_colon = str.indexOf(":");}//check is all exist.if(index_problem * index_colon <= 0 ){throw new ExpressionParseException("'?' and ':' must exist at the same time ,"+ "or expression is incorrect");}if(index_problem > 0 && index_colon > 0){String left =  str.substring(0, index_problem);String middle =  str.substring(index_problem + 1, index_colon);String right =  str.substring(index_colon + 1);IExpression expr_left = ExpressionParserImpl.parse(left, false).get(0);IExpression expr_middle = ExpressionParserImpl.parse(middle, false).get(0);IExpression expr_right = ExpressionParserImpl.parse(right, false).get(0);return new TernaryExpression( expr_left, expr_middle, expr_right );}return ExpressionParserImpl.parse(str, false).get(0);}

代码稍微有点多,不过不用怕,代码多了就慢慢啃,这个方法首先判断表达式字符串中是否有@,有的话先截掉,然后判断剩下的是否被{}包裹,有的话也截掉,只留大括号以内的,然后获取剩下的字符串是否有?和:这是明显的三目运算符吗,如果有的话单独对它的表达式进行过渌,最后返回 TernaryExpression策略,否则调用 ExpressionParserImpl.parse方法根据过渌返回策略,不管哪一种策略,都会先调用ExpressionParserImpl.parse将策略过渌出来。看一下它都干了啥?

public static List<IExpression> parse(String str,boolean isMethodParam)throws ExpressionParseException {str = str.trim();List<IExpression> exprs = null;if(isMethodParam){exprs = new ArrayList<IExpression>();}//检验字符串是什么类型的,如果是null则用ObjectExpression//check nullif(StringUtil2.isNull(str)){if(exprs == null) exprs = new ArrayList<IExpression>();exprs.add(new ObjectExpression(null));return exprs;}//check float ,  max bits like: 20 0000 0000.666666 = 17if(StringUtil2.isFloat(str)){//may be only a floatif(exprs == null) exprs = new ArrayList<IExpression>();exprs.add( new FloatExpr(Float.valueOf(str)) );return exprs;}//check intif(StringUtil2.isInteger(str)){if(exprs == null) exprs = new ArrayList<IExpression>();exprs.add( new IntExpre(Integer.valueOf(str)) );return exprs;}//check booleanif(Boolean.TRUE.toString().equals(str)){if(exprs == null) exprs = new ArrayList<IExpression>();exprs.add( new BooleanExpr(Boolean.TRUE) );return exprs;}else if(Boolean.FALSE.toString().equals(str)){if(exprs == null) exprs = new ArrayList<IExpression>();exprs.add( new BooleanExpr(Boolean.FALSE) );return exprs;}//check constant string like "hello"//如果在/和、之下那么采用ObjectExpressionif(str.startsWith(QUOTE+"") && str.endsWith(QUOTE+"")){if(exprs == null) exprs = new ArrayList<IExpression>();exprs.add( new ObjectExpression(str.substring(1, str.length() - 1 )));return exprs;}//strc是不是资源的if(StringUtil2.isResourceReferOfR(str)){if(exprs == null) exprs = new ArrayList<IExpression>();//用RResourceExprexprs.add( new RResourceExpr(str));return exprs;}// . ,() , []ExpressionParserImpl impl = ExpressionParser.getInternalPool().obtainParser();//变成字符数组char[] chs = str.toCharArray();boolean miniPaired = true;// is inside '[]'boolean isInSquare = false;int lastDotIndex = INVALID_INDEX;int tempMiniPos = 0;int miniPos = 0;int tempSquarePos = 0;int squarePos = 0;int lastTag = 0;int lastCommaIndex = INVALID_INDEX;// "()" the position of right bracket must the nearest left bracket'sLinkedList<Integer> minBracketStack = impl.mMiniStack;// "[]"LinkedList<Integer> squareStack     = impl.mSquareStack;//new LinkedList<Integer>();//都不匹配循环判断for (int i = 0, size = chs.length; i < size; i++) {switch (chs[i]) {//检测到字符串中的有(代表方法case BRACKET_MINI_LEFT:{if(isInSquare)continue;miniPos++;minBracketStack.push(miniPos);if(miniPaired){miniPaired = false;tempMiniPos = miniPos;ExpressionInfo info = impl.getLastExpressionInfo();info.miniBracketLeftIndex = i;//方法名获取,最后一个点加一info.accessName = str.substring(lastDotIndex+1,i);}//ignore !miniPairedlastTag = TAG_MINI_LEFT;break;}//结束)case BRACKET_MINI_RIGHT:{if(isInSquare)continue;if(miniPaired){throw new ExpressionParseException("'(' and ')' must be pair of.");}if(minBracketStack.pop() == tempMiniPos){miniPaired = true;ExpressionInfo info = impl.getLastExpressionInfo();info.miniBracketRightIndex = i;}lastTag = TAG_MINI_RIGHT;break;}//[中括号规则case BRACKET_SQUARE_LEFT:squarePos ++;squareStack.push(squarePos);if(isInSquare)continue;if(lastTag == TAG_MINI_RIGHT || lastTag == TAG_DOT){//xxx.xxx()[] or xxx.xxx[]impl.getLastExpressionInfo().compactSquareLeftIndex = i;}else{throw new IllegalStateException();}isInSquare = true;tempSquarePos = squarePos;lastTag = TAG_SQUARE_LEFT;break;case BRACKET_SQUARE_RIGHT:Integer val = squareStack.pop();if(val == null)throw new ExpressionParseException("'[' and ']' must be pair of.");if( !isInSquare ) continue ;if(val.intValue() == tempSquarePos){isInSquare = false;impl.getLastExpressionInfo().compactSquareRightIndex = i;lastTag = TAG_SQUARE_RIGHT;}break;//有点出现case DOT:{if(isInSquare) continue;//Xxx.xxx()[] / .xxx()[] /.xxx[] / .xxx  // 如果( 还没有找到配对的 ) ignoreif(miniPaired){ExpressionInfo info = obtain();info.dotIndex = i;if(lastDotIndex == INVALID_INDEX){ //first dotString s = str.substring(0, i);if(StringUtil2.isFirstUpperCase(s)){info.staticClassname = s;}else{info.variableName = s;//next impl.mInfos.add(info);info = obtain();info.dotIndex = i;}//第一个dot前面不可能包含() or []}impl.mInfos.add(info);lastDotIndex = i;}lastTag = TAG_DOT;break;}//逗号,方法里面逗号间隔case COMMA:// xx.xxx(),xxxx,xxx.xxx.xxx()if(isInSquare )continue;if(miniPaired && isMethodParam){String newStr = str.substring(lastCommaIndex +1, i);//逗号以前的递归调用exprs.add(parse(newStr, false).get(0));lastCommaIndex = i;}break;}}//mMethod paramif(isMethodParam){// one "," indicate two param. but previous only add oneexprs.add( parse( str.substring(lastCommaIndex +1), false).get(0));}else{//假如是点的if(lastTag == TAG_DOT){//得到属性名String accessName = str.substring(lastDotIndex+1);ExpressionInfo info = impl.getLastExpressionInfo();if(info!=null && info.accessName == null){info.accessName = accessName;}else{info = obtain();info.accessName = str.substring(lastDotIndex+1);info.dotIndex = lastCommaIndex;impl.mInfos.add(info);}}else if(lastTag == 0){//just a variable (may be integer)ExpressionInfo info = obtain();info.variableName = str.substring(lastDotIndex+1);impl.mInfos.add(info);}}if(exprs!=null){getInternalPool().recycle(impl);return exprs;}exprs = new ArrayList<IExpression>();List<ExpressionInfo> infos = impl.mInfos;Expression previous = null;Expression first = null;for(int i=0,size = infos.size() ; i<size ;i++){ExpressionInfo info = infos.get(i);if(sDebug){System.out.println("the raw string = " +str);System.out.println("begin convert ExpressionInfo --> Expression, i = "+ i);System.out.println(info);}Expression expr = new Expression.Builder().setVariable(info.variableName).setAccessName(info.accessName).setStaticAccessClassname(info.staticClassname).setIsMethod(info.isMethod())//填入方法参数.setParamAccessInfos(info.isIncludeMethodParam() ?parse(str.substring(info.miniBracketLeftIndex + 1, info.miniBracketRightIndex), true) :null).setArrayIndexExpression(info.isArray() ? parse(str.substring(info.compactSquareLeftIndex + 1,info.compactSquareRightIndex),false).get(0) :null).build();if(i == 0){first = expr; }else{previous.setNextAccessInfo(expr);}previous = expr;}exprs.add(first);getInternalPool().recycle(impl);return exprs;}

代码量也比较大,作者注释都不加,看起来确实有点头疼,这个方法首先对字符串进行一些判断(判断是否为null,是否是float类型、是否是int、是否是资源id等等),满足这些条件直接采用以上策略。比如我设置一个字体大小如下

<property name="textSize" referVariable="user" >30</property>

可以直接返回 IntExpre策略,进行控件属性值的获取,如果以上规则都不配配的话,重新对剩余字符串进行判断,如果带着点的话,例如user.name,则将点后面的属性截取出来值赋给 ExpressionInfo,如果首字母为大写字母的话,可以认为它是class的名字,如果小写是绑定了 variable标签的name属性,如果剩下字符串带有()号的话肯定认为它是调用的方法(根据type为callback判断),然后截取需要信息(例如方法名、方法参数)进行ExpressionInfo的属性赋值,如果剩下字符串包括[],这里作者还没用这一块
,最后将ExpressionInfo放入集合 mInfos中,然后创建 Expression策略表达式, 当然这些解析都和你写xml的规则有关。

解析完规则之后,接下来就是把其他的映射类解析出来然后放在集合中,以在绑定数据的时候用,ok其它解析类似,不再细说。下面直接来看看怎么用保存在集合中的数据完成动态的绑定

mDataBinder.bind(R.id.bt, true, mUser = new User("heaven7", false));
<bind id="bt"><property name="text" referVariable="user" >@{user.username}</property><property name="textColor" referVariable="user" >user.male ? {@color/red} : {@color/random}</property><property name="backgroundColor" referVariable="user" >#00ff00</property></bind>

这句绑定正好对应以上xml中的配置,直接来看绑定的代码

public void applyData(int id, int type, boolean checkStrictly ,boolean cacheData,Object... datas) {checkDatasExist(datas);//check , put data and applymDataResolver.setCurrentBindingView(mViewHelper.getView(id));if(cacheData){final ViewHelper mViewHelper = this.mViewHelper;final IDataResolver mDataResolver = this.mDataResolver;final EventParseCaretaker caretaker = this.mEventCareTaker;final SparseArray<ListenerImplContext> mListenerMap = this.mListenerMap;final Array<VariableInfo> propVarInfos = new Array<>(4);Array<PropertyBindInfo> array = mBindMap_viewId.get(id);if(checkStrictly) {checkReferVariables(id, array, datas);}PropertyBindInfo info ;for (int i = 0, size = array.size; i < size; i++) {info = array.get(i);getReferVariableInfos(info.referVariables, datas, propVarInfos);//here  checkStrictly must be false, to ignore unnecessary exceptionapplyDataInternal0(id, propVarInfos, info, mViewHelper, mDataResolver,false, mListenerMap,caretaker);addToVariableInfoCache(id,propVarInfos,info);propVarInfos.clear();}}else {if(checkStrictly) {checkReferVariables(id, mBindMap_viewId.get(id), datas);}Array<VariableInfo> mTmpVariables = getAllVariables(datas);//here checkStrictly must be false, to ignore unnecessary exceptionapplyDataInternal(id, null, mTmpVariables, false);}this.mTmpVariables.clear();//clear datas refermDataResolver.clearObjects();}

这个方法首先根据ViewHelper中保存的根View获得子View然后将它暂时保存在 BaseDataResolver类中,前面有介绍,然后通过 mBindMap_viewId获取当前view下的所有绑定的子属性 Array<PropertyBindInfo>集合,然后遍历集合,就相当于遍历子View设置了那些属性,然后一个一个去给它的属性赋值,每个属性赋值进入这个方法

 private static void applyDataInternal0(int id, Array<VariableInfo> mTmpVariables,PropertyBindInfo info, ViewHelper mViewHelper,IDataResolver mDataResolver, boolean checkStrictly,SparseArray<ListenerImplContext> mListenerMap ,EventParseCaretaker caretaker) {if(!checkStrictly || containsAll(mTmpVariables,info.referVariables)){if(!checkStrictly) {if(sDebug) {String msg = "the property [ id = " + id + " ,name = " + info.propertyName+ "] defined in xml may couldn't be apply , you should be careful!";Logger.d(TAG, msg);}}VariableInfo varInfo;for(int j = 0, len = mTmpVariables.size ; j< len ; j++){varInfo = mTmpVariables.get(j);mDataResolver.putObject(varInfo.variableName, varInfo.data);}applyDataReally(id, 0, info, mViewHelper, mDataResolver, mListenerMap, caretaker);} else{String msg = "the property [ id = "+ id +" ,name = "+info.propertyName+"] defined in xml can't be apply ,caused by some data mapping is missing !";throw new DataBindException(msg);}}

这个集合是从data标签下的Variable标签属性name值匹配获得的mTmpVariables(保存了object和name的一一映射),然后遍历这个对象,将数据暂存到baseDataResolver中。

然后调用applyDataReally实现真正的数据绑定。

 private static void applyDataReally(int id, int layoutId,PropertyBindInfo info, ViewHelper vp,IDataResolver dr,SparseArray<ListenerImplContext> mListenerMap,EventParseCaretaker caretaker) {if(info instanceof ImagePropertyBindInfo){checkAndGetImageApplier().apply((ImageView) vp.getView(id),dr, (ImagePropertyBindInfo) info);// applyImageProperty(vp.getView(id), dr, (ImagePropertyBindInfo) info);}else {caretaker.beginParse(id, layoutId, info.propertyName, mListenerMap);final Object val = info.realExpr.evaluate(dr);caretaker.endParse();apply(vp, id, layoutId, info.propertyName, val, mListenerMap);}}

这里又分了 ImagePropertyBindInfo和普通的PropertyBindInfo,这一节暂不做区分,直接进入else,else中主要做的就是通过 info. realExpr .evaluate( dr ),也就是上面分析的策略模式将返回我们需要绑定的数据,也就是说每一个属性都会有一个策略和它相关,然后通过 evaluate方法获取参数值

 <property name="text" referVariable="user" >@{user.username}</property>

好,打个比方,也就是上面这个属性最后绑定的规则为 Expression规则类,所以直接看一下它的实现方法

public Object evaluate(IDataResolver dataResolver) throws DataBindException {try {if(sDebug){System.out.println("============== begin call ---> evaluate(IDataResolver) ===========");System.out.println("mHolder           = " + mHolder);System.out.println("staticClassname  = " + mStaticAccessClassname);System.out.println("variable         = " + mVariable);System.out.println("accessName       = " + mAccessName);System.out.println("arrayIndex       = " + mArrayIndex);}Object objHolder = null;// just variable expressionif(getVariable()!=null){// current is variable check is event handler,true to add current binding view to paramif(dataResolver.isEventHandlerOfView(mVariable) && dataResolver.getCurrentBindingView() !=null){Expression next = getNextAccessInfo();next.setIsMethod(true);final View currBindView = (View) dataResolver.getCurrentBindingView();Object tag ;if( (tag = currBindView.getTag(R.id.key_adapter_hash)) !=null) {//add item and position for item bind,// that means onClick in adapter  is (view, position, item, AdapterManager...etc)next.addExtraParamTofirst(dataResolver.getAdapterManager((Integer)tag),true);next.addExtraParamTofirst(dataResolver.getCurrentItem(), true);next.addExtraParamTofirst(dataResolver.getCurrentPosition(), true);}// make view at first onclickxxx(view v, IDataBinder b,...)next.addExtraParamTofirst(currBindView, true);}objHolder = performNextExpressionIfNeed(dataResolver,dataResolver.resolveVariable(mVariable));return objHolder;}//mHolder is IExpressionif( mHolder instanceof IExpression){objHolder = performNextExpressionIfNeed(dataResolver,((IExpression)mHolder).evaluate(dataResolver));return objHolder;}int arrayIndex =  mArrayIndexExpr!=null ?(Integer)mArrayIndexExpr.evaluate(dataResolver) :this.mArrayIndex;final Class<?> clazz = mStaticAccessClassname != null ? Class.forName(dataResolver.getClassname(mStaticAccessClassname) ): mHolder.getClass();if (mIsMethod) {final List<IExpression> mParamAccessInfos = this.mParamAccessInfos;final int len = mParamAccessInfos == null ? 0 : mParamAccessInfos.size();Object[] params = new Object[len];//mParams[i].getClass().isPrimitive() //if wrapped i don't know is int or IntegerIExpression ie ;for (int i = 0; i < len; i++) {ie = mParamAccessInfos.get(i);params[i] = ie.evaluate(dataResolver);if(sDebug){System.out.println("Param value: "+params[i]);}}//reset and clear occasional ObjectExpressionif(len > 0 ) {for (int i = 0; i < mParamAccessInfos.size(); ) {ie = mParamAccessInfos.get(i);if (ie instanceof ObjectExpression && ((ObjectExpression)ie).isOccasional() ) {ie.reset();mParamAccessInfos.remove(ie);}else{i++;}}}//invokeCallback mMethod or callback if is eventObject holder = this.mHolder;Object result = null;if(params.length > 0 && params[0] instanceof View){Method method  = ReflectUtil.getAppropriateMethod(clazz,mAccessName,ArrayUtil.getTypes(params));/*try {method = clazz.getDeclaredMethod(mAccessName, ArrayUtil.getTypes(params));}catch (NoSuchMethodException e){List<Method> ms = dataResolver.getMethod(clazz, mAccessName);final int size = ms.size();if( size == 0){throw new DataBindException("event handler must have the method name = " + mAccessName);}if(size > 1){throw new DataBindException("event handler can only have one method with the name = " +mAccessName + " ,but get " + size +"( this means burden method in event handler" +" is not support !)");}method = ms.get(0);}*/dataResolver.getEventEvaluateCallback().onEvaluateCallback(holder,method,params);}else {final boolean useStaticClassname = mStaticAccessClassname != null;//just find mMethod by mMethod name,so care burdenfinal List<Method> ms = dataResolver.getMethod(clazz, mAccessName);//mMethod can't burdenfor (int i = 0, size = ms.size(); i < size; i++) {try {if (sDebug) {System.out.println(">>>>> begin invokeCallback mMethod: " + ms.get(i));}result = useStaticClassname ? ms.get(i).invoke(null, params): ms.get(i).invoke(holder, params);break;} catch (Exception e) {if (i == size - 1) {// at last still exception,throw itthrow e;}}}if(sDebug){System.out.println(">>>>> mMethod invokeCallback: result = " +result);}if (arrayIndex != INVALID_INDEX && result!=null && result.getClass().isArray()) {objHolder = Array.get(result, arrayIndex);} else {objHolder = result;}}} else {//直接通过属性反射获取值final Field f = dataResolver.getField(clazz, mAccessName);final Object result = mStaticAccessClassname != null ? f.get(null): f.get(mHolder);if (arrayIndex != INVALID_INDEX && result.getClass().isArray()) {objHolder = Array.get(result, arrayIndex);} else {objHolder = result;}}objHolder = performNextExpressionIfNeed(dataResolver, objHolder);if(sDebug){System.out.println("============== end call ========================");}return objHolder;} catch (Exception e) {if(e instanceof DataBindException)throw (DataBindException)e;elsethrow new DataBindException(e);}}

又是代码比较多的方法,如果按上面那个属性举的例子来说的话,它只会走下面这段代码

 final Field f = dataResolver.getField(clazz, mAccessName);final Object result = mStaticAccessClassname != null ? f.get(null): f.get(mHolder);if (arrayIndex != INVALID_INDEX && result.getClass().isArray()) {objHolder = Array.get(result, arrayIndex);} else {objHolder = result;}

即利用反射取出来属性所对应的值,也就是User对象的userName属性值,还记得 dataResolver保存了我们传入的User对象了吗,so在这个地方用的。这个方法的其他的方式无非就是判断是不是callback方式,也就是说是不是绑定事件的方式,然后利用反射调用方法,比如绑定onClick事件,这个部分下一篇将做详细介绍。既然值获取完了是不是接下来就是该给控件赋值了

 public static void apply(ViewHelperImpl impl ,View v,int id , int layoutId, String propertyName,Object value, SparseArray<ListenerImplContext> mListenerMap){if(impl ==null){impl = new ViewHelperImpl(v);}final Resources res = impl.getContext().getResources();//backgroundif(PropertyNames.BACKGROUND.equals(propertyName)){ViewCompatUtil.setBackgroundCompatible(v, (Drawable) value);}else if(PropertyNames.BACKGROUND_COLOR.equals(propertyName)){v.setBackgroundColor((Integer) value);}else if(PropertyNames.BACKGROUND_RES.equals(propertyName)){v.setBackgroundResource((Integer) value);}else if(PropertyNames.TEXT.equals(propertyName)){impl.setText((CharSequence) value);}else if(PropertyNames.TEXT_RES.equals(propertyName)){impl.setText(res.getText((Integer) value));}else if(PropertyNames.TEXT_COLOR.equals(propertyName)){if(value instanceof String){impl.setTextColor(Color.parseColor((String) value));}else {impl.setTextColor((Integer) value);}}else if(PropertyNames.TEXT_COLOR_RES.equals(propertyName)){impl.setTextColor(res.getColor((Integer) value));}else if(PropertyNames.TEXT_COLOR_STATE.equals(propertyName)){impl.setTextColor((ColorStateList) value);}else if(PropertyNames.TEXT_COLOR_STATE_RES.equals(propertyName)){impl.setTextColor(res.getColorStateList((Integer) value));}else if(PropertyNames.TEXT_SIZE.equals(propertyName)){impl.setTextSize((Float) value);}else if(PropertyNames.TEXT_SIZE_RES.equals(propertyName)){impl.setTextSize(res.getDimensionPixelSize((Integer) value));}else if(PropertyNames.VISIBILITY.equals(propertyName)){impl.setVisibility((Integer) value);} else if(PropertyNames.ON_CLICK.equals(propertyName)){impl.setOnClickListener((View.OnClickListener)mListenerMap.get(getEventKey(id, layoutId, propertyName)));}else if(PropertyNames.ON_LONG_CLICK.equals(propertyName)){//helper.setVisibility(viewId, (Integer) value);impl.setOnLongClickListener((View.OnLongClickListener)mListenerMap.get(getEventKey(id, layoutId, propertyName)));}else if(PropertyNames.TEXT_CHANGE.equals(propertyName)|| PropertyNames.TEXT_CHANGE_BEFORE.equals(propertyName)|| PropertyNames.TEXT_CHANGE_AFTER.equals(propertyName)){impl.addTextChangedListener((TextWatcher)mListenerMap.get(getEventKey(id, layoutId, propertyName)));}else if(PropertyNames.ON_FOCUS_CHANGE.equals(propertyName)){v.setOnFocusChangeListener((View.OnFocusChangeListener) mListenerMap.get(getEventKey(id, layoutId, propertyName)));}else if(PropertyNames.ON_TOUCH.equals(propertyName)){v.setOnTouchListener((View.OnTouchListener) mListenerMap.get(getEventKey(id, layoutId, propertyName)));}省略N行.......

这个方法就很好理解了,判断这个属性到底是干什么的然后将上个方法获得到的值赋给控件从而达到动态绑定。

通过现象看本质,分析源码不是为了看作者怎么写,还是要学会别人的思路。那么这个思路是什么呢?第一步作者制定了自己xml规则,哪些属性是固定干什么的,规则制定好后,将xml解析出来并映射为对象。第二步将对象中标签中为text的内容利用规则为每一个属性绑定一个策略用来显示最终的绑定到控件的值。第三步利用享元模式将规则解析完的对象封装到集合中。第四步进行数据的绑定,将数据对象或事件或数据集合交给DataBinder进行缓存数据的分析,分析出传过来的对象是是不是xml里面bind标签和data标签根据name和referVariable互相映射,映射存进临时集合中。第五步取出临时集合中的数据,运用策略模式取出数据。第六步,根据property标签的name属性判断要给控件的那个属性赋值,然后设置控件属性值。

LightSun/android-databinding(第一篇属性绑定)源码剖析与思考相关推荐

  1. 第一篇:mybatis源码入门

    估计现在市面上操作数据库的多数都是用这个框架了,当然有很多也是用的plus,但是底层仍然是mybatis,这个栏目我将简单的梳理我自己看mybatis源码的一些知识,写的可能不是很深,也可能会有错误, ...

  2. jQuery 源码分析第一篇之入口源码

    目前阅读的是jQuery 1.11.3的源码,有参考nuysoft的资料.原来比较喜欢在自己的Evernote上做学习基类,并没有在网上写技术博客的习惯,现在开始学习JS的开源代码,想跟大家多交流,希 ...

  3. nginx系列第二篇:nginx源码调试

    第一篇将nginx源码从下载到运行进行了说明,这一节继续讲解如何调试nginx源代码.本人使用vscode进行调试,选择vscode是因为其比较轻巧,python/C++/C/js等开发都可以,适用性 ...

  4. 深入理解RCU | RCU源码剖析

    hi, 上次分析了RCU核心思想:深入理解RCU|核心原理,后面说会分享一篇RCU的源码剖析,其实我这边已经总结得差不多: 但自己思考了一下,发现大部分都是代码分析,这样很多人其实并不喜欢看源代码分析 ...

  5. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  6. <Android开发> Android vold - 第一篇 vold前言简介

    本系列主要介绍 Android vold,分为以下篇章 <Android开发> Android vold - 第一篇 vold前言简介 <Android开发> Android ...

  7. Android Jetpack组件之Navigation使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  8. Android Jetpack组件之 Room使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  9. Android Jetpack组件之 LiveData使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

最新文章

  1. java linux cpu 多核 负载不均匀,系统CPU负载过高、CPU使用率不高的问题
  2. 英学者研究60亿次通话记录发现:好友再多也没用,最好朋友就4个
  3. Ubuntu通过windows代理上网
  4. 100 计算机网络概述小结
  5. java gui 层次结构_javaGUI教学图形界面的层次结构.ppt
  6. python_day_5:20180720
  7. 译 | 在 Azure SQL 上节约成本的八种方法
  8. 如何快速启动MongoDB服务?
  9. 乱码问题的原理及解决方法
  10. 《手机音频》参数与选择
  11. 抖音快手短视频 影视后期制作工具网址大全
  12. Android系统基础(03) Android系统源码下载
  13. 很多次游戏的最后取胜实际上都有很强的偶然性
  14. 最全Pycharm教程(40)——Pycharm扩展功能之捆绑插件TextMate
  15. 哈希表,设计哈希集合,
  16. Android系统完整的启动流程
  17. 华为手机怎么刷android系统,怎样刷入安卓原生系统 在手机系统更新这件事上,小米华为和OPPOvivo谁更有良心...
  18. 上海富爸爸_放弃高薪选择财务自由之路(转)
  19. 批量导入Sql数据库
  20. 极客日报第 40 期:小米 11 发布,售价 3999 元起;罗永浩回应败诉半导体公司

热门文章

  1. 请介绍一下TP-Link
  2. mysql常见慢sql,MySQL中慢SQL的查询及原因分析
  3. 制作全景图的软件都有哪些?全景图怎么制作做的?
  4. 【蓝桥】第十一届软件类校内模拟赛(二)填空题部分
  5. HMAC在“挑战/响应”(Challenge/Response)身份认证的应用
  6. 什么是高并发,怎么解决高并发
  7. 计算机辅助设计课程设计评分标准,计算机辅助设计课程设计报告 - 图文
  8. 不需要无限只猴子的创新抄袭
  9. HTML-HTML块
  10. nat服务器性能,iptables的性能问题:用iptables做了web服务器的NAT,速度很慢是何解??...