安卓组件化架构设计

目录

写在前面

一、Gradle语法

二、组件化项目部署

2.1、组件化项目的意义

2.2、Phone Module和Android Library的区别

2.3、项目构建基础

2.4、集成化模式和组件化模式开发

2.5、动态隔离

三、子模块间交互

四、APT介绍与使用

4.1、APT简介

4.2、APT实战

五、APT高级用法JavaPoet

5.1、什么是JavaPoet

5.2、JavaPoet代码实战

六、组件化路由架构设计

6.1、配置arouter_api

6.2、配置common

6.3、APT生成路由Group和Path类文件

6.4、组件化APT生成路由动态参数类文件


写在前面

今天是1024程序猿节,首先祝各位猿猿们节日快乐,过节不加班!

今天思考再三之后,决定来写一篇Android组件化介绍的文章,说一说Android中的组件化如何使用,在这里我们先不使用阿里的ARouter,我们自己先实现一版功能简单的路由框架,下一篇再来使用业内比较知名的阿里的ARouter框架实现组件化。大家要明白什么是组件化,在我看来哈,组件化并不是一种需要你去怎样学习的技术,它实际上是对业务层面,具体到项目中就是项目架构上的解耦,将每个业务模块做成可拆分可集成化的组件,所以说组件化实际上就是基于模块化进行的。核心是模块的角色具有可转变的特性,集成化模式打包时各个业务组件是作为Android Library,打出来的只有一个完整的apk包,在单独调试模式的时候,每个业务组件都是作为单独的一个application存在,单独调试单独运行,互不干扰!

组件化非常重要,在中大型项目中基本上都会用到,我们首先来学习Gradle语法,因为它对于我们组件化环境的配置是十分重要的,gradle我们在项目中每天都能用到,见的很多了,但是很多人却不一定会写哈,下面就来总结一下Gradle语法相关的知识点。

一、Gradle语法

说明:由于本篇是介绍组件化架构而不是专门介绍Gradle语法的,所以这一部分并不会详细介绍具体的语法规则,而是结合实际应用来介绍项目中经常用到的一些规则或者是配置的使用。

Gradle简介

  • Gradle核心是基于Groovy脚本语言,Groovy脚本基于Java且拓展了Java。因此Gradle需要依赖JDK和Groovy库
  • 和ant、maven构建有区别,gradle是一种编程思想

Gradle语法简介

①、万法第一式:Hello World

我们在Build的Toggle View中打印"Hello Gradle",有两种方式:

点击右上角的同步操作,同步完成后下方Sync面板中就会输出结果了:

然后我们新建一个Android的Library,这里面肯定也有一个build.gradle文件吧,打开之后和app module下的build.gradle文件对比,你会发现这两者之间有很多东西都是一样的,那么此时你能想到什么?321,对喽,可以把公共的部分抽离出来嘛。

First:新建一个config.gradle用作公共的配置文件,这里举个栗子如下所示,关于一些语法已经做了注释:

//添加多个自定义属性,可以通过ext代码块
ext {

    //生产/开发环境(或者叫做正式/测试环境)
    //false:组件化模式(子模块可以独立运行)true:集成化模式(打包整个项目apk,子模块不可独立运行)
    isRelease = true

    //生产/开发环境域名地址
    hostUrl = [
            debug  : "http://www.111.com/debug/",
            release: "http://www.222.com/release/"
    ]

    //建立Map存储,对象名、key都可以自定义,groovy的语法糖非常灵活(字典)
    androidConfig = [
            compileSdkVersion: 29,
            buildToolsVersion: "29.0.2",
            minSdkVersion    : 14,
            targetSdkVersion : 29,
            versionCode      : 1,
            versionName      : "1.0"
    ]

    //ApplicationId(应用包名)
    appId = [
            app    : "com.jarchie.component",
            library: "com.jarchie.library"
    ]

    //依赖库版本:调用处的语法为:${keyName}
    depVersions = [
            appcompatVersion       : "1.2.0",
            consVersion: "2.0.1"
    ]

    //依赖库
    depConfig = [
            appcompat       : "androidx.appcompat:appcompat:${depVersions.appcompatVersion}",
            constraintlayout: "androidx.constraintlayout:constraintlayout:${depVersions.consVersion}"
    ]

}

Second:需要在工程的根目录下的build.gradle文件中引入咱们的公共配置文件:

//根目录下的build.gradle头部引入自定义config.gradle,相当于layout布局中加入include标签
apply from: "config.gradle"

Third:修改原来的app module下的build.gradle文件,在文件中使用我们的基础配置:

apply plugin: 'com.android.application'

//赋值与引用
def androidConfig = rootProject.ext.androidConfig
def appId = rootProject.ext.appId
def depConfig = rootProject.ext.depConfig

