项目的升级-给RemoveButterKnife插件增长新功能

前言

通过项目的初步编写和进一步改造,RemoveButterKnife插件终于也有模有样了,可是,功能上仅仅支持Activity/Fragment的BindView注解。android

关于编写和优化的过程能够看下面两篇文章 项目构造RemoveButterKnifegit

项目改进-重构RemoveButterKnifegithub

固然,这里也附上这个项目的github地址正则表达式

为了让插件支持更加完全,咱们还要支持组合自定义view以及viewholder中使用butterknife的状况,固然,咱们也要支持OnClick注解以及一些其余的使用场景。bash

要增长哪些功能?

首先要肯定须要增长哪些功能点,功能点的更新以下app

  1. 增长对多moudle时的R2.id.xxx的支持
  2. 增长对OnClick注解的支持
  3. 增长对viewholder和自定义组合view的支持

肯定增长功能的前后顺序

审阅咱们的功能,发现1号功能是比较容易的,因此咱们把1号定为优先 再次思考发现,2,3号功能之间存在关联性,即,3号功能说起的支持种类中也要支持Onclick注解的形式。 因此肯定开发顺序为1->3->2ide

功能拆分

在这里咱们使用github提供的project功能,具体分解以下 能够看到,咱们分为了todo,doing,done三个部分,并且把每一个任务都细分为了几个步骤,这样咱们就能够在开发某一功能时保持专一,而不须要东写一点西写一点了函数

具体功能开发

1.对R2.id.xxxx的支持

因为咱们原来的代码中寻找匹配使用的是正则表达式,以下优化

String pattern = "^@(BindView|InjectView|Bind)\\((R.id.*)|(R2.id.*)\\)$";
   Pattern r = Pattern.compile(pattern);
复制代码

能够看到,咱们的代码已经添加了对R2.id.xxx的支持,只须要给正则表达式增长一个条件。 最后,咱们给这个功能添加上unit test,就能够完成对功能1的开发ui

2.对自定义view和viewholder的支持

首先,咱们要判别一个类究竟是自定义view仍是viewholder,对于activity和fragment很简单,由于初始函数是不一样的,一个是oncreate,一个是oncreateview,可是因为增长了支持种类,老办法就行不通了,这时咱们须要使用idea的sdk来进行判断,代码以下

GenCodeContext codeContext = new GenCodeContext(mClass, mFactory);
        String type = mClass.getSuperClassType().toString();
            if (type.contains("Activity")){
                codeContext.setStrategy(new ActivityStrategy(code,clickMap));
            }else if (type.contains("Fragment")) {
                codeContext.setStrategy(new FragmentStrategy(code,clickMap));
            }else if (type.contains("ViewHolder")||type.contains("Adapter<ViewHolder>")) {
                codeContext.setStrategy(new AdapterStrategy(code,clickMap));
            }else {
                codeContext.setStrategy(new CustomViewStrategy(code,clickMap));
            }
            codeContext.executeStrategy();
复制代码

对于原来的代码,咱们已经可以找到activity/fragment的特定位置插入代码,可是对于自定义view和viewholder,又该用什么特征来定位该在哪里插入呢? 对于这个问题咱们分状况讨论

  1. 自定义view 咱们这里讨论的自定义view仅仅针对组合view,自绘和扩展方式不作讨论,由于这两种方式通常不会使用ButterKnife. 组合自定义view的特征 对于这种自定view,最大的特征就是在构造的时候会使用inflate方法将xml文件进行压入,那么,找到inflate或者R.layout.xxx的语句,这里就是咱们插入生成后代码的位置 代码
private PsiStatement findInflateStatement(PsiClass mClass){
        PsiStatement result = null;
        PsiMethod[] methods = mClass.getAllMethods();
        for (PsiMethod method:methods) {
            for (PsiStatement statement : method.getBody().getStatements()) {
                String returnValue = statement.getText();
                if (returnValue.contains("R.layout") || returnValue.contains("LayoutInflater.from(context).inflate")) {
                    result = statement;
                    break;
                }
            }
        }
        return result;
    }
