1、APT KAPT auto_service javapoet
2、Transform scop jar directory ASM javassists
Annotation Processing Tool 注解处理工具
@Retention(RetentionPolicy.CLASS) // 注解的生命周期
@Target(ElementType.TYPE)
public @interface ModuleProvider {
Class<?> interfaceClass();
int type() default 0; // 默认值
}
继承AbstractProcessor
public class ModuleInitProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotationTypes = new LinkedHashSet<>();
// 1、返回需要处理的注解
annotationTypes.add(ModuleProvider.class.getCanonicalName());
return annotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
// 2、固定写法
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler(); // 3、后续用于生成class文件
mMessager = processingEnv.getMessager(); // 4、日志辅助类
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
if (env.processingOver()) { // 5、判断是否是最后一轮处理
generateInit();
} else {
processAnnotations(env);
}
return true;
}
private void processAnnotations(RoundEnvironment env) {
// 6、通过注解类型获取元素集合
Set<? extends Element> set = env.getElementsAnnotatedWith(ModuleProvider.class);
。。。
}
}
Way 1、三方库:AutoService
1)、build.gradle
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
2)、MyProcessor
@AutoService(Processor.class) // 添加AutoService注解
public class ModuleInitProcessor extends AbstractProcessor {
}
resources/MEAT-INF/
gradle/
incremental.annotation.processors // Gradle (>= 4.7) 增量编译支持
services/
javax.annotation.processing.Processor
增量处理器限制:
- 只能使用Filer API生成新的文件
- 不能依赖特定编译器的API
- 如果使用了Filer.creataResource(), Location参数只能是 CLASS_OUTPUT、SOURCE_OUTPUT、NATIVE_HEADER_OUTPUT Gradle 支持两种注解处理器的增量编译:isolating和aggregating,
isolating:
相对aggregating、dynamic这是最快的一种增量注解处理器,也是最容易理解的一个.
限制:
- 要去每一个注解处理器只能使用一个相关的编译时注解去生成新的文件。简单来说就是不能使用两个以上的注解去生成一个新的文件。
- 对于每个用filer生成的文件,必须要在构造时传入一个originating element与其对应.
// 注意看第二个参数传入了一个element,并且只能传入一个
JavaFileObject file = filer.createSourceFile("com.xxx.classname", element);
重新编译源文件时,Gradle 将重新编译由源文件生成的所有文件。 删除源文件后,从其生成的文件也会被删除。 ??
- aggregating: isolating不能使用多个注解生成一个文件,而aggregating是允许的,但是增量成功率和效率比不上isolating。
限制:
- 注解的Rentention必须是:CLASS OR RUNTIME
- 如果用户传递-parameters编译器参数,则它们只能读取参数名称。(不理解,估计也不常用) Gradle 将始终重新处理(但不会重新编译)APT 已处理的所有带注解的源文件。Gradle 将始终重新编译 APT 生成的任何文件。 ??
- dynamic:
需要用户自己决定是否开启增量注解,那么
dynamic
就很适合.
kapt {
useBuildCache = false
arguments {
//将这个选项传递到注解处理器中
arg("enableIncremental", "true")
}
}
4.1、Javapoet
辅助生成java文件的工具类
APT:
APT(Annotation Processing Tool),即注解处理工具,在代码编译期对源代码进行扫描,找出代码中的注解,根据开发者定义的解析规则生成新的Java文件,新生成的Java文件最终也会生成class文件。APT处理流程如下:
annotationProcessor:
annotationProcessor在Android Gradle 2.2之后由Google引入的,是Gradle中内置的APT工具,同时支持 javac 和 jack 编译方式。使用方式(即Java语言下的使用方式)如下:
dependencies {
annotationProcessor "com.xxx"
}
kapt:
Kotlin中不使用annotationProcessor,而是使用kapt,其使用方式为:
dependencies {
kapt "com.xxx"
}
此外在Java和Kotlin两种语言下APT的配置命令也不一样:
Java:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = ["ARGU_NAME": "xxx"]
}
}
}
}
Kotlin:
kapt {
arguments {
arg("ARGU_NAME": "xxx")
}
}
public class ModuleInitPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getExtensions().findByType(BaseExtension.class)
.registerTransform(new ModuleInitTransform());
}
}
public class ModuleInitTransform extends Transform {
@Override
public String getName() {
return "ModuleInitPlugin"; // 可以在编译Task中看到
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT;
}
@Override
public boolean isIncremental() { // 是否支持增量编译
return false;
}
@Override
public void transform(TransformInvocation invocation) {
for (TransformInput input : invocation.getInputs()) {
// 遍历jar
input.getJarInputs().parallelStream().forEach(jarInput -> {
}
// 遍历文件
input.getDirectoryInputs().parallelStream().forEach(directoryInput -> {
}
}
}
}
指明需要操作的内容。
ContentType:
- DefaultContentType.CLASSES java代码
- DefaultContentType.RESOURCES java资源
- ExtendedContentType.DEX
- ExtendedContentType.NATIVE_LIBS
- ExtendedContentType.CLASS_ENHANCED
- ExtendedContentType.DATA_BINDING
- ExtendedContentType.DEX_ARCHIVE
指明需要搜索的目标范围
Scope:
- PROJECT 当前项目内容
- SUB_PROJECTS 子项目内容
- EXTERNAL_LIBRARIES 外部依赖库
- TESTED_CODE 测试代码
- PROVIDED_ONLY provider 方式的本地或者远程依赖
- PROJECT_LOCAL_DEPS 项目本地依赖(local jars)
- SUB_PROJECTS_LOCAL_DEPS 子项目的本地依赖(local jars)
resources
META-INFO.gradle-plugins
pluginname.properties
implementation-class=com.kwai.sdk.init.plugin.ModuleInitPlugin
添加classpath
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath 'com.kwai.sdk.init.plugin:moduleinitplugin:1.0.0'
}
使用自定义plugin
apply plugin: 'com.android.application'
apply plugin: 'moduleinitplugin'
5.1 asm
5.2 javassist
private void generateJarCode(CtClass ctClass) throws CannotCompileException, IOException {
JarFile jarFile = new JarFile(mManagerSrc);
File tempDir = mInvocation.getContext().getTemporaryDir();
// 1、创建一个新jar文件
File outputJar = new File(tempDir, mManagerSrc.getName());
// 2、根据新jar文件,创建一个JarOutputStream
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(outputJar));
Enumeration<JarEntry> enumeration = jarFile.entries();
JarEntry jarEntry;
// 3、遍历之前的jar
while (enumeration.hasMoreElements()) {
// 4、写入新的jar中
jarEntry = enumeration.nextElement();
jarOutputStream.putNextEntry(new ZipEntry(jarEntry.getName()));
if (checkPackage(jarEntry.getName()) && isManagerClass(jarEntry.getName())) {
jarOutputStream.write(ctClass.toBytecode());
} else {
InputStream inputStream = jarFile.getInputStream(jarEntry);
jarOutputStream.write(IOUtils.toByteArray(inputStream));
inputStream.close();
}
jarOutputStream.closeEntry();
}
jarOutputStream.close();
jarFile.close();
// 5、替换jar
saveModifiedFile(outputJar, mManagerSrc);
}
private void saveModifiedFile(File newFile, File oldFile) throws IOException {
if (oldFile.exists()) {
oldFile.delete();
}
FileUtils.copyFile(newFile, oldFile);
}