android {
    //上面定义了三个属性,分别指向公共配置config.gradle中定义的集合,然后根据集合中的key来获取对应的值
    compileSdkVersion androidConfig.compileSdkVersion
    buildToolsVersion androidConfig.buildToolsVersion
    defaultConfig {
        applicationId appId.app
        minSdkVersion androidConfig.minSdkVersion
        targetSdkVersion androidConfig.targetSdkVersion
        versionCode androidConfig.versionCode
        versionName androidConfig.versionName
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //gradle自带的这种方式实际上是一种简写
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    //它的标准写法应该是下面这种:
    implementation group:'androidx.appcompat',name:'appcompat',version:'1.2.0'

    //经过全局配置之后,依赖方式应该写成如下的格式
    implementation project(":library")
    implementation depConfig.appcompat
    implementation depConfig.constraintlayout

    //原理:实际上这种依赖的语法,本质相当于从Map中按照Key取对应的值,比如下面这样
    HashMap<String,String> map = new HashMap<>()
    implementation map.get("appcompat")

    //所以,实际上对于依赖第三方库最简洁的语法可以写成下面这种形式,遍历这个Map集合
    depConfig.each { k, v -> implementation v }
}

OK,经过上面的三个步骤就已经完成了对公共内容的抽离,然后可以按照同样的方法修改library moudule中的build.gradle文件中的内容。关于公共的配置相关的使用已经介绍的差不多了,但是别急还没完,因为你会发现config.gradle文件中定义的东西有两个还没用到,先说一下定义的isRelease,这个东西目前先放着因为暂时没啥好说的,就是一个属性,不过在下面说组件化的时候是十分重要的,它是用来识别是否是组件化以及决定module是组件还是单独的app的一个标识。

还有一个属性hostUrl我们也没有用到,那么想要用它我们就必须了解一个类BuildConfig,这个类的位置如下图所示:

它是存在于APK中的,你可以打包之后找到classes.dex文件,在这个文件中你就能看到它的存在。那么如果我们可以往这个类中添加一个属性,那是不是你就可以在任何地方调用你自定义的属性了,是不是非常方便,下面就来看一下该怎么添加自定义的属性呢?

如下所示:在build.gradle中的buildTypes中添加如下的两行代码,使用buildConfigField()这个方法来进行添加:

buildTypes {
        //public void buildConfigField(@NonNull String type,@NonNull String name,@NonNull String value){}
        //此方法接收三个非空参数:①确定值的类型;②指定key的名字;③传值(必须是String)
        //注意:不能在android根节点,只能在defaultConfig或者buildTypes节点下
        debug {
            buildConfigField("String", "DEBUG_URL", "\"${hostUrl.debug}\"")
        }
        release {
            buildConfigField("String", "RELEASE_URL", "\"${hostUrl.release}\"")
        }
    }

此时你再次进行同步就可以发现已经添加进去了,你当前是什么环境它就给你添加了哪个属性,比如我这里是Debug环境:

关于Gradle的语法部分就先介绍这么多,以上这些都是咱们这次能用到的,需要熟练掌握。

二、组件化项目部署

2.1、组件化项目的意义


组件化简单来说就是把一个非常完整的功能拆分成多个子模块,每个子模块又可以独立的编译和运行,也可以任意组合成为一个新的App

  • 开发需求:不相互依赖,可以相互交互、任意组合、高度解耦
  • 团队效率:分模块打包、测试,统一版本管理

2.2、Phone Module和Android Library的区别

①、Phone Module

  • 新建出可以独立运行的模块,可以看成是app,配置为:apply plugin:'com.android.application'
  • 它有applicationId
  • 切换为Android Library可以修改配置为:apply plugin:'com.android.library'

②、Android Library

  • 新建出Android库,不能独立运行,配置为:apply plugin:'com.android.library'
  • 没有applicationId
  • 切换为Phone Module可以修改配置为:apply plugin:'com.android.application'

2.3、项目构建基础

首先,新建一个空的工程,然后创建common公共基础库,接着分别创建四个module:home、product、order、personal,关于如何创建工程以及module,我这里就不详细说了,这些都是最基础最简单的,如果你不会请自行谷歌或百度。
然后,我们按照上面第一大部分介绍的Gradle相关的知识点,来创建config.gradle,并且在工程根目录中引入,然后依次修改每个module下的build.gradle文件,关于这一部分的操作步骤,在上面第一大点中已经详细列出了,可以回过头去翻翻看。
这里提几点组件化开发的规范:

  • app module:可不改,让它默认
  • order module:order_前缀(src类和res资源)
  • personal module:personal_前缀(src类和res资源)

简单点说就是尽量每个单独的module都使用modulename_xxx这种形式,比如:
 

2.4、集成化模式和组件化模式开发

关于集成化模式和组件化模式就要用到我们上面定义的isRelease这个属性了,还记得config.gradle中定义过这个东西嘛?

//生产/开发环境(或者叫做正式/测试环境)
//false:组件化模式(子模块可以独立运行)true:集成化模式(打包整个项目apk,子模块不可独立运行)
isRelease = true

它的出现就是为了方便我们去切换是使用的集成化模式还是组件化模式,当然每个module的build.gradle文件中也要做些修改:

首先是头部的module类型的配置,需要根据isRelease来灵活实现可控:

if (isRelease) { //如果是发布版本,各个模块都不能独立运行
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

然后是android闭包下的defaultConfig节点下的applicationId属性,因为集成化模式下是没有这个属性的:

android {
    defaultConfig {
        if (!isRelease) { //如果是集成化模式,不能有applicationId
            applicationId appId.home //组件化模式独立运行时才可以有applicationId
        }
    }
}

然后依次修改每个module中的这几项配置即可,对于app module中的依赖项,也需要使用该属性进行判断:

    //如果是集成化模式,在发布版本时,各个模块都不能独立运行了
    if (isRelease){
        implementation project(':home')
        implementation project(':product')
        implementation project(':order')
        implementation project(':personal')
    }

配置完成之后,集成化模式开发和组件化模式开发项目结构对比:
  

2.5、动态隔离

组件化开发的临时代码,集成化打包时动态隔离:这句话是什么意思呢?比如当前项目每个module单独分给了项目组中的一个成员去做,每个人在开发的时候使用组件化模式单独编译运行时不可避免的会添加一些额外的测试类,而针对于这些额外的代码实际上在集成化模式下都是不需要的,它们无需合并到主工程中去,所以需要采用动态隔离技术在集成时将这部分代码剔除掉。

首先说几点规范:

①、Manifest.xml文件在单独运行时,我们在main文件夹下新建一个debug包,配置一份组件化模式下使用的清单文件:

②、在main/java/packageName目录下新建debug目录,将测试类写在该目录下:

③、在module的build.gradle文件中的android闭包下添加如下配置:

    //配置资源路径,方便测试环境,打包不集成到正式环境
    sourceSets{
        main{
            if (!isRelease){
                //如果是组件化模式,需要单独运行时
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            }else {
                //集成化模式,整个项目打包到apk
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java{
                    //release时debug目录下文件不需要合并到主工程
                    exclude '**/debug/**'
                }
            }
        }
    }

三、子模块间交互

Module与Module之间该怎么交互呢?(包括:跳转、传参等)
比如:personal子模块需要跳转到order子模块怎么办?方式有很多,简单列举几个:

  • EventBus:EventBean非常多(一对一),一对多就会很混乱难以维护
  • 反射:反射技术可以成功,维护成本较高且容易出现高版本(Android9.0以上)@hide限制
  • 隐式意图:维护成本还好,只是比较麻烦,需要维护Manifest中的action
  • BroadCastReceiver:需要动态注册(7.0以后),需求方发送广播
  • 类加载:需要准确的全类名路径,维护成本较高且容易出现人为性的失误(比如类名打错了)
  • ......

实现方案一:类加载技术交互

实现方案二:全局Map记录信息

有人该说了,这种方式为什么可以啊?因为虽然是各自不同的module,但是集成化模式下打包之后它们都是在主包名这个目录下的,只不过目前是相互隔离的,想要做交互,你只要知道它们各自的地址就可以了:

然后定义一个PathManager类,作为全局的路径管理器:

public class PathManager {

    //key:order组  value:某个子模块下对应所有的Activity路径信息
    private static Map<String, List<PathBean>> groupMap = new HashMap<>();

    /**
     * 将路径信息加入全局Map
     * @param groupName 组名
     * @param pathName 路径名
     * @param clazz 类对象
     */
    public static void joinGroup(String groupName,String pathName,Class<?> clazz){
        List<PathBean> list = groupMap.get(groupName);
        if (list == null){
            list = new ArrayList<>();
            list.add(new PathBean(pathName,clazz));
            groupMap.put(groupName,list);
        }else {
            for (PathBean pathBean : list) {
                if (!pathName.equals(pathBean.getPath())){
                    list.add(new PathBean(pathName,clazz));
                    groupMap.put(groupName,list);
                }
            }
        }
    }

    /**
     * 根据组名和路径名获取类对象,达到跳转目的
     * @param groupName 组名
     * @param pathName 路径名
     * @return 跳转目标的class对象
     */
    public static Class<?> getTargetClass(String groupName,String pathName){
        List<PathBean> list = groupMap.get(groupName);
        if (list == null) return null;
        for (PathBean pathBean : list) {
            if (pathName.equalsIgnoreCase(pathBean.getPath())){
                return pathBean.getClazz();
            }
        }
        return null;
    }
}

接着在app module的Application类中将所需的类添加进去:

public class MainApp extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
        PathManager.joinGroup("app","MainActivity",MainActivity.class);
        PathManager.joinGroup("personal","Personal_MainActivity", Personal_MainActivity.class);
        PathManager.joinGroup("order","Order_MainActivity", Order_MainActivity.class);
    }
}

