当前位置:首页 > 技术文章 > 正文内容

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

u3blog2年前 (2022-10-08)技术文章454

前言

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

关于编写和优化的过程可以看下面两篇文章
项目构造RemoveButterKnife

项目改进-重构RemoveButterKnife

当然,这里也附上这个项目的github地址

为了让插件支持更加彻底,我们还要支持组合自定义view以及viewholder中使用butterknife的情况,当然,我们也要支持OnClick注解以及一些其他的使用场景。

要增加哪些功能?

首先要确定需要增加哪些功能点,功能点的更新如下

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

    确定增加功能的先后顺序

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

    功能拆分

    在这里我们使用github提供的project功能,具体分解如下
    undefined
    可以看到,我们分为了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的开发

    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,又该用什么特征来定位该在哪里插入呢?
    对于这个问题我们分情况讨论
  4. 自定义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;
     }
  5. 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;
     }
    那么,既然能够识别和找到哪里插入代码了,我们的类型支持也就水到渠成了。
    在类型支持的时候,我们使用了策略模式,这样根据类型不同,设置不同的策略就可以方便的进行处理。
    目录结构如下
    undefined

    3.对onclick注解的支持

    我们对onclick的处理分以下几步
  6. 寻找注解
  7. 分析注解信息并保持
  8. 根据保存信息生成代码并插入

    1.寻找注解

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

    2.分析注解信息并保存

    onclick注解有几种情况
  9. 单id/多id的绑定
  10. 点击函数是否有参数的情况
    我们要获取的信息有以下几个
  11. 绑定的id列表
  12. 点击函数的名称,是否有参数,参数的类型
    针对第二点,我们使用一个对象将其封装起来
    保存获取信息我们使用一个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插件中的思路和遇到的问题,更重要的是总结了作者我开发软件项目的一个历程,而把这些写下来的过程,也是巩固这段历程的重要步骤。

扫描二维码推送至手机访问。

版权声明:本文由u3blog发布,如需转载请注明出处。

本文链接:https://u3blog.xyz/?id=693

分享给朋友:

“项目的升级-给RemoveButterKnife插件增加新功能” 的相关文章

开源数据库Postgresql安装/卸载总结

What Postgresql一个开源数据库,类似mysql,由于mysql被收购了,这个数据库正被越来越多的使用 怎么安装?安装非常简单,但是安装过后的初始化还是有点麻烦,具体可以看这篇文章为什么要卸载?安装好之后,如果你很倒霉的话,会遇到postgresql服务怎么都启动不了,输入psql指令显...

AndroidStudio插件开发——RemoveButterKnife从构思到实现

AndroidStudio插件开发——RemoveButterKnife从构思到实现

ReomveButterKnife插件这是一个用于移除代码中对ButterKnife使用的AS插件,接下来我们将从头开始讲讲AS插件开发和这个插件的开发过程地址是<a href="https://github.com/u3shadow/RemoveButterKnife"...

项目的改造——RemoveButterKnife插件代码的重构

项目的改造——RemoveButterKnife插件代码的重构

前言这篇文章记述了我的插件RemoveButterKnife的代码改进过程以及思路,关于插件,各位可以看RemoveButterKnife代码库,关于文章,可以看构思到实现RemoveButterKnife 原因近期想给原来的插件RemoveButterKnife加入一些新的功能,发现以前的代码没...

Android测试体系-在MVVM架构中如何测试Model层与ViewModel层

背景此文章是对于google code lab中《Introduction to Test Double and Dependence injection》 与 《Testing Basics》的总结,本篇主要讲述如何在mvvm架构的android项目中对Model层以及ViewModel层进行测试...

使用Databinding为Recyclerview使用同一个ViewHolder加载不同Item

提示:在阅读本篇文章前,你最好对android databinding有一定了解,本文使用的代码均为kotlin,但是不用担心,都很简单 最近在写项目的时候使用了databinding技术,突发奇想,databinding是不是也能应用于recyclerview中,让加载多个不同的item更简单呢...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。