美文网首页Android进阶之路Android开发Android开发经验谈
Android进阶——Java注解实战之APT构建模块化的第一步

Android进阶——Java注解实战之APT构建模块化的第一步

作者: Android架构木木 | 来源:发表于2019-05-10 15:35 被阅读9次

    前言

    APT的学习要花点时间去掌握和实践的,短时间内只能掌握知识点,更多的是在实战中去实践。其实,APT就是一种工具而已,只要用多了,自然就会熟练了,不过要想实践之前,还是必须把基础知识学好才能实战进入开发。文章会从基础用例讲解知识点,然后再通过实战进行实践

    APT简介

    APT(Annotation Processing Tool)是一种处理注解的工具,它会对源代码中的注解进行额外的处理,比如在编译时生成一些重复性操作的Java代码,或者不需要程序员去关心的Java代码等。
    在使用APT的过程中会涉及到下面两个第三方库的使用

    1. AutoService:这个库的主要作用是注册注解,并对其生成META-INF的配置信息
    2. JavaPoet:这个库的主要作用是帮助我们通过类调用的形式来生成Java代码

    APT主要过程包括初始化过程和注解处理过程

    1. 初始化过程:获取APT提供的工具类,为后面的注解处理提供帮助
    2. 注解处理过程:获取注解的元素,对元素进行额外处理,可用JavaPoet生成Java代码

    APT流程

    1、定义注解

    该注解是可以在我们的项目中使用到的,且规定为注解元素的类型为Type,和在编译时生效

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ModuleWrapper {
    
    }
    

    2、定义Processor

    Processor会在编译期对注解进行解析,取出对应的元素进行处理。至于AutoService则是固定的写法,加个注解即可

    @AutoService(Processor.class)
    public class ModuleProcessor extends AbstractProcessor {
    
        private Filer filerUtils; // 文件写入
        private Elements elementUtils; // 操作Element工具类
        private Messager messagerUtils; // Log 日志
        private Map<String, String> options; // 额外配置参数
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
    
            filerUtils = processingEnvironment.getFiler();
            elementUtils = processingEnvironment.getElementUtils();
            messagerUtils = processingEnvironment.getMessager();
            options = processingEnvironment.getOptions();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            types.add(ModuleWrapper.class.getCanonicalName());
            return types;
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            initModuleMap(roundEnvironment);
            return false;
        }
    
        private void initModuleMap(RoundEnvironment roundEnv) {
            //获取对应的注解元素
            Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class);
            for (Element element : set) {
                //如果是个类
                if (element.getKind() == ElementKind.CLASS) {
                    //获取类名
                    String clzName = element.getSimpleName().toString();
                    //对元素进行处理,可用javapoet生成Java代码
                    ......
                } else {
                    messagerUtils.printMessage(Diagnostic.Kind.NOTE, "only support class");
                }
            }
        }
    }
    

    3、AbstractProcessor实现方法介绍

    1. init():初始化过程,可通过初始化方法获取各种工具类
    2. process():注解处理过程,可通过获取注解元素后,对注解元素进行额外处理
    3. getSupportedAnnotationTypes():获取需要解析的注解类型

    APT知识点

    1、初始化介绍

    APT初始化阶段为init方法的调用,我们可以使用ProcessingEnvironment获取一些实用类以及获取选项参数等


    2、Element介绍

    Element是操作元素最主要的类,可通过getElementsAnnotatedWith获取Element的元素,经过getKind()判断元素的类型后,可强制转换成对应的类型,在对应的类型中有着不同的方法可以调用


    ElementKind为元素的类型,元素的类型判断不需要用instanceof去判断,而应该通过getKind()去判断对应的类型

    3、TypeMirror介绍

    TypeMirror是一个接口,表示Java编程语言中的类型。这些类型包括基本类型、引用类型、数组类型、类型变量和null类型等等


    TypeKind为类型的属性,类型的属性判断不需要用instanceof去判断,而应该通过getKind()去判断对应的属性

    这里需要注意的是,如果我们通过注解去获取Class类型的值,如果获取的Class未被编译,则会抛出MirroredTypeException异常,此时我们需要通过try-catch语句在catch里去获取我们所需要的类元素

    try {  
        annotation.value();//如果value为Class类型则会报异常
    } catch (MirroredTypeException mte) {
        DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
        TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();//通过异常去获取类元素
    }
    

    4、Filer介绍

    Filer接口支持通过注解处理器创建新文件。可以创建三种文件类型:源文件、类文件和辅助资源文件


    5、Messager介绍

    Messager接口提供注解处理器用来报告错误消息、警告和其他通知的方式


    6、Options介绍

    通过getOptions()方法获取选项参数,在gradle文件中配置选项参数值

    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [ version : '1.0.0' ]
                }
            }
        }
    }
    

    通过ProcessingEnvironment去获取对应的参数

    processingEnvironment.getOptions().get("version");
    

    7、获取注解元素

    通过RoundEnvironment接口去获取注解元素,通过JavaPoet生成Java代码

    APT实战

    下面通过APT的实战,进行对项目的模块化划分

    1、项目结构

    1. 创建Module,名为annotation,放置我们的注解类
    2. 创建Module,名为compiler,放置我们的注解处理类
    3. 主工程则直接依赖annotation和compiler
      注意事项:创建Module的时候,需要选择java Lib,而不是Android Lib

    2、Gradle配置

    annotation的Module必须声明Java编译版本

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    }
    
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
    

    compiler的Module必须声明Java编译版本,且依赖于annotation和导入我们所需的库

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    
        implementation project(':annotation')
        implementation 'com.google.auto.service:auto-service:1.0-rc2'
        implementation 'com.squareup:javapoet:1.7.0'
    }
    
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
    

    主工程必须通过依赖annotaion和compiler,由于我们只是在编译期生效,可用annotationProcessor

    implementation project(':annotation')
    annotationProcessor project(':compiler')
    

    注意事项:定义编译的jdk版本为1.7

    3、定义注解

    ModuleWrapper注解,表示需要加载的模块

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ModuleWrapper {
    
    }
    

    IModule接口,表示当前类是一个模块类

    public interface IModule {
        String getModuleName();
    }
    

    4、定义Processor

    @AutoService(Processor.class)
    public class ModuleProcessor extends AbstractProcessor {
    
        private Map<String, ModuleInfo> moduleMaps = new HashMap<>();
    
        private Filer filerUtils; // 文件写入
        private Elements elementUtils; // 操作Element 的工具类
        private Messager messagerUtils; // Log 日志
        private Map<String, String> options;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
    
            filerUtils = processingEnvironment.getFiler();
            elementUtils = processingEnvironment.getElementUtils();
            messagerUtils = processingEnvironment.getMessager();
            options = processingEnvironment.getOptions();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new LinkedHashSet<>();
            types.add(ModuleWrapper.class.getCanonicalName());
            return types;
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            try {
                initModuleMap(roundEnvironment);
                createModuleMap();
                createModuleConstant();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return false;
        }
    
        /**
         * 通过注解元素获取组件实体
         *
         * @param roundEnv
         */
        private void initModuleMap(RoundEnvironment roundEnv) {
            Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ModuleWrapper.class);
            for (Element element : set) {
                if (element.getKind() == ElementKind.CLASS) {
                    String clzName = element.getSimpleName().toString();
                    if (moduleMaps.get(clzName) == null) {
                        ModuleInfo info = new ModuleInfo(elementUtils, (TypeElement) element);
                        moduleMaps.put(clzName, info);
                    }
                } else {
                    messagerUtils.printMessage(Diagnostic.Kind.NOTE, "only support class");
                }
            }
        }
    
        /**
         * 创建组件管理者
         *
         * @throws IOException
         */
        private void createModuleMap() throws IOException {
    
            FieldSpec fieldSpec = FieldSpec
                    .builder(ParameterizedTypeName.get(HashMap.class, String.class, IModule.class)
                            , "moduleMap", Modifier.PRIVATE)
                    .initializer("new HashMap<>()")
                    .build();
    
            CodeBlock.Builder codeBlock = CodeBlock.builder();
            for (String key : moduleMaps.keySet()) {
                ModuleInfo info = moduleMaps.get(key);
                codeBlock.addStatement("moduleMap.put($S ,new $T())", info.getFullClassName(),
                        ClassName.get(info.packageName, info.className));
            }
    
            MethodSpec initMethod = MethodSpec.methodBuilder("init")
                    .addModifiers(Modifier.PUBLIC)
                    .addCode(codeBlock.build())
                    .returns(TypeName.VOID)
                    .build();
    
            MethodSpec getMethod = MethodSpec.methodBuilder("get")
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(String.class, "cls")
                    .addStatement("return moduleMap.get(cls)")
                    .returns(IModule.class)
                    .build();
    
            ArrayList<MethodSpec> methods = new ArrayList<>();
            methods.add(initMethod);
            methods.add(getMethod);
    
            TypeSpec moduleFactory = TypeSpec.classBuilder("ModuleFactory")
                    .addModifiers(Modifier.PUBLIC)
                    .addMethods(methods)
                    .addField(fieldSpec)
                    .build();
    
            JavaFile javaFile = JavaFile.builder("com.hensen.compiler.processor", moduleFactory)
                    .build();
            javaFile.writeTo(filerUtils);
        }
    
        /**
         * 创建组件常量
         *
         * @throws IOException
         */
        private void createModuleConstant() throws IOException {
    
            TypeSpec.Builder moduleConstant = TypeSpec.classBuilder("ModuleConstant")
                    .addModifiers(Modifier.PUBLIC);
    
            for (String key : moduleMaps.keySet()) {
                ModuleInfo info = moduleMaps.get(key);
                FieldSpec fieldSpec = FieldSpec.builder(String.class, info.className)
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .initializer("$S", info.getFullClassName())
                        .build();
                moduleConstant.addField(fieldSpec);
            }
    
            JavaFile javaFile = JavaFile.builder("com.hensen.compiler.processor", moduleConstant.build())
                    .build();
            javaFile.writeTo(filerUtils);
        }
    }
    

    5、组件实体

    组件实体保存着组件的信息

    public class ModuleInfo {
    
        public String packageName;
        public String className;
    
        public ModuleInfo(Elements elementUtils, TypeElement typeElement) {
            packageName = getPackageName(elementUtils, typeElement);
            className = getClassName(typeElement, packageName);
        }
    
        public String getClassName(TypeElement type, String packageName) {
            int packageLen = packageName.length() + 1;
            return type.getQualifiedName().toString().substring(packageLen)
                    .replace('.', '$');
        }
    
        public String getPackageName(Elements elementUtils, TypeElement classElement) {
            PackageElement packageElement = elementUtils.getPackageOf(classElement);
            return packageElement.getQualifiedName().toString();
        }
    
        public String getFullClassName() {
            return packageName + "." + className;
        }
    
        public String getClassName(){
            return className;
        }
    }
    

    6、注解使用

    分别创建出礼物模块和聊天模块,聊天模块增加发消息的方法

    @ModuleWrapper
    public class ChatModule implements IModule{
    
        @Override
        public String getModuleName() {
            return "ChatModule";
        }
    
        public void sendMessage() {
            Log.i("TAG", "Hi");
        }
    }
    
    @ModuleWrapper
    public class GiftModule implements IModule{
    
        @Override
        public String getModuleName() {
            return "GiftModule";
        }
    }
    

    7、生成代码

    在生成代码之前需要gradle build,查看生成代码。当然对于模块来说,不仅有get()、还有add()、remove()等其他扩展功能,具体的就留给大家去操作

    public class ModuleFactory {
      private HashMap<String, IModule> moduleMap = new HashMap<>();
    
      public void init() {
        moduleMap.put("com.hensen.geneapt.GiftModule" ,new GiftModule());
        moduleMap.put("com.hensen.geneapt.ChatModule" ,new ChatModule());
      }
    
      public IModule get(String cls) {
        return moduleMap.get(cls);
      }
    }
    
    public class ModuleConstant {
      public static final String GiftModule = "com.hensen.geneapt.GiftModule";
    
      public static final String ChatModule = "com.hensen.geneapt.ChatModule";
    }
    

    8、组件使用

    初始化组件的加载,通过工厂获取对应的模块进行操作

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ModuleFactory moduleFactory = new ModuleFactory();
            moduleFactory.init();
    
            ChatModule chatModule = (ChatModule) moduleFactory.get(ModuleConstant.ChatModule);
            chatModule.sendMessage();
        }
    }
    

    相关文章

      网友评论

        本文标题:Android进阶——Java注解实战之APT构建模块化的第一步

        本文链接:https://www.haomeiwen.com/subject/eoeaoqtx.html