这样我们就可以在personal module中与其它模块的module进行交互了:

//全局Map跳转
Class<?> targetClass = PathManager.getTargetClass("order", "Order_MainActivity");
if (targetClass == null) return;
Intent intent = new Intent(this, targetClass);
intent.putExtra("params", "jarchie");
startActivity(intent);

上面简单的介绍了一下实现子模块跳转的两种方式,现在我们来思考一下:能不能将类加载和全局Map进行合并,进而做成一个架构来解决这种比较糟糕的问题呢?答案是可以的,这就是后面会说到的路由框架。

如果是将第一种和第二种这两种方案进行结合,第一种方案还好,对于第二种方案来说,我们需要在app module中添加很多的类,数量少了还好,如果有几十个甚至几百个Activity我想你已经面临崩溃的边缘了,即使你能写看着也是相当恶心的一件事。所以接下来就来介绍一种更优雅的实现方案——注解处理器,也叫APT技术,通过它可以帮助我们自动生成文件。

四、APT介绍与使用

4.1、APT简介

  • APT:全称Annotation Processing Tool
  • 是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。也可以这样理解,只有通过生命APT工具后,程序在编译期间自定义注解解释器才能执行。
  • 简单来说:根据规则帮我们生成代码、生成类文件

熟悉HTML的朋友都知道HTML是一种结构体语言,它是由element组成的结构体,比如<html>标签、<body>标签、<div>标签等等,同样的,对于Java的源文件来说,它也是一种结构体语言,比如你的类名肯定是要写在包名下面的吧,在这里简单介绍一下Element程序元素:

  • PackageElement:表示一个包程序元素。提供对有关包及其成员的信息的访问
  • ExecutableElement:表示某个类或接口的方法、构造方法或初始化程序(静态或实例)
  • TypeElement:表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问
  • VariableElement:表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数

需要掌握的API如下:

属性名 
getEnclosedElements()返回该元素直接包含的子元素
getEnclosingElement()返回包含该element的父element,与上一个方法相反
getKind()返回element的类型,判断是哪种element
getModifiers()获取修饰关键字,如public static final等关键字
getSimpleName()获取名字,不带包名
getQualifiedName()获取全名,如果是类的话,包含完整的包名路径
getParameters()获取方法的参数元素,每个元素是一个VariableElement
getReturnType()获取方法元素的返回值
getConstantValue()如果属性变量被final修饰,则可以使用该方法获取它的值

4.2、APT实战

①、创建注解库

首先新建一个Module选中Java Library,我这里是命名为annotation,创建Module的过程就不再详细赘述了,没什么好说的。

接着打开build.gradle文件,为了防止输出的日志信息乱码,需要在gradle中添加一句话,指定编码为UTF-8:

tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

然后新建一个注解类ARouter,由于我们的路径是modulename/ActivityName这种形式的,所以我们定义两个方法:一个是指定详细路径,一个是它的分组,关于注解的一些使用方法这里也列出了一些:

