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<String, LauncherTask> 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<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> launcherElements = roundEnvironment.getElementsAnnotatedWith(Laucher.class);
        parseElements(launcherElements);
        try {
            createJavaDoc(launcherElements);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    private void createJavaDoc(Set<? extends Element> 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<? extends Element> elements) {
        ClassName classTask = ClassName.get(LauncherTask.class);
        Set<String> 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<String> list) {
        List<String> test = new ArrayList() {{
            add("a");
        }};
        StringBuilder builder = new StringBuilder();
        for (String item : list) {
            builder.append("add(\"" + item + "\"); ");
        }
        return "new java.util.ArrayList<String>(){{" + builder.toString() + "}}";
    }

    private void parseElements(Set<? extends Element> 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<String, LauncherTask>
private String inputParamsName = "taskMap";
ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build(); 
//这个代码就是生成一个参数,对应的java代码是
HashMap<String, LauncherTask> taskMap

//下面的代码是构建一个方法对象,public属性,入参是HashMap<String, LauncherTask> 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<? extends Element> elements) {
        ClassName classTask = ClassName.get(LauncherTask.class);
        Set<String> 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文件,下图就是生成代码的结果。

 

四.可能遇到的问题:

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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android是一个开放的移动操作系统,爱好者和开发者可以根据自己的需求进行定制和开发。在Android开发中,JavaPoet是一个非常有用的库,可以动态生成Java代码,简化一些重复的工作。而AOP面向切面编程)则是一种编程范式,可以将横切关注点与业务逻辑分离,提高代码的可复用性和可维护性。 动态权限申请是Android开发中经常遇到的一个问题。在Android系统中,一些敏感的操作和资源访问需要动态申请权限,以确保用户的隐私和安全。传统的权限申请方式是在每个需要权限的地方都进行判断和申请,这样会导致代码的冗余和可读性的下降。使用AOP结合JavaPoet可以实现动态权限申请的解决方案。 首先,我们可以通过AOP在需要权限的方法周围添加一个切面,用于检查和申请权限。通过AspectJ等AOP框架,我们可以定义一个切面,在方法执行之前和之后执行相应的逻辑。 然后,利用JavaPoet动态生成申请权限的代码。我们可以定义一个注解,用于标识需要权限的方法。在AOP切面中,当检测到方法上有该注解时,生成相应的权限申请代码。 最后,在代码编译阶段,通过JavaPoet生成的代码会自动插入到原始代码中。这样,我们就可以在运行时动态地进行权限的申请了。 通过以上的实践,我们可以实现动态权限申请的功能,同时可以减少重复的代码,并提高代码的可维护性。使用JavaPoetAOP相结合的方式,可以使我们的开发变得更加高效和便捷。它们为Android开发带来了更多的灵活性和扩展性,帮助我们更好地应对权限申请的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值