复制代码
  1. viewholder 这里说的viewholder特指recyclerview.viewholder. 这种viewholder都有一个构造函数,参数为(View xxx)第一句是super(xxx); 咱们能够基于这两个特征进行定位。
private PsiStatement findSuperStatement(PsiMethod method,String viewName){
        PsiStatement result = null;
        for (PsiStatement statement : method.getBody().getStatements()) {
            String returnValue = statement.getText();
            if (returnValue.contains("super(" + viewName + ")")) {
                result = statement;
                break;
            }
        }
        return result;
    }
复制代码

那么,既然可以识别和找到哪里插入代码了,咱们的类型支持也就水到渠成了。 在类型支持的时候,咱们使用了策略模式,这样根据类型不一样,设置不一样的策略就能够方便的进行处理。 目录结构以下

3.对onclick注解的支持

咱们对onclick的处理分如下几步

  1. 寻找注解
  2. 分析注解信息并保持
  3. 根据保存信息生成代码并插入

1.寻找注解

使用正则表达式很容易找到,这里再也不重复贴代码

2.分析注解信息并保存

onclick注解有几种状况

  1. 单id/多id的绑定
  2. 点击函数是否有参数的状况 咱们要获取的信息有如下几个
  3. 绑定的id列表
  4. 点击函数的名称,是否有参数,参数的类型 针对第二点,咱们使用一个对象将其封装起来 保存获取信息咱们使用一个Map<>来进行 代码:
@Override
    public void process() {
        String pattern = "^@OnClick\\(\\{*(R.id.*,|R.id.*|R2.id.*|R2.id.*,)+\\}*\\)$";
        Pattern r = Pattern.compile(pattern);
        for (int i = 0;i < currentDoc.length;i++){
            Matcher m = r.matcher(currentDoc[i].trim());
            currentDoc[i] = currentDoc[i].trim();
            if (m.find()) {
                method = detectMethod(currentDoc[i+1]);
                ids = detectID(currentDoc[i], method);
                methodAndIDMap.put(method,ids);
                deleteLineNumbers.add(i);
            }
        }
    }
复制代码

3.根据保存信息生成代码并插入

这步咱们须要根据保存的信息进行代码生成和插入,咱们主要讨论生成,插入部分和findviewbyid代码大同小异 咱们已经知道了注解的id和点击对应的方法,那么咱们复原的结果就应该是 findViewById(R.id.xxx).setOnclickListener(new OnclickListener(.... 咱们须要注意的地方就是点击函数是否有参数,这会影响到咱们生成的代码 看具体代码:

protected StringBuilder getMethodInvokeString(ClickMehtod method) {
        StringBuilder methodString = new StringBuilder();
        if (method.isHaveArg()){
            methodString.append(method.getName()+"(("+method.getArgType()+")"+"v);");
        }else{
            methodString.append(method.getName()+"();");
        }
        return methodString;
    }
 protected String getOnClickCode(StringBuilder methodString, String id) {
        return "findViewById("+id+").setOnClickListener(new View.OnClickListener() {\n" +
                " @Override\n" +
                " public void onClick(View v){\n"+
                methodString.toString()+
                "}"+
                "});";
    }
复制代码

到了这里,咱们的Onclick注解支持也完成了。

总结

经过对这个小小的插件的开发和重构以及功能添加,虽然项目很小,可是工程和面向对象的思想的重要性已经体现了出来,在一个拥有良好项目结构的工程下增长新功能是很是简答而明快的,若是像最第一版本那样把全部的代码写在一个文件中而没有进行逻辑拆分的话,新增功能基本等于重写项目,这确定是痛苦的。

还有一点值得一提,在作项目的时候第一步永远是整体构思,第二部是具体拆分,写代码这件事的优先级并无那么高,容易犯的一个问题就是一提到某个功能立刻就开始写具体代码,这样的结果每每费力不讨好,有一个明确的功能拆分和行进步骤会极大的加强开发体验。

至此,RemoveButterKnife系列文章就告一段落了,这几篇文章的目的不只仅是记录开发RemoveButterKnife插件中的思路和遇到的问题,更重要的是总结了做者我开发软件项目的一个历程,而把这些写下来的过程,也是巩固这段历程的重要步骤。

相关文章
相关标签/搜索