/**
 * @Target(ElementType.TYPE) //接口、类、枚举、注解
 * @Target(ElementType.FIELD) //属性、枚举的常量
 * @Target(ElementType.METHOD) //方法
 * @Target(ElementType.PARAMETER) //方法参数
 * @Target(ElementType.CONSTRUCTOR) //构造函数
 * @Target(ElementType.LOCAL_VARIABLE) //局部变量
 * @Target(ElementType.ANNOTATION_TYPE) //该注解使用在另一个注解上
 * @Target(ElementType.PACKAGE) //包
 * @Target(ElementType.RUNTIME) //注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容
 * 生命周期:SOURCE < CLASS < RUNTIME
 * 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
 * 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
 * 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ARouter {

    //详细路由路径(必填),比如:“app/MainActivity”
    String path();

    //从path中截取出来,规范开发者的编码
    String group() default "";

}

②、创建注解处理器库

有了注解以后,那就需要注解处理器登场了,所以跟上面一样,我们再创建一个Java Library,就叫它compiler,然后打开build.gradle文件,在里面添加谷歌的两个依赖库:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //注册注解,并对其生成META-INF的配置信息
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    //引入annotation,让注解处理器处理注解
    implementation project(':annotation')
}

tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "8"
targetCompatibility = "8"

然后我们来编写注解处理器这个类,它的作用就是来处理我们自定义的ARouter注解:

@AutoService(Processor.class) //通过AutoService来自动生成注解处理器用来作注册
@SupportedAnnotationTypes({"com.jarchie.annotation.ARouter"}) //获取支持的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8) //通过哪个jdk的版本进行编译
@SupportedOptions("content") //接收外部传入的参数
public class ARouterProcessor extends AbstractProcessor {

    //操作Element工具类
    private Elements elementUtils;

    //type(类信息)工具类
    private Types typeUtils;

    //用来输出警告、错误等日志信息
    private Messager messager;

    //文件生成器
    private Filer filer;

    //初始化工作
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        String content = processingEnvironment.getOptions().get("content");
        //此处有坑,不能像android中Log.e的写法
        messager.printMessage(Diagnostic.Kind.NOTE, content);
    }

    /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合(类 上面写了注解)
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) return false;
        //获取项目中所有使用了ARouter注解的节点
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        //遍历所有的类节点
        for (Element element : elements) {
            //类节点之上就是包节点
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            //获取简单类名
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "被注解的类有:" + className);
            //最终我们想要生成的类文件,比如:MainActivity$$ARouter
            String finalClassName = className + "$$ARouter";
            try {
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
                Writer writer = sourceFile.openWriter();
                //设置包名
                writer.write("package " + packageName + ";\n");
                writer.write("public class " + finalClassName + " {\n");
                writer.write("public static Class<?> findTargetClass(String path) {\n");
                //获取类之上@ARouter注解的path值
                ARouter aRouter = element.getAnnotation(ARouter.class);
                writer.write("if (path.equalsIgnoreCase(\"" + aRouter.path() + "\")) {\n");
                writer.write("return " + className + ".class;\n}\n");
                writer.write("return null;\n");
                writer.write("}\n}");
                //非常重要
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}

首先就是一些初始化的工作,然后在process()方法中处理注解,具体的代码都加了注释,相信大家也都很容易能看懂,我这里简单的提一下可能看不懂的地方——文件生成器,关于如何生成文件这个地方可能会有些懵逼,所以来解释一下,我项目中生成某个类的类名的格式我将其定义为XActivity$$ARouter,通过对比注解标注的路径,如果相等就将对应的Activity进行返回,比如我这里举个栗子就能明白了:

public class XActivity$$ARouter {

    public static Class<?> findTargetClass(String path){
        if (path.equalsIgnoreCase("/app/MainActivity")){
            return MainActivity.class;
        }
        return null;
    }

}

所以通过Writer去写入文件的东西跟上面这段代码的格式是一样的,简单直白的说就是搞一个文件File,然后一行一行的把类似上面的代码写到文件里,这是一种很传统的写入文件的技术。

五、APT高级用法JavaPoet

5.1、什么是JavaPoet

  • JavaPoet是square推出的开源java代码生成框架,提供Java API生成.java源文件
  • 框架功能实用,是我们习惯的Java面向对象OOP语法
  • 可以方便的使用它根据注解生成对应的代码
  • 通过自动化生成代码的方式,以一种更加简洁优雅的方式替代繁琐冗杂的重复工作
  • 项目主页及源码:https://github.com/square/javapoet

JavaPoet中的8个常用类:

类对象说明
MethodSpec代表一个构造函数或方法声明
TypeSpec代表一个类,接口,或者枚举声明
FieldSpec代表一个成员变量,一个字段声明
JavaFile包含一个顶级类的Java文件
ParameterSpec用来创建参数
AnnotationSpec用来创建注解
ClassName用来包装一个类
TypeName类型,如在添加返回值类型是使用TypeName.VOID

JavaPoet字符串格式化规则:

  • $L:字面量,如:“int value=$L”,10
  • $S:字符串,如:$S,“hello”
  • $T:类、接口,如:$T,MainActivity
  • $N:变量,如:user.$N,name

还记得Java中的字符串格式化是什么样的吗,和这个是很类似的,比如下面这个,输出的结果就是Jarchie的年龄为:20

public class JavaPoetTest {

    public void test(){
        String s = String.format("%s的年龄为:%d","Jarchie",20);
        System.out.println(s);
    }

}

只是符号规则有些不同而已,完全可以类比着去修改。

5.2、JavaPoet代码实战

①、添加依赖

在上一部分的基础上,我们打开compiler这个module,在注解处理器库中再添加一个依赖:

//帮助我们通过类调用的方式来生成Java代码
implementation 'com.squareup:javapoet:1.13.0'

②、编写生成文件的模拟类

其实和上一部分中写的那个模拟的示例代码是一样的,只不过这里是把它调整成了三目运算而已:

public class XActivity$$ARouter {

    public static Class<?> findTargetClass(String path) {
        return path.equalsIgnoreCase("/app/MainActivity") ? MainActivity.class : null;
    }

}

③、修改注解处理器

我们需要把上一部分中写好的注解处理器稍作修改,修改的部分也就是文件生成器的部分,改为使用JavaPoet来实现:

/**************************** ②、JavaPoet方案 *****************************/
            ARouter aRouter = element.getAnnotation(ARouter.class);
            MethodSpec methodSpec = MethodSpec.methodBuilder("findTargetClass")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(Class.class)
                    .addParameter(String.class, "path")
                    .addStatement("return path.equalsIgnoreCase($S) ? $T.class : null",
                            aRouter.path(),
                            ClassName.get((TypeElement) element))
                    .build();

            TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(methodSpec)
                    .build();

            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .build();

            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }

