Android 运行时注解(APT)使用及组件初始化逻辑讲解

在组件化开发过程中,我们经常会遇到一个问题,一些全局变量如何共享,如Application的全局功效,还有在应用初始化过程中能否同时对组件进行初始化操作。诚然,通过每次使用手动编写,或者维护一个组件初始化表通过反射方式进行动态初始化操作都是可以的。那有没有一个更效率更方便的方式实现呢,答案是有多,接下来我们就通过Demo了解APT的使用及组件初始化的实现逻辑。

GitHub

一 创建项目

首先我们先创建一个MyAnnotation的项目。同时添加两个Java-lib的Module。
其中AnnotationLib作为注解类。ProcessLib作为解析类。


-w461

二 创建注解

然后我们在AnnotationLib下添加注解类InitAppliation 代码如下

package kt.dongdong.annotationlib;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface InitAppliation {
}

其中元注解@Target和@Retention分别表示作用对象为class,留存范围为源码阶段。其他相关参数意义可以自行查询。
这样我们注解就完成了,接下来我们处理对于多解析类。

解析配置

在创建解析类前,我们先在ProcessLib中做一些相关引用。

  implementation project(":AnnotationLib")
  implementation 'com.google.auto.service:auto-service:1.0-rc4'

然后我们需要创建一个解析类InitProcess。并在类上添加注解与父类。

@AutoService(Processor.class)
public class InitProcess extends AbstractProcessor {

    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

其中@AutoService表示注册此解析类,只有注册了才能用。
然后继承AbstractProcessor并实现相关方法。

@Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

这个方法返回执行的java版本,一般直接返回SourceVersion.latestSupported()即可,当然也可以指定版本如:SourceVersion.RELEASE_7则定义为java7.

@Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> setTypes = new HashSet<>();
        setTypes.add(InitAppliation.class.getCanonicalName());
        return setTypes;
    }

getSupportedAnnotationTypes返回需要处理的注解的集合。其中InitAppliation.class.getCanonicalName()表示拿到注解类的完整路径。

ps:以上两个方法配置都可以用注解的形式实现。

接下来我们就需要实现自动生成类的部分了

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // 参数annotations表示需要处理的注解的集合
        // 参数roundEnv通过getElementsAnnotatedWith方法可以获取对应使用注解的类

        if (annotations.isEmpty()) {
            return false;
        }
        for (TypeElement typeElement : annotations) {

            //判断是否是要解析的注解类
            if (typeElement.toString().equals(InitApplication.class.getCanonicalName())) {
                //获取对应列表
                Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
                StringBuilder strImpory = new StringBuilder();
                StringBuilder strNew = new StringBuilder();

                for (Element element : elements) {
                    strImpory.append("import " + element.toString() + ";\n");
                    strNew.append(element.getSimpleName() + " "
                            + element.getSimpleName().toString() + "C = new "
                            + element.getSimpleName() + "();\n");
                    strNew.append(element.getSimpleName().toString() + "C.init(application);\n");
                }
                try {
                    //通过processingEnv.getFiler()创建类文件并写入。
                    JavaFileObject fileObject = processingEnv.getFiler().createSourceFile("kt.dongdong.annotationlib.AllInit");
                    Writer writer = fileObject.openWriter();
                    writer.write("package kt.dongdong.annotationlib;\n\n");
                    writer.write("import android.app.Application;\n");
                    writer.write(strImpory.toString() + "\n\n\n");
                    writer.write("public class AllInit{\n\n");
                    writer.write("public AllInit(Application application){\n");
                    writer.write(strNew.toString());
                    writer.write("}\n}\n");
                    writer.close();//注意需要关闭流 不然文件没有内容

                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
        return false;
    }
  1. 在上面代码中我写了些注释,主要流程是:
  2. 通过roundEnv.getElementsAnnotatedWith()获取使用了对应注解多所有类信息。
  3. 通过循环这些类编写对应import和初始化的代码串。
  4. 通过processingEnv.getFiler().createSourceFile()创建指定名称的类文件。
  5. 然后开启流编写包名,引用,类名,初始化并调用方法等信息
  6. 然后关闭流 writer.close()很重要 不然生成的类没有内容。

生成文件

先在App项目中添加引用

    implementation project(":AnnotationLib")
    annotationProcessor project(":ProcessLib")

这里要注意的是解析Lib引用使用annotationProcessor关键字

创建AppliationA,AppliationB类并添加注解。然后执行Build 》ReBuild Project。


-w412

便能在该目录下生成文件了。
文件内容为我们在解析类里面写入的内容

package kt.dongdong.annotationlib;

import android.app.Application;
import kt.dongdong.myannotation.AppliationA;
import kt.dongdong.myannotation.AppliationB;

public class AllInit{

public AllInit(Application application){
AppliationA AppliationAC = new AppliationA();
AppliationAC.init(application);
AppliationB AppliationBC = new AppliationB();
AppliationBC.init(application);
}
}

PS:这里有几点需要注意的
如果你的Gradle版本高于5,也许会因为不兼容无法生成类文件。这时候需要在注解类的Lib里面添加下面的引用

implementation 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'

或者你可以把gradle版本降低至4.6 ,这时候生成类的地址变为build>generated>source>apt下

使用方式

在上面我们知道可以生成对于的类文件,但是在开发中我们未编译时又如何去使用对应的生成类呢。这时候我们就需要通过一个中间类以反射的形式进行初始化了。

package kt.dongdong.myannotation;

import android.app.Application;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class TestAppInit {

    public static void init(Application application) {

        try {
            Class<?> clazz = Class.forName("kt.dongdong.annotationlib.AllInit");
            Class[] parameterTypes = {Application.class};
            Constructor constructor = clazz.getConstructor(parameterTypes);
            Object object = constructor.newInstance(application);


        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }

}

其中Class.forName的类名就是我们在解析类里生成文件的名称。
然后在Application中进行调研


public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        TestAppInit.init(this);
    }
}

运行起来后查看日志


-w551

OK 致此通过注解形式初始化组件与APT的使用方式大致了解了就。

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

推荐阅读更多精彩内容