程序员

Android注解全面解析

2020-01-12  本文已影响0人  Brook_liu

一、注解知识体系

注解知识体系

二、基础知识

必备基础知识:
了解注解

三、注解在Android中的应用

1. 利用注解代替枚举

因为枚举静态单例的实现方式,导致枚举相对基础类型变量更耗内存。

优化方案:采用@IntDef/@StringDef替代枚举。

详细分析及实现推荐以下博客:
Android中不使用枚举类(enum)替代为@IntDef @StringDef

2. 运行时注解

Retention(RetentionPolicy.RUNTIME)修饰的就是运行时注解。使用这种注解,多数情况是为了在运行时做一些事情。

运行时注解主要是利用了反射的原理将代码中的注解读出来并做相应操作。

简单举个例子,什么人在什么地方做什么事。
定义注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Where {
    String country();
    String province();
    String city();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Who {
    String name();
    int age();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface DoSomething {
    String value();
}

使用注解:

@Who(name = "liuhe", age = 26)
public class Person {

    @Where(country = "China", province = "guangdong", city = "shenzhen")
    private String where;

    @DoSomething("coding")
    public void doSomething() {

    }
}

解析注解:

/**
 * 自定义运行期注解实践
 */
public class AnnotationTest {
    public static void main(String[] args) {
        Class<Person> personClass = Person.class;
        Who who = personClass.getAnnotation(Who.class);
        System.out.print(who.name());
        Field[] fields = personClass.getDeclaredFields();
        for (Field field : fields) {
            Where where = field.getAnnotation(Where.class);
            System.out.print(where == null? "" : where.country());
        }

        Method method;
        try {
            method = personClass.getMethod("doSomething");
            DoSomething doSomething = method.getAnnotation(DoSomething.class);
            System.out.print(doSomething == null? "": doSomething.value());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

3. 编译时注解

Retention(RetentionPolicy.CLASS)修饰的就是编译时注解。
编译时注解必须用到APT(编译时注解解析技术)
APT技术主要是通过编译期解析注解,并且生成java代码的一种技术,一般会结合Javapoet技术来生成代码。

想要实现APT,就必须了解Javapoet,而最关键的就是Element
java-apt的实现之Element详解

开始实践:
实现功能:为一个Javabean内的字段生成getset方法。

  1. 编写注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface GenerateGS {
}
  1. 实现注解解析器Processor
    注解解析器需要实现抽象类AbstractProcessor,而且解析器需要单独放在一个Java Library Module中。在使用时通过其他Android module去依赖这个lib。
    新建Javalib后,在build.gradle中需要导入两个依赖库:
//  Google 开源的注解注册处理器
 implementation 'com.google.auto.service:auto-service:1.0-rc2'
// square 开源的 Java 代码生成框架
implementation 'com.squareup:javapoet:1.10.0'

实现AbstractProcessor

/**
 * 编译时注解处理器
 * 注:必须放在Javalib中
 */
@AutoService(Processor.class)
public class GenerateGSProcesser extends AbstractProcessor {
    private Filer mFiler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
    }

    /**
     * 指定processer支持的注解对象
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        LinkedHashSet<String> types = new LinkedHashSet<>();
        types.add(GenerateGS.class.getCanonicalName());
        return types;
    }

    /**
     * 处理注解的方法
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        HashMap<String, HashSet<Element>> map = new HashMap<>();
        System.out.println("apt:开始process");
        // 获取所有包含GenerateGS注解的元素
        Set<? extends Element> elementSets = roundEnvironment.getElementsAnnotatedWith(GenerateGS.class);
        for (Element element : elementSets) {
            // 因为该注解是作用于field,所以转成VariableElement
            VariableElement variableElement = (VariableElement) element;
            // 获取封装element的外层element
           TypeElement parentElement = (TypeElement) variableElement.getEnclosingElement();
           String parentClass = parentElement.getSimpleName().toString();
            System.out.println("apt:元素外部:" + parentClass);
            HashSet<Element> sets = map.get(parentClass);
            if (sets == null) {
                sets = new HashSet<>();
            }
            sets.add(element);
            map.put(parentClass, sets);
        }
        generateCode(map);
        return true;
    }

    private void generateCode(HashMap<String, HashSet<Element>> map) {
        Set<Map.Entry<String, HashSet<Element>>> entrySet = map.entrySet();
        System.out.println("apt:开始遍历元素");
        for (Map.Entry<String, HashSet<Element>> entry : entrySet) {

            String className = entry.getKey();
            HashSet<Element> sets = entry.getValue();
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(className + "Bean")
                    .addModifiers(Modifier.PUBLIC);
            for (Element element : sets) {
                // getKind:获取element类型
                if (element.getKind().isField()) {
                    String fieldname = element.getSimpleName().toString();
                    System.out.println("apt:类:" + className + " 元素:" + fieldname);
                    // 将字段首字母变成大写
                    char[] chars = fieldname.toCharArray();
                    chars[0] -= 32;

                    String firstUpperName = String.valueOf(chars);
                    // 获取字段的类型信息
                    TypeName typeName = TypeName.get(element.asType());

                    // 生成字段
                    FieldSpec fieldSpec = FieldSpec.builder(typeName, fieldname, Modifier.PRIVATE).build();
                    MethodSpec getMethodSpec = MethodSpec.methodBuilder("get" + firstUpperName)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(typeName)
                            .addStatement("return " + fieldname)
                            .build();
                    MethodSpec setMethodSpec = MethodSpec.methodBuilder("set" + firstUpperName)
                            .addModifiers(Modifier.PUBLIC)
                            .addParameter(typeName, fieldname)
                            .returns(TypeName.VOID)
                            .addStatement("this." + fieldname + " = " + fieldname)
                            .build();
                    // 构建JavaBean结构
                    typeBuilder.addField(fieldSpec)
                            .addMethod(getMethodSpec)
                            .addMethod(setMethodSpec);
                }
            }

            TypeSpec typeSpec = typeBuilder.build();
            // 生成Java文件
            JavaFile javaFile = JavaFile.builder("com.eric.aptlib", typeSpec).build();
            try {
                javaFile.writeTo(mFiler);
                System.out.println("apt:生成" + className + "Bean" + "类");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("apt:代码生成完毕");
    }
}
  1. 使用
    添加一个测试JavaBean类
public class Test {

    @GenerateGS
    String id;
}

并在类所在的module的build.gradle中添加依赖:

   // 依赖注解库
    implementation project(':aptlib')
  // 依赖注解解析库
    annotationProcessor project(':aptlib')

然后rebuild一下就可以在build/generated/source/apt/下找到生成的Java代码。

public class TestBean {
  private String id;

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }
}

四、编译时注解应用实例:Dagger2

1. 什么是Dagger2?

Dagger2是Dagger的升级版,是一款利用注解实现依赖注入的开源框架。而利用注解实现的目的就是降低类之间的耦合度。

2. Dagger2原理

简单说下原理,Dagger2也是利用编译时注解的机制,生成一个被注入类的实例,并注入到使用该实例的类中,只不过这个注入的实现并不是直接在代码中以new的方式注入,如果直接new,在需要修改注入类的构造参数时,就不得不修改被注入类。因此使用Dagger2能够很好的减少代码耦合度,并且更方便维护代码。

3. 使用场景:

在MVP开发模式中使用Dagger2,对view和presenter层以及model和http,database,file等下层提供数据能力的组件进行解藕。

关于Dagger2的详细说明推荐以下博客:
Dagger2从入门到放弃再到恍然大悟


文中运行时注解、编译时注解、MVP中Dagger2的应用的代码地址:
github代码地址


上一篇下一篇

猜你喜欢

热点阅读