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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiatiandefeiyu/article/details/78929476

自从谷歌推出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 data
        mDataBinder.bind(R.id.bt0, false, mUser, new MainEventHandler(mDataBinder));

        //bind a data to multi views. but not cache
        mDataBinder.bind(new User("joker", true, "xxx_joker"));

如果某个控件你所绑定的数据发生变化的话,直接更改你所绑定的对象里面的属性值,然后通知一下,如下

   Util.changeUserName(mUser,"traditional_onClick");
            mDataBinder.notifyDataSetChanged(R.id.bt);


当然,别忘了配置文件
<DataBinding
    xmlns = "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){
        //文件id
        this.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 xml
        InputStream 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 import
        parseVariableAndImport(dataEle, dataElement);
        setDataElement(dataElement);
        //parse all custom bind
        parseBindElements(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 bug

            dataElement.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 View
            ie.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 id
                String 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 name
        if(!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)){
            //event
            mVariableCallbakMap.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;
        //referVariables
        var = 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,判断它最终选择处理数据的模式IExpression
            outInfo.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{
						 //find
						 break 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 null
		if(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 = 17
		if(StringUtil2.isFloat(str)){
			//may be only a float
			if(exprs == null) exprs = new ArrayList<IExpression>();
			exprs.add( new FloatExpr(Float.valueOf(str)) );
			return exprs;
		}
		//check int
		if(StringUtil2.isInteger(str)){
			if(exprs == null) exprs = new ArrayList<IExpression>();
			exprs.add( new IntExpre(Integer.valueOf(str)) );
			return exprs;
		}
		//check boolean
		if(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"
		//如果在/和、之下那么采用ObjectExpression
		if(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>();
			//用RResourceExpr
			exprs.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's
		LinkedList<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 !miniPaired
				lastTag = 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  
					// 如果( 还没有找到配对的 ) ignore
					if(miniPaired){
						ExpressionInfo info = obtain();
						info.dotIndex = i;
						if(lastDotIndex == INVALID_INDEX){ //first dot
							String 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 param
		if(isMethodParam){
			// one "," indicate two param. but previous only add one
			exprs.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 apply
        mDataResolver.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 exception
                applyDataInternal0(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 exception
            applyDataInternal(id, null, mTmpVariables, false);
        }

        this.mTmpVariables.clear();
        //clear datas refer
        mDataResolver.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 expression
			if(getVariable()!=null){
				// current is variable check is event handler,true to add current binding view to param
				if(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 IExpression
			if( 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 Integer
				IExpression 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 ObjectExpression
				if(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 event
				Object 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 burden
					final List<Method> ms = dataResolver.getMethod(clazz, mAccessName);//mMethod can't burden
					for (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 it
								throw 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;
			else
				throw 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();
        //background
        if(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属性判断要给控件的那个属性赋值,然后设置控件属性值。





































猜你喜欢

转载自blog.csdn.net/xiatiandefeiyu/article/details/78929476