Android注解全面解析

一、注解知识体系

注解知识体系

二、基础知识

必备基础知识:
了解注解

三、注解在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代码地址


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 序言 注解是Java程序和Android程序中常见的语法,之前虽然知道有这么个东西,但并没有深入了解注解。写Eve...
    左大人阅读 4,577评论 3 15
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 6,196评论 4 31
  • 该文章中涉及的代码,我已经提交到GitHub上了,大家按需下载---->源码 前言 在上篇文章Android 注解...
    AndyJennifer阅读 1,821评论 3 5
  • 一、首先你要知道什么是依赖? 想要理解Dagger2,首先你要理解一个概念,就是什么是依赖,懂的同学可以省过此段。...
    为梦想战斗阅读 405评论 0 0
  • 同样的路途风景,却不一样的感觉。时光飞逝,记忆却犹新。愿一切都安好!
    饭后杂记阅读 171评论 0 0