参考:
gradle组件化配置
Android组件化开发的配置,离不开gradle构建工具,它的出现让工程有无限的可能。gradle核心是基于groovy脚本语言,groovy脚本基于java且扩展了java,所以gradle需要依赖JDK和Groovy库。
gradle简单介绍
gradle语法:从gradle的日志输出开始讲解组件化设计之旅
// 第一种打印字符串的方式
println("hello, gradle!")
// 第二种打印字符串的方式
println "hello2, gradle!"
从这两种字符串输出方式可以看出,方法可以不写括号,一句话后可以不写分号,这是groovy的特性。
可以在Android工程中的build.gradle文件中使用println函数输出日志。然后通过 Build->Toggle view 查看build输出的日志
自定义属性
gradle可以添加额外的自定义属性,通过ext属性实现。先新建一个config.gradle文件,并自定义isRelease属性,用于动态切换:组件化模式/集成化模式
ext {
// false: 组件化模式(子模块可以独立运行)
// true :集成化模式(打包整个项目apk,子模块不可独立运行)
isRelease = true
}
那么这个config文件怎么使用呢?需要在项目的根build.gradle文件通过 apply from 方式引用config.gradle文件
// build.gradle
// 可以通过apply from方式引用
apply from: 'config.gradle'
然后在app应用项目的build.gradle文件中使用自定义属性
// build.gradle
// 使用自定义属性,属性需要写在${}中
println "${rootProject.ext.isRelease}"
config.gradle文件基本配置
再新建一个购物shop库模块,项目结构如下图:
查看app应用模块和shop库模块中的build.gradle文件,发现上图红色框配置很多类似,那么是不是可以使用分模块方式配置呢,使用gradle自定义属性将共性的配置抽取出来,放在单独的文件里,供其他build引用?答案是可以的。
接下来动手操作下,红色框的配置都是跟Android版本有关的,可以定义一个Map集合存相关属性信息,配置如下:
// config.gradle
ext {
// 定义Map存取版本相关的信息,key名称可以任意取
versions = [
compileSdkVersion: 29,
buildToolsVersion: "29.0.2",
minSdkVersion : 21,
targetSdkVersion : 29,
versionCode : 1,
versionName : "1.0"
]
}
然后在app应用模块和shop库模块中的build.gradle文件,访问Map集合中定义的属性,例如app应用模块build.gradle中访问Map中自定义的属性
// 获取Map
def versions = rootProject.ext.versions
android {
// 直接通过map.key访问值
compileSdkVersion versions.compileSdkVersion
buildToolsVersion versions.buildToolsVersion
defaultConfig {
applicationId "com.example.modular.todo"
minSdkVersion versions.minSdkVersion
targetSdkVersion versions.targetSdkVersion
versionCode versions.versionCode
versionName versions.versionName
}
}
1. applicationId配置
在组件化模块与集成化模块做切换,组件化模块为了能够独立运行,将库模块切换成应用模块时需要设置applicationId
所以还需要配置不同的applicationId属性
// config.gradle
ext {
// 组件化与集成化切换时,设置不同的applicationId
appId = [
app : "com.example.modular.todo",
shop: "com.example.modular.shop"
]
}
在app应用模块中的build.gradle文件中使用
def appId = rootProject.ext.appId
android {
defaultConfig {
applicationId appId.app
}
}
2. 代码中生产和正式环境配置切换
有的时候还需要在代码中切换生产和正式环境配置,如:网络请求的URL。Android为我们提供自定义BuildConfig功能。
// config.gradle
ext {
baseUrl = [
debug : "https://127.0.0.1/debug", // 测试版本URL
release: "https://127.0.0.1/relase" // 正式版本URL
]
}
在app应用模块build.gradle文件中通过buildConfigField配置,在代码中就可以通过BuildConfig.baseUrl访问到了。
// build.gradle
def baseUrl = rootProject.ext.baseUrl
android {
buildTypes {
debug {
// void buildConfigField(
// @NonNull String type,
// @NonNull String name,
// @NonNull String value) { }
buildConfigField("String", "baseUrl", "\"${baseUrl.debug}\"")
}
release {
buildConfigField("String", "baseUrl", "\"${baseUrl.release}\"")
}
}
}
3. dependencies依赖配置
// config.gradle
ext {
appcompatVersion = "1.0.2"
constraintlayoutVersion = "1.1.3"
dependencies = [
appcompat : "androidx.appcompat:appcompat:${appcompatVersion}",
constraintlayout: "androidx.constraintlayout:constraintlayout:${constraintlayoutVersion}",
]
tests = [
"junit" : "junit:junit:4.12",
"espresso" : "androidx.test.espresso:espresso-core:3.1.1",
"androidJunit": "androidx.test.ext:junit:1.1.0"
]
}
app模块中build.gradle文件引用
// build.gradle
def supports = rootProject.ext.dependencies
def tests = rootProject.ext.tests
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 标准写法
// implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.2'
// implementation supports.appcompat
// implementation supports.constraintlayout
// supports 依赖
supports.each { key, value -> implementation value }
testImplementation tests.junit
androidTestImplementation tests.espresso
androidTestImplementation tests.androidJunit
}
4. 签名配置
签名配置时需要注意:signingConfigs 必须写在buildTypes之前
android {
// 签名配置(隐形坑:必须写在buildTypes之前)
signingConfigs {
debug {
// 天坑:填错了,编译不通过还找不到问题
storeFile file('/Users/xujinbing839/.android/debug.keystore')
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
release {
// 签名证书文件
storeFile file('/Users/xujinbing839/work/mycode/todo/todo_modular/keystore/modular')
storeType "modular" // 签名证书的类型
storePassword "123456" // 签名证书文件的密码
keyAlias "modular" // 签名证书中密钥别名
keyPassword "123456" // 签名证书中该密钥的密码
v2SigningEnabled true // 是否开启V2打包
}
}
buildTypes {
debug {
// 对构建类型设置签名信息
signingConfig signingConfigs.debug
}
release {
// 对构建类型设置签名信息
signingConfig signingConfigs.release
}
}
}
其它配置
android {
defaultConfig {
// 开启分包
multiDexEnabled true
// 设置分包配置
// multiDexKeepFile file('multidex-config.txt')
// 将svg图片生成 指定维度的png图片
// vectorDrawables.generatedDensities('xhdpi','xxhdpi')
// 使用support-v7兼容(5.0版本以上)
vectorDrawables.useSupportLibrary = true
// 只保留指定和默认资源
resConfigs('zh-rCN')
// 配置so库CPU架构(真机:arm,模拟器:x86)
// x86 x86_64 mips mips64
ndk {
//abiFilters('armeabi', 'armeabi-v7a')
// 为了模拟器启动
abiFilters('x86', 'x86_64')
}
}
// AdbOptions 可以对 adb 操作选项添加配置
adbOptions {
// 配置操作超时时间,单位毫秒
timeOutInMs = 5 * 1000_0
// adb install 命令的选项配置
installOptions '-r', '-s'
}
// 对 dx 操作的配置,接受一个 DexOptions 类型的闭包,配置由 DexOptions 提供
dexOptions {
// 配置执行 dx 命令是为其分配的最大堆内存
javaMaxHeapSize "4g"
// 配置是否预执行 dex Libraries 工程,开启后会提高增量构建速度,不过会影响 clean 构建的速度,默认 true
preDexLibraries = false
// 配置是否开启 jumbo 模式,代码方法是超过 65535 需要强制开启才能构建成功
jumboMode true
// 配置 Gradle 运行 dx 命令时使用的线程数量
threadCount 8
// 配置multidex参数
additionalParameters = [
'--multi-dex', // 多dex分包
'--set-max-idx-number=50000', // 每个包内方法数上限
// '--main-dex-list=' + '/multidex-config.txt', // 打包到主classes.dex的文件列表
'--minimal-main-dex'
]
}
// 执行 gradle lint 命令即可运行 lint 检查,默认生成的报告在 outputs/lint-results.html 中
lintOptions {
// 遇到 lint 检查错误会终止构建,一般设置为 false
abortOnError false
// 将警告当作错误来处理(老版本:warningAsErros)
warningsAsErrors false
// 检查新 API
check 'NewApi'
}
}
组件化详细部署
组件化开发的意义
什么是组件化开发?
组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
组件化和插件化开发略有不同:
插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开打包。
为什么要组件化呢?
1. 开发需求
不相互依赖、可以相互交互、任意组合、高度解耦
2. 团队效率
library与application区别、切换
Phone Module 和 Android Library区别:
Phone Module是一个可以独立运行,编译成一个apk,且build.gradle文件中需要配置applicationId;而Android Library 不能独立运行,不能单独编译成一个apk,且build.gradle文件中不需要配置applicationId。
Phone Module 和 Android Library切换:
下面以子模块购物shop为例:
如果购物shop模块能够独立编译apk,就需要切换为Phone Module(也就是组件化模式),通过isRelease动态切换:
- 当isRelease为true:集成化模式(也就是可打包整个项目apk),子模块购物shop不可独立运行
- 当isRelease为false:组件化模式,子模块购物shop可以独立运行
其中isRelease变量是config.gradle中定义的属性
// build.gradle
if (isRelease) { // 如果是发布版本时,各个模块都不能独立运行
apply plugin: 'com.android.library'
} else {
apply plugin: 'com.android.application'
}
android {
defaultConfig {
// 如果是集成化模式,不能有applicationId
if (!isRelease) applicationId appId.shop
}
}
当子模块购物shop从library模块切换为application模块时,可能需要编写测试代码,如:启动的入口。
使用sourceSets配置,将测试的代码放入debug文件夹中,当切换到集成化模式时,打包成apk时移除所有的debug代码(也就是debug代码不会打包到apk中)。
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/**'
}
}
}
}
}
组件化开发规范:
- 子模块购物shop:在src类和res资源命令中添加前缀,shop_,如:shop_activity_home
- app模块:可以不修改,默认
Module与Module之间交互
Module间怎么交互(包括:跳转、传参等)?方式有很多:
- EventBus 非常混乱,难以维护
- 反射 反射技术可以成功,但是维护成本较高,且出现高版本的@hide限制
- 隐式意图 维护成本还好,就是比较麻烦,需要维护Manifest的action
- BroadCastReceiver 需要动态注册(7.0后),需求方发送广播
- 类加载 需要准确的全类名路径,维护成本较高且容易出现人为失误
第一种实现方案:类加载技术交互
通过Class的forName加载目标类,需要准确知道目标类的全类名路径。
private void jump() {
try {
Class<?> targetClass = Class.forName("com.example.modular.shop.ShopActivity");
Intent intent = new Intent(this, targetClass);
intent.putExtra("moduleName","app");
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
第二种实现方案:全局Map记录路径信息
要跳转,如果知道了目标类的Class对象,不就可以跳转了,接下来只需要解决目标类的Class对象查找就可以了。
可以定义一个PathBean用于封装目标类的相关信息,如:目标类的全路径名,目标类的Class对象
public class PathBean {
public String path; // 跳转目标全类名
public Class<?> targetClass; // 跳转目标类的Class对象
public PathBean(String path, Class<?> targetClass) {
this.path = path;
this.targetClass = targetClass;
}
}
一个模块中会有很多PathBean,可以List存取PathBean,而又有很多模块,可以使用Map区分不同的模块。
// key:模块名,如shop模块 value:该模块下所有的Activity路径信息
private static final Map<String, List<PathBean>> mGroupMap = new HashMap<>();
mGroupMap是一个Map,key是模块名,如:shop模块;value是该模块下所有的Activity路径信息.
将所有的路径信息加入到mGroupMap中,使用时通过根据组名(模块名)和路径名获取目标类对象。
/**
* 将路径信息加入到全局的Map中
*/
public static void addGroup(String groupName, String path, Class<?> targetClass) {
List<PathBean> pathBeans = mGroupMap.get(groupName);
if (pathBeans == null) {
pathBeans = new ArrayList<>();
pathBeans.add(new PathBean(path, targetClass));
mGroupMap.put(groupName, pathBeans);
} else {
pathBeans.add(new PathBean(path, targetClass));
}
}
/**
* 根据组名和路径名获取目标类
*/
public static Class<?> findTargetClass(String groupName,String path) {
List<PathBean> pathBeans = mGroupMap.get(groupName);
if (pathBeans != null) {
for (PathBean pathBean : pathBeans) {
if (!TextUtils.isEmpty(path) && path.equalsIgnoreCase(pathBean.path)) {
return pathBean.targetClass;
}
}
}
return null;
}
APT介绍和使用
APT介绍
APT(Annotation Processing Tools)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotaion,使用Annotation进行额外的处理。Annotation处理器在处理Annotation时可以根据源文件中的Annotaion生成额外的源文件和其它的文件(文件具体内容由Annotaion处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
通俗的理解:根据规则,帮助我们生成代码,生成类文件。
1. APT核心实现原理
编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时javac编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如:根据注解生成新的java类,这也就是ARouter、Butterknife、Dragger等开源库的基本原理。
2. java源文件编程层Class文件
工具是通过javac工具,注解处理器是一个在javac中的,用来编译时扫描和处理的注解工具。可以认为是特定的注解,注册你自己的注解处理器。
3. 怎么注册注解处理器
MyProcessor到javac中。你必须提供一个.jar文件。就像其它.jar文件一样,你打包你的注解处理器到此文件中。并且,在你的jar中,你需要打包一个特定的文件javax.annotation.processing.Processor到META-INF/services路径下。
知识点详解
1. jar
- com.google.auto.service:auto-service 谷歌提供的java生成源代码库
- com.squareup:javapoet 提供了各种API让你用各种姿势去生成java代码文件
2. @AutoService
这是一个其它注解处理器中引入的注解。AutoService注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的。我们可以在注解处理器中使用注解。非常方便
3. 结构体语言
对于java源文件来说,也是一种结构体语言。JDK中提供了用来描述结构体语言元素的接口。
在注解处理过程中,我们扫描所有的java源文件。源代码的每一部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。
package com.example; // PackageElement
public class Foo { // TypeElement
private int a; // VariableElement
private Foo other; // VariableElement
public Foo {} // ExecutableElement
public void setA( // ExecutableElement
int newA // VariableElement ) {}
}
Element程序元素
PackageElement 表示包程序元素。
TypeElement 表示一个类或接口程序元素。
ExecutableElement 表示类或接口的方法,构造函数或初始化器(静态或实例),包括注释类型元素。
VariableElement 表示一个字段, 枚举常量,方法或构造函数参数,局部变量,资源变量或异常参数。
TypeParameterElement 表示通用类,接口,方法或构造函数元素的正式类型参数。
Types
一个用来处理TypeMirror的工具类
Filer
使用Filer你可以创建java文件
AbstractProcessor核心API
1. process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
相当于每个处理器的主函数main()。可以在这里写你的扫描、评估和处理注解的代码,以及生成java文件。输入参数RoundEnvironment,可以让你查询出包含特定注解的被注解元素。
2. getSupportedAnnotationTypes()
这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
3. getSupportedSourceVersion()
用来指定你使用的java版本。通常这里返回SourceVersion.latestSupported()。如果你有足够的理由只支持java6的话,你也可以返回SourceVersion.RELEASE_6
4. getSupportedOptions()
用来指定注解处理器处理的选项参数。需要在gradle文件中配置选项参数值
// 在gradle文件中配置选项参数值(用于APT传参接收)
// 切记:必须写在defaultConfig节点下
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
这些API也可以使用注解的方式指定:
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理(新增annotation module)
@SupportedAnnotationTypes({"com.example.modular.annotations.ARouter"})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions("moduleName")
public class ARouterProcessor extends AbstractProcessor {
// ignore
}
ProcessingEnvironment核心API
// ignore
public class ARouterProcessor extends AbstractProcessor {
// 操作Element工具类 (类、函数、属性都是Element)
private Elements elementUtils;
// type(类信息)工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
// 文件生成器 类/资源,Filter用来创建新的源文件,class文件以及辅助文件
private Filer filer;
// 模块名,通过getOptions获取build.gradle传过来
private String moduleName;
// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// processingEnv是父类受保护属性,可以直接拿来使用。
// 其实就是init方法的参数ProcessingEnvironment
// processingEnv.getMessager(); //参考源码64行
elementUtils = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
typeUtils = processingEnvironment.getTypeUtils();
// 通过ProcessingEnvironment去获取build.gradle传过来的参数
Map<String, String> options = processingEnvironment.getOptions();
if (options != null && !options.isEmpty()) {
moduleName = options.get("moduleName");
// 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e那么好使
messager.printMessage(Diagnostic.Kind.NOTE, "moduleName=" + moduleName);
}
}
}
RoundEnvironment 核心API
// ignore
public class ARouterProcessor extends AbstractProcessor {
/**
* 相当于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) {
// ignore
}
// ignore
return true;
}
}
Element核心API
APT使用
开发环境的兼容
https://github.com/google/auto
1. Android Studio 3.2.1 + Gradle 4.10.1 临界版本
dependencies {
// 注册注解,并对其生成META-INF的配置信息,rc2在gradle5.0后有坑
// As-3.2.1 + gradle4.10.1-all + auto-service:1.0-rc2
implementation 'com.google.auto.service:auto-service:1.0-rc2'
}
2. Android Studio 3.4.1 + Gradle 5.1.1 向下兼容
dependencies {
// As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
}
使用APT技术帮助我们生成代码
1. ARouter注解
新建java library工程,工程名为:annotations。然后创建ARouter注解
/**
* <ul>
* <li>@Target(ElementType.TYPE) // 接口、类、枚举、注解</li>
* <li>@Target(ElementType.FIELD) // 属性、枚举的常量</li>
* <li>@Target(ElementType.METHOD) // 方法</li>
* <li>@Target(ElementType.PARAMETER) // 方法参数</li>
* <li>@Target(ElementType.CONSTRUCTOR) // 构造函数</li>
* <li>@Target(ElementType.LOCAL_VARIABLE)// 局部变量</li>
* <li>@Target(ElementType.ANNOTATION_TYPE)// 该注解使用在另一个注解上</li>
* <li>@Target(ElementType.PACKAGE) // 包</li>
* <li>@Retention(RetentionPolicy.RUNTIME) <br>注解会在class字节码文件中存在,jvm加载时可以通过反射获取到该注解的内容</li>
* </ul>
*
* 生命周期:SOURCE < CLASS < RUNTIME
* 1、一般如果需要在运行时去动态获取注解信息,用RUNTIME注解
* 2、要在编译时进行一些预处理操作,如ButterKnife,用CLASS注解。注解会在class文件中存在,但是在运行时会被丢弃
* 3、做一些检查性的操作,如@Override,用SOURCE源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
*/
@Target(ElementType.TYPE) // 该注解作用在类之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作。注解会在class文件中存在
public @interface ARouter {
// 详细路由路径(必填),如:"app/MainActivity"
String path();
// 路由组名(选填,如果不填写,可以从path中截取)
String group() default "";
}
2. 自定义ARouterProcessor注解处理器
新建java library工程,工程名为:compiler。创建ARouterProcessor类继承AbstractProcessor。
为了使用APT技术生成代码,首先要设计我们想生成的代码模版,下面的代码是由APT生成的。
package com.example.modular.shop;
public class ARouter$$ShopActivity {
public static Class<?> findTargetClass(String path) {
if (path.equals("/shop/ShopActivity")) {
return ShopActivity.class;
}
return null;
}
}
在ARouterProcessor类中实现process方法,处理支持的注解,生成我们想要的代码。
/**
*
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param set 支持注释类型的集合,如:@ARouter注解
* @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// set集合,就是支持的注解集合,如:ARouter注解
if (set.isEmpty()) return false;
// 获取所有被@ARouter注解注释的元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) {
for (Element element : elementsAnnotatedWith) {
// 通过类节点获取包节点,(全路径名,如:com.example.modular.shop)
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
// 获取被@ARouter注解的简单类名
String simpleName = element.getSimpleName().toString();
// 注: 包名:com.example.modular.shop 被注解的类名:ShopActivity
messager.printMessage(Diagnostic.Kind.NOTE, "包名:" + packageName + " 被注解的类名:" + simpleName);
// 最终生成的类文件名
String finalClassName = "ARouter$$" + simpleName;
try {
// 创建一个新的源文件并返回一个JavaFileObject对象以允许写入它
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);
// 获取此文件对象的Writer,开启写入功能
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注解
ARouter aRouter = element.getAnnotation(ARouter.class);
writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n");
writer.write("return " + simpleName + ".class;\n");
writer.write("}\n");
writer.write("return null;\n");
writer.write("}\n}");
// 关闭写入流
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
使用时要在build.gradle文件添加依赖,如购物shop模块中
dependencies {
implementation project(path: ':ARouter:annotations')
annotationProcessor project(path: ':ARouter:compiler')
}
然后在ShopActivity类上添加ARouter注解
@ARouter(path = "/shop/ShopActivity")
public class ShopActivity extends AppCompatActivity {}
最后build -> Make Project,就会生成我们想要的代码。
注意:如果出现中文乱码,在build.gradle中添加如下配置:
// java控制台输出中文乱码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
APT + javapoet
javapoet是square公司推出的开源java代码生成框架,提供java api生成.java源文件。这个框架非常实用,也是我们习惯的java面向对象OOP语法。可以很方便的使用它根据注解生成对于的代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式替代繁琐冗杂的重复工作。
项目主页及源码
https://github.com/square/jav...
依赖javapoet库
dependencies {
// 帮助我们通过类调用的方式来生成java代码
implementation 'com.squareup:javapoet:1.11.1'
}
javapoet 8个常用的类
先来看下javapoet官网提供的一个简单的例子,生成HelloWorld类
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
使用javapoet生成上面这段代码:
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
javapoet jar中提供了8个常用的类
javapoet字符串格式化规则
$L 字面量,如:"int value=$L", 10
$S 字符串,如:$S, "hello"
$T 类、接口,如:$T, MainActivity
$N 变量,如:user.$N, name
接下来还是生成ARouter$$ShopActivity
类
package com.example.modular.shop;
public class ARouter$$ShopActivity {
public static Class<?> findTargetClass(String path) {
return path.equals("/shop/ShopActivity") ? ShopActivity.class : null;
}
}
使用javapoet生成ARouter$$ShopActivity
类
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 获取所有被@ARouter注解注释的元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) {
for (Element element : elementsAnnotatedWith) {
// 通过类节点获取包节点,(全路径名,如:com.example.modular.shop)
String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
// 获取被@ARouter注解的简单类名
String simpleName = element.getSimpleName().toString();
// 注: 包名:com.example.modular.shop 被注解的类名:ShopActivity
messager.printMessage(Diagnostic.Kind.NOTE, "包名:" + packageName + " 被注解的类名:" + simpleName);
// 最终生成的类文件名
String finalClassName = "ARouter$$" + simpleName;
ARouter aRouter = element.getAnnotation(ARouter.class);
ClassName targetClassName = ClassName.get((TypeElement) element);
// 构建方法体
MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class) // 返回值Class<?>
.addParameter(String.class, "path")
.addStatement("return path.equals($S) ? $T.class : null", aRouter.path(), targetClassName)
.build();
// 构建类
TypeSpec finalClass = TypeSpec.classBuilder(finalClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(findTargetClass)
.build();
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, finalClass)
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。