六、组件化路由架构设计

上面这几部分我们先是说到了类加载、全局Map记录实现组件化模块之间的交互,后面又衍生出了APT的技术生成一些类文件。那么,既然是做组件化架构,我们究竟需要在组件化架构中通过APT和JavaPoet技术生成什么样子的类文件呢?如下图中所示:我们分成了一个一个的组,每个组下面再有对应的类:

问题思考:

①、为什么需要组名?

我们在注解库中定义了两个方法,一个是获取注解作用的路径,另一个是获取它的组名,有人会问了,有路径了还不够吗,为什么还要组名呢?这里简单思考一下,一个项目中一般情况下都是有多个模块的,每个模块下面都有不止一个Activity,而用户很有可能是进入app之后什么都没有操作就按下返回键退出了,所以我们需要给每个模块指定一个组名,完了之后呢,比如我在首页默认只加载一两个必经的Activity,这样做的好处是我们不用一次性把所有Activity都加载到内存中,更加的节省内存。

②、上面生成的文件有什么用?

我们在上面花了挺长的时间来学习这个注解处理器生成文件,那么这些文件生成完了有什么用呢?可以说,上面那些我们只是介绍了一下用法,说了一下原理,因为原理都是互通的,根据path路径来获取class对象,我们项目中需要从path路径中截取出一个组名,比如"/app/MainActivity"这里组名就是app,然后到生成的组文件记录中去匹配,比如ARouter$$Group$$app,然后判断组名是否存在,存在就去找到生成的Class文件比如ARouter$$Path$$app,这个文件中再去匹配某个具体的Activity,最后把匹配到的Class文件返回。

上面简单的做了两个问题思考,下面就开始这一部分的正题——组件化路由架构的设计。

6.1、配置arouter_api

创建一个AndroidLibrary,这里面存放的是ARouter的api,定义两个接口组接口和详细路径Path接口,对外提供加载Class对象的方法:

public interface ARouterLoadGroup {

    /**
     * 记载路由组Group数据
     * 比如:“app”,ARouter$$Path$$app.class(实现了ARouterLoadPath接口)
     * @return key:"app",value:"app" 分组对应的路由详细对象类
     */
    Map<String,Class<? extends ARouterLoadPath>> loadGroup();

}
public interface ARouterLoadPath {

    /**
     * 记载路由组Group中的Path详细数据
     * 比如:"app"分组下有这些信息
     * @return key:"/app/MainActivity",value:MainActivity 信息封装到RouterBean对象中
     */
    Map<String, RouterBean> loadPath();

}

RouterBean就是PathBean的一个升级版,我们把它创建在arouter_annotation这个module下: 

public class RouterBean {

    public enum Type {
        ACTIVITY
    }

    //枚举类型
    private Type type;
    //节点类
    private Element element;
    //被@ARouter注解的类对象
    private Class<?> clazz;
    //路由的地址
    private String path;
    //路由的组名
    private String group;
}

6.2、配置common

在common公共库的build.gradle文件中将arouter_api这个库依赖进来并且提供的是双向依赖,由于每个module库都依赖了common公共库,所以每个module库就都能依赖上arouter_api了:

dependencies {

    //每个模块都要用到arouter_api模块对外提供的加载数据接口(组、路径)
    //各个module都依赖于common公共基础库
    api project(':arouter_api') //双向依赖,这些接口同样对所有业务子模块开放了

}

6.3、APT生成路由Group和Path类文件

(一)、首先来看一下我们最终要生成的组文件和类文件是什么样子的?简单的举两个例子:

Group:1对1

public class ARouter$$Group$$order implements ARouterLoadGroup {
    @Override
    public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
        Map<String,Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
        groupMap.put("order",ARouter$$Path$$order.class);
        return groupMap;
    }
}

Path:1对N

public class ARouter$$Path$$order implements ARouterLoadPath {
    @Override
    public Map<String, RouterBean> loadPath() {
        Map<String,RouterBean> pathMap = new HashMap<>();
        pathMap.put("/order/Order_MainActivity",RouterBean.create(RouterBean.Type.ACTIVITY,
                Order_MainActivity.class,
                "/order/Order_MainActivity",
                "order"));
        return pathMap;
    }
}

生成的文件样式就是下图中这种格式的: 

(二)、修改注解处理器来生成上面的这两类文件:

①、首先我们需要传递两个参数过来,一个是module的名称,一个是生成文件的存放路径包名,参数同样通过build.gradle中的配置传递过来;

②、填充process方法

  • 首先获取所有被@ARouter注解标记的元素集合
  • 然后解析所有被@ARouter注解的元素集合:获取每个元素的类信息,获取每个类上的@ARouter注解对应的path值,然后将路由详细信息封装到实体类RouterBean
  • 赋值临时Map存储路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
  • 生成路由的详细Path类文件,如ARouter$$Path$$app
  • 生成路由组Group类文件(没有Path类文件则取不到),如ARouter$$Group$$app

