Android AOP 编程实践 javapoet + autoService

什么是AOP?

AOP面向切面编程,就是在代码预编译阶段,在不修改源代码的情况下,给程序添加某一功能。

像成熟的框架,ARouter,ButterKnife等也都使用了这个技术。任何技术的出现都有其实际应用场景,为了解决某一方面的痛点。AOP的出现让某些功能组件的封装更加解耦,使用者能够更加的方便的使用组件里的功能。

拿ButterKnife举例,我们原生开发,以前经常写很多findViewById的代码,显然这类代码写起来很繁琐,且容易出错(id和view有时候没对上)。而AOP可以有效避免这些问题。

比如我们可以通过在预编译的阶段解析注解,然后生成对应的java文件,该java文件封装了findviewbyid的方法,实现view和id的动态绑定。这样就非常有效减少了后期的编写代码的工作量,可以快速实现view和id的绑定操作。

AOP编程思路:

一.定义注解:自定义注解参考下面例子即可,default表示该字段不是必传字段,下面例子的自定义注解在使用的时候必须传入name。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Laucher {
    String name();
    boolean async() default false;
    int priority() default -1;
    String[] preTasks() default {};
}
元注解     说明
@Target     定义该自定义注解类型。
@Retention     这个注解的的存活时间
@Document     表明注解可以被javadoc此类的工具文档化
@Inherited     是否允许子类继承该注解,默认为false

@Target   类型     说明 
ElementType.TYPE     接口、类、枚举、注解
ElementType.FIELD     字段、枚举的常量
ElementType.METHOD     方法
ElementType.PARAMETER     方法参数
ElementType.CONSTRUCTOR     构造函数
ElementType.LOCAL_VARIABLE     局部变量
ElementType.ANNOTATION_TYPE     注解
ElementType.PACKAGE     包  

@Retention                  说明
RetentionPolicy.SOURCE     注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
RetentionPolicy.CLASS     注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
RetentionPolicy.RUNTIME     注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在

二.编译阶段解析注解:创建一个java-library(不是androd library),实现自定义Processor,注册Processor。引入auto-service可以实现自动注册。rebuild之后,在build目录会生成javax.annotation.processing.Processor文件,这个文件能看到你注册的Processor。

dependencies {
    implementation project(path: ':xlauncher-annotation')
    implementation 'com.squareup:javapoet:1.8.0'

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
    compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
}
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.azx.xlauncher_annotation.Laucher"})
@AutoService(Processor.class)
public class LauncherProcessor extends AbstractProcessor {

    private Map taskMap = new HashMap<>();
    private String inputParamsName = "taskMap";

    private Filer filer;
    private Writer docWriter;
    private Logger logger;
    Elements elementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        logger = new Logger(processingEnv.getMessager());
        filer = processingEnv.getFiler();
        elementUtils = processingEnv.getElementUtils();
        try {
            docWriter = filer.createResource(StandardLocation.SOURCE_OUTPUT,
                    PACKAGE_OF_GENERATE_DOCS,
                    PACKAGE_OF_GENERATE_DOCS+".json").openWriter();
        } catch (IOException e) {
            logger.error(e);
        }
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        Set launcherElements = roundEnvironment.getElementsAnnotatedWith(Laucher.class);
        parseElements(launcherElements);
        try {
            createJavaDoc(launcherElements);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    private void createJavaDoc(Set elements) throws IOException {
        ParameterizedTypeName mapTaskLauncher = ParameterizedTypeName.get(
                ClassName.get(HashMap.class),
                ClassName.get(String.class),
                ClassName.get(LauncherTask.class)

        );
        ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build();
        MethodSpec.Builder method = MethodSpec.methodBuilder(METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(paramSpec);
        addCodeIntoMethod(method, elements);

        JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(CLASS_NAME)  //class文件名字
                        .addModifiers(Modifier.PUBLIC)  //公有方法
                        .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITASK_MANAGER)))
                        .addJavadoc("@Launcher注解自动生成的java文件")
                        .addMethod(method.build())
                        .build()
        ).build().writeTo(filer);
    }

    private void addCodeIntoMethod(MethodSpec.Builder method, Set elements) {
        ClassName classTask = ClassName.get(LauncherTask.class);
        Set keys = taskMap.keySet();

        for (Element item : elements) {
            Laucher annotation = item.getAnnotation(Laucher.class);
            String key = annotation.name();
            logger.info(key + "  " + ClassName.get((TypeElement)item));

            method.addStatement("$T.build($S, $T.class, " + taskMap.get(key).getPriority() + "," + taskMap.get(key).isAsync() + ", " + createListMethod(taskMap.get(key).getPreTaskList()) + ")",
                    classTask,
                    taskMap.get(key).getTaskName(),
                    ClassName.get((TypeElement)item));
        }
    }

    private String createListMethod(List list) {
        List test = new ArrayList() {{
            add("a");
        }};
        StringBuilder builder = new StringBuilder();
        for (String item : list) {
            builder.append("add(\"" + item + "\"); ");
        }
        return "new java.util.ArrayList(){{" + builder.toString() + "}}";
    }

    private void parseElements(Set elements) {
        taskMap.clear();
        for (Element item : elements) {
            LauncherTask launcherTask = new LauncherTask();
            Laucher annotation = item.getAnnotation(Laucher.class);
            launcherTask.setTaskName(annotation.name());
            launcherTask.setAsync(annotation.async());
            launcherTask.setPriority(annotation.priority());
            launcherTask.setPreTaskList(Arrays.asList(annotation.preTasks()));
            taskMap.put(launcherTask.getTaskName(), launcherTask);
        }
    }
}

