Nara: 大搜车的注解收集器

栏目: IOS · Android · 发布时间: 6年前

内容简介:本文主要是对编译时注解展开讨论。Android端对自定义注解的应用程度远没有达到Java Web端的那种程度,而且市面上也没有一个专注于抽象编译时注解底层技术的库。在真实开发场景中,如果想要实现一个利用编译时注解的功能,往往需要花大量精力来处理APT这块的繁琐逻辑。例如,遍历所有注解相关类然后生成一定规律的代码。这些繁琐操作阻碍了大家使用注解的热情。设想一下,如果使用编译时注解只需要增加一两行代码,那你会不会更愿意使用注解呢?所以,我们在想是否能够对编译时注解做进一步的抽象,帮助开发者将重点放在具体的功能

前言

本文主要是对编译时注解展开讨论。Android端对自定义注解的应用程度远没有达到Java Web端的那种程度,而且市面上也没有一个专注于抽象编译时注解底层技术的库。

在真实开发场景中,如果想要实现一个利用编译时注解的功能,往往需要花大量精力来处理APT这块的繁琐逻辑。例如,遍历所有注解相关类然后生成一定规律的代码。这些繁琐操作阻碍了大家使用注解的热情。设想一下,如果使用编译时注解只需要增加一两行代码,那你会不会更愿意使用注解呢?

所以,我们在想是否能够对编译时注解做进一步的抽象,帮助开发者将重点放在具体的功能实现上,而不是处理编译时注解的繁琐操作上?

Nara的诞生

在动手之前,需要先弄清楚大家利用注解都做了哪些事情。清楚注解的使用场景,能帮助我们抽象出一个更好用的注解框架。于是,我们调研了Android端上常用的注解框架,看看他们利用注解都做了什么事情?

  • ButterKnife:例如@BindViews,ButterKnife在编译期,利用@BindView将控件ID和类属性建立对应关系。然后在页面启动时,根据控件ID通过findViewById方法将控件赋值到类属性上。

  • EventBus:通过@Subscribe对普通方法进行标记。然后,EventBus在编译期通过@Subscribe将这些方法收集起来,作为EventBus的订阅方法。

  • Retrofit:通过反射方式处理注解,不再本文范畴。

编译时注解的主要用途在于,收集注解并通过注解信息生成一些有规律的代码。

而这些有规律的代码,一般都是可以通过其他方式来间接实现的。比如说,ButterKnife的属性赋值可以通过反射来实现,EventBus的方法收集可以直接换个写法。所以,如果要对编译时注解做进一步抽象,我们认为可以从注解收集方面入手。由此,Nara注解收集器诞生了。

API的设计

对于底层库而言,设计一套好用的API是很重要的。从库的使用者角度看,希望注解收集器能够帮助我们实现哪些功能呢?

  • 注解的作用范围:通过分析市面上编译时注解的使用场景,发现对注解的应用主要在于类,方法和属性上。
  • 自定义注解:作为注解抽象库,如果无法支持自定义注解,那对可扩展性简直就是致命的打击。所以需要提供自定义注解功能。
  • 注解收集:注解收集作为核心功能,必须要提供一套好用的收集操作。我们考虑用链式Builder来完成注解收集功能。

最终设计的API如下:

/**
 * 自定义注解API
 **/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE, ElementType.METHOD, ElementType.FIELD)
@ShadowBinding
public @interface CustomAnnotation {  
    String value() default "";
}

/**
 * 注解的使用API
 **/
@CustomAnnotation // 类
public class AnnotationClass extends Activity{}

@CustomAnnotation // 函数
public <T> T AnnotationMethod(int p1, String p2, AnnotationClass p3, T p4){  
    Log.e("souche", "test2(" + "p1(" + p1 + "),p2(" + p2 + "),p3(" + p3 + "),p4(" + p4 + "));");
    return p4;
}

@CustomAnnotation // 成员属性
public List<AnnotationClass> annotationField;

/**
 * 收集注解的使用API,以Class的收集举例
 **/