由于篇幅太长了,我这里就直接上完整代码了,具体的流程就是上面说的这几步,代码里也给了详细注释:

/**
 * 作者:created by Jarchie
 * 时间:2020/9/10 14:17:46
 * 邮箱:jarchie520@gmail.com
 * 说明:创建注解处理器
 */

@AutoService(Processor.class) //通过AutoService来自动生成注解处理器用来作注册
@SupportedAnnotationTypes({Constants.AROUTER_ANNOTATION_TYPES}) //获取支持的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8) //通过哪个jdk的版本进行编译
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE}) //接收build.gradle传过来的参数
public class ARouterProcessor extends AbstractProcessor {

    //操作Element工具类
    private Elements elementUtils;

    //type(类信息)工具类
    private Types typeUtils;

    //用来输出警告、错误等日志信息
    private Messager messager;

    //文件生成器
    private Filer filer;

    //子模块名称,如:app/order等模块,需要拼接类名时用到(必传)ARouter$$Group$$app
    private String moduleName;

    //包名,用于存放APT生成的类文件
    private String packageNameForAPT;

    //临时Map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
    //key:组名"app",value:"app"组的路由路径"ARouter$$Path$$app.class"
    private Map<String, List<RouterBean>> tempPathMap = new HashMap<>();

    //临时map存储,用来存放路由Group信息,生成路由组类文件时遍历
    //key:组名"app",value:类名"ARouter$$Path$$app.class"
    private Map<String,String> tempGroupMap = new HashMap<>();

    //初始化工作
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();

        //通过ProcessingEnvironment去获取对应的参数
        Map<String, String> options = processingEnvironment.getOptions();
        if (!EmptyUtils.isEmpty(options)) {
            moduleName = options.get(Constants.MODULE_NAME);
            packageNameForAPT = options.get(Constants.APT_PACKAGE);
            //此处有坑,不能像android中Log.e的写法
            messager.printMessage(Diagnostic.Kind.NOTE, "moduleName >>> " + moduleName);
            messager.printMessage(Diagnostic.Kind.NOTE, "packageName >>> " + packageNameForAPT);
        }