三.生成java文件:

我们可以看上面的LauncherProcessor类的createJavaCode的方法,里面的代码是整个生成java文件的逻辑。

我们先通过parseElements方法获取使用到Laucher注解的Elements,然后解析Laucher注解内部的参数。

接下来我们一步步阅读代码

ParameterizedTypeName mapTaskLauncher = ParameterizedTypeName.get(
        ClassName.get(HashMap.class),
        ClassName.get(String.class),
        ClassName.get(LauncherTask.class)

);  
//这个代码实际上就是创建一个参数类型,对应的代码是HashMap
private String inputParamsName = "taskMap";
ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build(); 
//这个代码就是生成一个参数,对应的java代码是
HashMap taskMap

//下面的代码是构建一个方法对象,public属性,入参是HashMap taskMap,带有Override注解

MethodSpec.Builder method = MethodSpec.methodBuilder(METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(paramSpec);
        addCodeIntoMethod(method, elements);
private void addCodeIntoMethod(MethodSpec.Builder method, Set elements) {
        ClassName classTask = ClassName.get(LauncherTask.class);
        Set keys = taskMap.keySet();

        for (Element item : elements) {
            Laucher annotation = item.getAnnotation(Laucher.class);
            String key = annotation.name();
            logger.info(key + "  " + ClassName.get((TypeElement)item));

            method.addStatement("$T.build($S, $T.class, " + taskMap.get(key).getPriority() + "," + taskMap.get(key).isAsync() + ", " + createListMethod(taskMap.get(key).getPreTaskList()) + ")",
                    classTask,
                    taskMap.get(key).getTaskName(),
                    ClassName.get((TypeElement)item));
        }
    }

//在方法体里面插入代码。

JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(CLASS_NAME)  //class文件名字
                        .addModifiers(Modifier.PUBLIC)  //公有方法
                        .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITASK_MANAGER)))
                        .addJavadoc("@Launcher注解自动生成的java文件")
                        .addMethod(method.build())
                        .build()
        ).build().writeTo(filer);

构建class,并把方法插入,通过javadoc接口加入类的注解,并设置class实现的接口等,然后写入文件。

经过上面代码的处理,我们可以在build目录下面找到生成后的java文件,下图就是生成代码的结果。

Android AOP 编程实践 javapoet + autoService_第1张图片

 

四.可能遇到的问题:

1.Processor,init方法没走!

        可能原因1:配置问题,jdk没统一,如果设置的jdk-8,结果annotation和compile的lbrary设置了java-7,其次也有可能是androd studio的jdk设置不是jdk-8。

        可能原因2:SupportedAnnotationTypes设置错误,在log里面警告没找到对应注解。

2.Processor,init方法走了,但是process方法不执行。

        1.java定义的注解用在了kotlin文件上面

        2.某个模块使用了该注解,也implement了annotation的lib,但是没导入有自定义Processor的compile的lib。

你可能感兴趣的:(android,android,aop,自定义注解,面向切面编程)