当前位置:   article > 正文

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

class moduleinfo {

前言

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 {

}
  • 1
  • 2
  • 3
  • 4
  • 5

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");
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

3、AbstractProcessor实现方法介绍

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

APT知识点

1、初始化介绍

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

方法说明
getElementUtils()返回实现Elements接口的对象,用于操作元素的工具类
getFiler()返回实现Filer接口的对象,用于创建文件、类和辅助文件
getMessager()返回实现Messager接口的对象,用于报告错误信息、警告提醒
getOptions()返回指定的参数选项
getTypeUtils()返回实现Types接口的对象,用于操作类型的工具类

2、Element介绍

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

类型说明
ExecutableElement表示类、接口的方法元素。包括构造方法、注解类型
PackageElement表示包元素。提供对有关包及其成员的信息的访问
TypeElement表示类、接口元素。提供对有关类型及其成员的信息的访问
TypeParameterElement表示类、接口、方法、构造方法的参数元素
VariableElement表示字段、enum、方法、构造方法参数、局部变量、异常参数

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

类型说明
PACKAGE
ENUM枚举
CLASS
ANNOTATION_TYPE注解
INTERFACE接口
ENUM_CONSTANT枚举常量
FIELD字段
PARAMETER方法参数
LOCAL_VARIABLE局部变量
METHOD方法
CONSTRUCTOR构造方法
TYPE_PARAMETER类型参数

3、TypeMirror介绍

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

类型说明
ArrayType表示数组类型
DeclaredType表示声明类型(类或接口类型)
ErrorType表示异常类或接口类型
ExecutableType表示executable类型(方法、构造方法、初始化)
NoType表示在实际类型不适合的地方使用的伪类型
NullType表示null类型
PrimitiveType表示基本数据类型
ReferenceType表示引用类型
TypeVariable表示类型变量
WildcardType表示通配符类型参数

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

类型说明
BOOLEAN基本类型boolean
INT基本类型int
LONG基本类型long
FLOAT基本类型float
DOUBLE基本类型double
VOID对应于关键字void的伪类型
NULLnull类型
ARRAY数组类型
PACKAGE对应于包元素的伪类型
EXECUTABLE方法、构造方法、初始化

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

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

4、Filer介绍

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

方法说明
createSourceFile创建源文件
createClassFile创建类文件
createResource创建辅助资源文件

5、Messager介绍

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

方法说明
printMessage打印错误消息

6、Options介绍

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

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ version : '1.0.0' ]
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

通过ProcessingEnvironment去获取对应的参数

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

7、获取注解元素

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

方法说明
getElementsAnnotatedWith返回注解元素的集合

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"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

implementation project(':annotation')
annotationProcessor project(':compiler')
  • 1
  • 2

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

3、定义注解

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5

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

public interface IModule {
    String getModuleName();
}
  • 1
  • 2
  • 3

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

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";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

9、源码下载

源码下载

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/240159
推荐阅读
相关标签
  

闽ICP备14008679号