        //必传参数判空(乱码问题:添加java控制台输出中文乱码)
        if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
            throw new RuntimeException("注解处理器需要的参数moduleName或者packageName为空,请在对应的build.gradle中配置参数");
        }

    }

    /**
     * 相当于main函数,开始处理注解
     * 注解处理器的核心方法,处理具体的注解,生成Java文件
     *
     * @param set              使用了支持处理注解的节点集合(类 上面写了注解)
     * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解
     * @return true 表示后续处理器不会再处理(已经处理完成)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //一旦有类上面使用了@ARouter注解
        if (!EmptyUtils.isEmpty(set)) {
            //获取项目中所有使用了ARouter注解的节点
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            if (!EmptyUtils.isEmpty(elements)) {
                //解析元素
                try {
                    parseElements(elements);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            //此处有坑,必须写
            return true;
        }
        return false;
    }

    //解析所有被@ARouter注解的元素集合
    private void parseElements(Set<? extends Element> elements) throws IOException {
        //通过Element工具类,获取Activity类型
        TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
        //显示类信息
        TypeMirror activityMirror = activityType.asType();
        for (Element element : elements) {
            //获取每个元素的类信息
            TypeMirror elementMirror = element.asType();
            messager.printMessage(Diagnostic.Kind.NOTE, "遍历的元素信息为:" + elementMirror.toString());
            //获取每个类上的@ARouter注解,对应的path值
            ARouter aRouter = element.getAnnotation(ARouter.class);
            //路由详细信息,封装到实体类
            RouterBean bean = new RouterBean.Builder()
                    .setGroup(aRouter.group())
                    .setPath(aRouter.path())
                    .setElement(element)
                    .build();
            //高级判断,@ARouter注解仅仅只能作用在类上,并且是规定的Activity
            if (typeUtils.isSubtype(elementMirror, activityMirror)) {
                bean.setType(RouterBean.Type.ACTIVITY);
            } else {
                throw new RuntimeException("@ARouter注解目前仅限用于Activity之上");
            }

            //赋值临时map存储以上信息,用来遍历时生成代码
            valueOfPathMap(bean);
        }

        //ARouterLoadGroup和ARouterLoadPath类型,用来生成类文件时实现接口
        TypeElement groupLoadType = elementUtils.getTypeElement(Constants.AROUTER_GROUP);
        TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTER_PATH);

        //1、生成路由的详细Path类文件,如:ARouter$$Path$$app
        createPathFile(pathLoadType);

        //2、生成路由组Group类文件(没有Path类文件则取不到),如:ARouter$$Group$$app
        createGroupFile(groupLoadType, pathLoadType);
    }

    /**
     * 生成路由组Group对应详细Path,如ARouter$$Path$$app
     *
     * @param pathLoadType ARouterLoadPath接口信息
     */
    private void createPathFile(TypeElement pathLoadType) throws IOException {
        if (EmptyUtils.isEmpty(tempPathMap)) return;
        //方法的返回值 Map<String, RouterBean>
        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouterBean.class));
        //遍历分组,每一个分组创建一个路径类文件,如:ARouter$$Path$$app
        for (Map.Entry<String, List<RouterBean>> entry : tempPathMap.entrySet()) {
            //public Map<String, RouterBean> loadPath() {
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME) //方法名
                    .addAnnotation(Override.class) //重写注解
                    .addModifiers(Modifier.PUBLIC) //public修饰符
                    .returns(methodReturns); //返回值
            //不循环部分:Map<String,RouterBean> pathMap = new HashMap<>();
            methodBuilder.addStatement("$T<$T,$T> $N = new $T<>()",
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouterBean.class),
                    Constants.PATH_PARAMETER_NAME,
                    ClassName.get(HashMap.class));
            ///app/MainActivity,/app/...
            List<RouterBean> pathList = entry.getValue();
            //方法内容的循环部分
            /* pathMap.put("/order/Order_MainActivity",RouterBean.create(RouterBean.Type.ACTIVITY,
                    Order_MainActivity.class,
                    "/order/Order_MainActivity",
                    "order"));
                    */
            for (RouterBean bean : pathList) {
                methodBuilder.addStatement("$N.put($S,$T.create($T.$L,$T.class,$S,$S))",
                        Constants.PATH_PARAMETER_NAME, //pathMap.put
                        bean.getPath(), //"/app/MainActivity"
                        ClassName.get(RouterBean.class),
                        ClassName.get(RouterBean.Type.class),
                        bean.getType(), //枚举Activity
                        ClassName.get((TypeElement) bean.getElement()), //MainActivity.class
                        bean.getPath(), //"/app/MainActivity"
                        bean.getGroup()); //"app"
            }
            //遍历过后,最后return pathMap
            methodBuilder.addStatement("return $N", Constants.PATH_PARAMETER_NAME);
            //生成类文件,如ARouter$$Path$$app
            String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
            messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件为:"
                    + packageNameForAPT + "." + finalClassName);
            JavaFile.builder(packageNameForAPT, //包路径
                    TypeSpec.classBuilder(finalClassName) //类名
                            .addSuperinterface(ClassName.get(pathLoadType)) //实现接口
                            .addModifiers(Modifier.PUBLIC) //修饰符
                            .addMethod(methodBuilder.build()) //方法的构建
                            .build())
            .build()
            .writeTo(filer); //类构建完成

            //别忘了,非常重要赋值,路径文件生成出来了,才能赋值路由组tempGroupMap
            tempGroupMap.put(entry.getKey(),finalClassName);
        }
    }

    /**
     * 生成路由组Group文件,如:ARouter$$Group$$app
     *
     * @param groupLoadType ARouterLoadGroup接口信息
     * @param pathLoadType  ARouterLoadPath接口信息
     */
    private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
        // 判断是否有需要生成的类文件
        if (EmptyUtils.isEmpty(tempGroupMap) || EmptyUtils.isEmpty(tempPathMap)) return;

        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class), // Map
                ClassName.get(String.class), // Map<String,
                // 第二个参数:Class<? extends ARouterLoadPath>
                // 某某Class是否属于ARouterLoadPath接口的实现类
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
        );

        // 方法配置:public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
        MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名
                .addAnnotation(Override.class) // 重写注解
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .returns(methodReturns); // 方法返回值

        // 遍历之前:Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
        methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
                Constants.GROUP_PARAMETER_NAME,
                HashMap.class);

        // 方法内容配置
        for (Map.Entry<String, String> entry : tempGroupMap.entrySet()) {
            // 类似String.format("hello %s net163 %d", "net", 163)通配符
            // groupMap.put("main", ARouter$$Path$$app.class);
            methodBuidler.addStatement("$N.put($S, $T.class)",
                    Constants.GROUP_PARAMETER_NAME, // groupMap.put
                    entry.getKey(),
                    // 类文件在指定包名下
                    ClassName.get(packageNameForAPT, entry.getValue()));
        }

        // 遍历之后:return groupMap;
        methodBuidler.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);

        // 最终生成的类文件名
        String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
        messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
                packageNameForAPT + "." + finalClassName);

        // 生成类文件:ARouter$$Group$$app
        JavaFile.builder(packageNameForAPT, // 包名
                TypeSpec.classBuilder(finalClassName) // 类名
                        .addSuperinterface(ClassName.get(groupLoadType)) // 实现ARouterLoadGroup接口
                        .addModifiers(Modifier.PUBLIC) // public修饰符
                        .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
                        .build()) // 类构建完成
                .build() // JavaFile构建完成
                .writeTo(filer); // 文件生成器开始生成类文件
    }

    /**
     * 赋值临时map存储用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
     *
     * @param bean
     */
    private void valueOfPathMap(RouterBean bean) {
        if (checkRouterPath(bean)) {
            messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
            //开始赋值
            List<RouterBean> routerBeans = tempPathMap.get(bean.getGroup());
            //如果从Map中找不到key
            if (EmptyUtils.isEmpty(routerBeans)) {
                routerBeans = new ArrayList<>();
                routerBeans.add(bean);
                tempPathMap.put(bean.getGroup(), routerBeans);
            } else { //找到了key,直接加入临时集合
                routerBeans.add(bean);
            }
        } else {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范,如:/app/MainActivity");
        }
    }

    /**
     * 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
     *
     * @param bean 路由详细信息,最终实体封装类
     * @return boolean
     */
    private boolean checkRouterPath(RouterBean bean) {
        String group = bean.getGroup();
        String path = bean.getPath();
        //@ARouter注解的path值,必须要以/开头(模仿阿里ARouter路由架构)
        if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范,如:/app/MainActivity");
            return false;
        }
        //开发者未按规定编写,path = "/MainActivity"
        if (path.lastIndexOf("/") == 0) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范,如:/app/MainActivity");
            return false;
        }
        //从第一个/到第二个/中间截取出组名
        String finalGroup = path.substring(1, path.indexOf("/", 1));
        //如果代码为:path = "/MainActivity/MainActivity/MainActivity"
        if (finalGroup.contains("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范,如:/app/MainActivity");
            return false;
        }
        //@ARouter注解中有group赋值
        if (!EmptyUtils.isEmpty(group) && !group.equals(moduleName)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和当前子模块名相同");
            return false;
        } else {
            bean.setGroup(finalGroup);
        }
        return true;
    }

}

6.4、组件化APT生成路由动态参数类文件

这一部分同样是使用APT技术来生成路由动态参数的类文件,步骤跟上面很类似,所以我就说的快一点。

例如:我们在app module中的MainActivity中添加两个需要传递的参数:

String name;
int age = 0;

同样的在arouter_api这个module中定义一个接口用来加载参数:

public interface ParameterLoad {

    /**
     * 目标对象.属性名 = target.getIntent().属性类型("注解值or属性名");完成赋值
     *
     * @param target 目标对象,如:MainActivity(中的某些属性)
     */
    void loadParameter(Object target);
}

那么我们需要生成的类文件格式如下,注意需要和Activity在同一个包下:

public class XActivity$$Parameter implements ParameterLoad {
    @Override
    public void loadParameter(Object target) {
        //一次
        MainActivity t = (MainActivity) target;
        //循环
        t.name = t.getIntent().getStringExtra("name");
        t.age = t.getIntent().getIntExtra("age",t.age);
    }
}

接着,我们需要再次创建一个用来生成参数文件的注解处理器,具体写法跟上面都很类似:

@AutoService(Processor.class) //通过AutoService来自动生成注解处理器用来作注册
@SupportedAnnotationTypes({Constants.PARAMETER_ANNOTATION_TYPES}) //获取支持的注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_8) //通过哪个jdk的版本进行编译
public class ParameterProcessor extends AbstractProcessor {

    private Elements elementUtils;
    private Types typeUtils;
    private Messager messager;
    private Filer filer;

    // 临时map存储,用来存放被@Parameter注解的属性集合,生成类文件时遍历
    // key:类节点, value:被@Parameter注解的属性集合
    private Map<TypeElement, List<Element>> tempParameterMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 一旦有类之上使用@Parameter注解
        if (!EmptyUtils.isEmpty(set)) {
            // 获取所有被 @Parameter 注解的 元素(属性)集合
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Parameter.class);

            if (!EmptyUtils.isEmpty(elements)) {
                // 解析元素
                try {
                    // 赋值临时map存储,用来存放被注解的属性集合
                    valueOfParameterMap(elements);
                    // 生成类文件,如:
                    createParameterFile();
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }

    private void createParameterFile() throws IOException {
        // 判断是否有需要生成的类文件
        if (EmptyUtils.isEmpty(tempParameterMap)) return;
        // 通过Element工具类,获取Parameter类型
        TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
        TypeElement parameterType = elementUtils.getTypeElement(Constants.PARAMETER_LOAD);

        // 参数体配置(Object target)
        ParameterSpec parameterSpec = ParameterSpec.builder(TypeName.OBJECT, Constants.PARAMETER_NAMR).build();
        for (Map.Entry<TypeElement, List<Element>> entry : tempParameterMap.entrySet()) {
            // Map集合中的key是类名,如:MainActivity
            TypeElement typeElement = entry.getKey();
            // 如果类名的类型和Activity类型不匹配
            if (!typeUtils.isSubtype(typeElement.asType(), activityType.asType())) {
                throw new RuntimeException("@Parameter注解目前仅限用于Activity类之上");
            }

            // 获取类名
            ClassName className = ClassName.get(typeElement);
            // 方法体内容构建
            ParameterFactory factory = new ParameterFactory.Builder(parameterSpec)
                    .setMessager(messager)
                    .setClassName(className)
                    .build();

            // 添加方法体内容的第一行
            factory.addFirstStatement();

            // 遍历类里面所有属性
            for (Element fieldElement : entry.getValue()) {
                factory.buildStatement(fieldElement);
            }

            // 最终生成的类文件名(类名$$Parameter)
            String finalClassName = typeElement.getSimpleName() + Constants.PARAMETER_FILE_NAME;
            messager.printMessage(Diagnostic.Kind.NOTE, "APT生成获取参数类文件:" +
                    className.packageName() + "." + finalClassName);

            // MainActivity$$Parameter
            JavaFile.builder(className.packageName(), // 包名
                    TypeSpec.classBuilder(finalClassName) // 类名
                            .addSuperinterface(ClassName.get(parameterType)) // 实现ParameterLoad接口
                            .addModifiers(Modifier.PUBLIC) // public修饰符
                            .addMethod(factory.build()) // 方法的构建(方法参数 + 方法体)
                            .build()) // 类构建完成
                    .build() // JavaFile构建完成
                    .writeTo(filer); // 文件生成器开始生成类文件
        }
    }

    /**
     * 赋值临时map存储,用来存放被@Parameter注解的属性集合,生成类文件时遍历
     *
     * @param elements 被 @Parameter 注解的 元素集合
     */
    private void valueOfParameterMap(Set<? extends Element> elements) {
        for (Element element : elements) {
            // 注解在属性之上,属性节点父节点是类节点
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
            // 如果map集合中的key:类节点存在,直接添加属性
            if (tempParameterMap.containsKey(enclosingElement)) {
                tempParameterMap.get(enclosingElement).add(element);
            } else {
                List<Element> fields = new ArrayList<>();
                fields.add(element);
                tempParameterMap.put(enclosingElement, fields);
            }
        }
    }

}

具体使用方式:

传递方:

@Parameter
String name;
@Parameter
int age = 0;

Intent intent = new Intent(this, routerBean.getClazz());
intent.putExtra("name", "jarchie");
intent.putExtra("age", 27);
startActivity(intent);

接收方:

    @Parameter
    String name;

    @Parameter
    int age = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.order_activity_main);
        ParameterLoad parameterLoad = new Order_MainActivity$$Parameter();
        parameterLoad.loadParameter(this);
        Log.e("测试接收数据------>", name);
    }

OK,到这里我们就完成了Activity页面之间的跳转和传值,今天也只会介绍Activity的跳转和传值,文章有点长了,怕大家读起来反胃,还请各位大佬见谅!

下一篇我会写一篇基于阿里的开源框架ARouter完成组件化架构的详细使用方法,今天的内容就先这么多吧!

另:今天是1024哦,祝各位大佬节日快乐!下期再会!

祝:工作顺利!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值