List<ClassDesc> listClass = Nara  
                .findClass(CustomAnnotation.class) // 查找类,参数为其所在注解
                .withExtends(Activity.class) // 筛选条件:查找的类,需要继承Activity类
                .withAnnotations(Collect.class) // 筛选条件:查找的类,需要被@Collect注解
                .filter(new AnnotationFilter<ClassDesc>() { // 自定义筛选条件:return true 表示过滤掉当前类
                    @Override
                    public boolean doFilter(ClassDesc obj) {
                        return false;
                    }
                })
                .list(); // 返回符合条件的类集合

实现的痛点

一个优秀的底层库,应该将复杂实现隐藏起来,并且做到实现对于上层使用者透明。下面简单分享下实现Nara时,遇到的几个技术痛点。

对泛型的支持

在收集注解信息时,会遇到方法参数或者属性中带有泛型信息的情况。为了收集这些泛型信息,我们利用了Gson实现的TypeToken泛型Type转化工具。将TypeToken的相关代码拷贝到了Nara内部。Gson获取Type的方法如下:

Type type = new TypeToken<clazz>() {}.getType();

被注解对象的包级作用域问题

Nara注解收集是支持包级作用域的信息收集的。这里就存在一个问题,包作用域的类,方法和属性是不允许跨包访问的。那么,如何在不同包下获取包级作用域下的信息呢?我们采取的方式是利用编译时注解生成被注解类的同包名下的类,然后将包级作用域的信息用public的形式暴露出来。如下所示:

// 例如包级作用域的属性
package com.example.souche.annotation;  
public class People {

    @CustomAnnotation
    String name;
}

// 为了将People.name的get和set方法的作用域暴露出来
// 我们在编译时,生成了public作用域的get和set方法
package com.example.souche.annotation;  
/** This class is generated by SouChe Annotation Collection, do not edit. */
public class CompilerCollection$AdaptableClass1540292058755_29  
                    extends com.souche.android.annotation.core.FieldDesc.BeanMethod {

            @Override
            public void set(Object... params){ 
                if (params.length != 1) throw new IllegalArgumentException("set argument format error!");
                com.example.souche.annotation.People.name = (java.lang.String)params[0];
            };

            @Override
            public Object get(Object... params){ 
                if (params.length != 0) throw new IllegalArgumentException("get argument format error!");
                return com.example.souche.annotation.People.name;
            };
}

组件化下的编译时注解问题

我们团队是采用的是模块化的开发形式,模块将代码将aar包上传到maven仓库,主工程再通过gradle来依赖模块aar包。这样就会存在一个问题,由于编译时注解的作用范围是源码级别的,所以无法对aar包中的注解进行收集。

我们的解决思路是,如果模块需要使用Nara注解收集器,在模块打包时,就将Nara相关代码打进aar包,然后在主工程打包时,通过gradle插件扫描整个app的字节码,找到Nara相关代码,将这些代码整合起来,生成一个Nara初始化主类。总体打包流程如下:

Nara: 大搜车的注解收集器

框架愿景

Nara是对编译时注解的上层抽象,我们希望通过Nara来降低编译时注解的开发成本。让使用者把精力放在逻辑实现上,而不是在注解信息收集上。


以上所述就是小编给大家介绍的《Nara: 大搜车的注解收集器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构(C语言版)

数据结构(C语言版)

严蔚敏、吴伟民 / 清华大学出版社 / 2012-5 / 29.00元

《数据结构》(C语言版)是为“数据结构”课程编写的教材,也可作为学习数据结构及其算法的C程序设计的参数教材。 本书的前半部分从抽象数据类型的角度讨论各种基本类型的数据结构及其应用;后半部分主要讨论查找和排序的各种实现方法及其综合分析比较。其内容和章节编排1992年4月出版的《数据结构》(第二版)基本一致,但在本书中更突出了抽象数据类型的概念。全书采用类C语言作为数据结构和算法的描述语言。 ......一起来看看 《数据结构(C语言版)》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具