APT 技术
- Java注解处理器使用详解
注解处理器运行是不会被打包apk,是在运行前生成代码否则会发生jar冲突,手机系统已经存在 - JavaPoet是一款可以自动生成Java文件的第三方依赖
需求当前
lib
需要得到app
参数时使用APT
技术通过注解在编译器前生成代码来获取app
中的参数
1.自定义注解
- 根据需要得到的参数定义注解(可以是
TYPE
,PARAMETER
,PACKAGE
,LOCAL_VARIABLE
) - compiler导入依赖生成注解处理器
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.google.auto.service:auto-service:1.0-rc7'
implementation 'com.squareup:javapoet:1.13.0'
- MvplibAnotation 生成注解类
//=========================================
/*
可选参数 说明
RetentionPolicy.SOURCE 注解将被编译器丢弃
RetentionPolicy.CLASS 注解在class文件中可用, 但会被VM丢弃
RetentionPolicy.RUNTIME VM将运行期也保留注解信息,因此可用通过反射机制来读取注解的信息
*/
//=========================================
@Retention(RetentionPolicy.SOURCE)
//=========================================
/*
ElementType.CONSTRUCTOR 构造函数的声明
ElementType.FIELD 成名变量的声明(包含enum)
LOCAL_VARIABLE 局部变量的声明
METHOD 方法的声明
PACKAGE 包声明
PARAMETER 参数声明
TYPE 类、接口(包括注解类型) 或enum声明
*/
//=========================================
@Target(ElementType.FIELD)
public @interface BaseUrl {
}
2.创建处理注解类接口
/*
利用工厂模式定义文件生成器
*/
public interface FileGenerator {
// 返回注解的类型
Set<String> getSupportedAnnotationTypes();
// 对使用注解的对象进行操作
boolean process(Elements elements, Messager messager, Filer filer, Set<? extends TypeElement> set, RoundEnvironment roundEnvironment);
}
3.创建注解类的工具类
public class ProcessorUtils {
private Elements mElementUtils;
private Messager mMessager;
private Filer mFiler;
// 创建文件生成器集合
private ArrayList<FileGenerator> mGenerators;
public ProcessorUtils(Elements mElementUtils, Messager mMessager, Filer mFiler) {
// 初始化所有工具类对象
this.mElementUtils = mElementUtils;
this.mMessager = mMessager;
this.mFiler = mFiler;
// 实例化
mGenerators = new ArrayList<>();
// 添加具体创建类型
mGenerators.add(new ConfigGenerator());
}
// 返回所有注释的类型
public Set<String> getSupportedAnnotationTypes() {
// 创建一个有序空集合添加所有注解类型
Set<String> types = new HashSet<>();
for (FileGenerator generator : mGenerators) {
types.addAll(generator.getSupportedAnnotationTypes());
}
return types;
}
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
for (FileGenerator generator : mGenerators) {
generator.process(mElementUtils, mMessager, mFiler, set, roundEnvironment);
}
return false;
}
}
4.创建注解处理器
/*
创建自定义的注解处理器
*/
@AutoService(Processor.class)
public class MvpProcessor extends AbstractProcessor {
ProcessorUtils mProcessorUtils;
/**
* 得到各种处理注解的工具对象
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//=================================================
* 返回用来在元素上进行操作的某些实用工具方法的实现。
* Elements是一个工具类,可以处理相关Element(
* 包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
// mElementsUtils = processingEnvironment.getElementUtils();//元素操作工具类
* 返回用来报告错误、警报和其他通知的 Messager。
// mMessager = processingEnvironment.getMessager();// 日志工具类
* 用来创建新源、类或辅助文件的 Filer。
// mFiler = processingEnvironment.getFiler();
返回用来在类型上进行操作的某些实用工具方法的实现。
Types getTypeUtils();
// 返回任何生成的源和类文件应该符合的源版本。
SourceVersion getSourceVersion();
// 返回当前语言环境;如果没有有效的语言环境,则返回 null。
Locale getLocale();
// 返回传递给注释处理工具的特定于 processor 的选项
Map<String, String> getOptions();
//=================================================
mProcessorUtils = new ProcessorUtils(processingEnvironment.getElementUtils(),processingEnvironment.getMessager(),processingEnvironment.getFiler());
}
/*
return 使用该注解的对象集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return mProcessorUtils.getSupportedAnnotationTypes();
}
/*
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* @param set
* @param roundEnvironment
* @return
* 注解处理器的核心方法,处理具体的注解。主要功能基本可以理解为两个
* 获取同一个类中的所有指定注解修饰的Element;
* set参数,存放的是支持的注解类型
* RoundEnvironment参数,可以通过遍历获取代码中所有通过指定注解(例如在ButterKnife中主要就是@BindeView等)
* 修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
* 创建Java文件;
* 将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,
* 这样做的目的是让在最终依赖注入时便于操作。
*/
//=================================================
// 、FactoryProcessor类中最后一个也是最重要的一个方法了。先看这个方法的返回值
// ,是一个boolean类型,返回值表示注解是否由当前Processor 处理。如果返回 true,则这些注解由此注解来处理
// ,后续其它的 Processor 无需再处理它们;如果返回 false,则这些注解未在此Processor中处理并,
// 那么后续 Processor 可以继续处理它们。
//=================================================
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mProcessorUtils.process(set, roundEnvironment);
return true;
}
}
5.创建Java配置文件()
# 要生存的 MvpConfig 类的包名
mvpConfigPackageName=com.l.y.mvp.config
# 类名
mvpConfigClassName=MvpConfig
# MvpConfig 类里面的 baseUrl 变量的名字
baseUrlFieldName=BASE_URL
6.创建具体处理的注解类
/*
创建具体注解实现类
*/
public class ConfigGenerator implements FileGenerator {
// 得到Java配置文件的地址
private static final String PROPERTIES_FILE_NAME = "./mvplib/mvp.properties";
// 得到Java配置文件的地址的key
private static final String PROPERTIES_KEY_MVP_CONFIG_PK_NAME = "mvpConfigPackageName";
private static final String PROPERTIES_KEY_MVP_CONFIG_C_NAME = "mvpConfigClassName";
private static final String PROPERTIES_KEY_BASE_URL_FIELD_NAME = "baseUrlFieldName";
private String mvpConfigClassName;
private String mvpConfigPackageName;
private String baseUrlFieldName;
// 创建空参构造读取java配置文件
public ConfigGenerator() {
// 得到Java配置文件对象
Properties properties = new Properties();
try {
// 加载配置文件
properties.load(new FileInputStream(new File(PROPERTIES_FILE_NAME)));
// 通过key值得到value
mvpConfigPackageName = properties.getProperty(PROPERTIES_KEY_MVP_CONFIG_PK_NAME);
mvpConfigClassName = properties.getProperty(PROPERTIES_KEY_MVP_CONFIG_C_NAME);
baseUrlFieldName = properties.getProperty(PROPERTIES_KEY_BASE_URL_FIELD_NAME);
} catch (IOException e) {
e.printStackTrace();
}
}
// 返回需要添加的注解类型
@Override
public Set<String> getSupportedAnnotationTypes() {
// 创建有序集合添加注解
Set<String> types = new HashSet<>();
types.add(BaseUrl.class.getCanonicalName());
return types;
}
// 处理注解得到对象
@Override
public boolean process(Elements elements, Messager messager, Filer filer, Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//返回所有所有使用该注解的对象
Set<? extends Element> baseUrlElements = roundEnvironment.getElementsAnnotatedWith(BaseUrl.class);
Element baseUrl = null;
// 判段是否使用了该注解
if (baseUrlElements != null && baseUrlElements.size() > 0) {
// 得到该注解对象(Element baseUrl)
baseUrl = baseUrlElements.iterator().next();
}
// 判断该注解格式是否正确
if (!isValidBaseUrlElement(baseUrl)) {
return false;
}
// 创建类
//=================================================
TypeSpec————用于生成类、接口、枚举对象的类
MethodSpec————用于生成方法对象的类
ParameterSpec————用于生成参数对象的类
AnnotationSpec————用于生成注解对象的类
FieldSpec————用于配置生成成员变量的类
ClassName————通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
ParameterizedTypeName————通过MainClass和IncludeClass生成包含泛型的Class
JavaFile————控制生成的Java文件的输出的类
//=================================================
TypeSpec.Builder config = TypeSpec.classBuilder(mvpConfigClassName)
// 添加创建该类的修饰符
.addModifiers(Modifier.PUBLIC);
// 如果注解使用符合规则
if (isValidBaseUrlElement(baseUrl)) {
// * Elements是一个工具类,可以处理相关Element(
// * 包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
VariableElement variableElement = (VariableElement) baseUrl;
// 得到该注解对象Element的值
String urlValue = variableElement.getConstantValue().toString(); // http:www.xxx.com
// 生成成员变量
FieldSpec baseUrlField = FieldSpec.builder(String.class, baseUrlFieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", urlValue)
.build();
config.addField(baseUrlField);
}
// 创建该Java文件
JavaFile file = JavaFile.builder(mvpConfigPackageName, config.build()).build();
try {
// 将该文件写入配置文件当中
file.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
private boolean isValidBaseUrlElement(Element element) {
// 判断该注解是否为空
if (element == null) {
return false;
}
// 是否是一个变量
if (element.getKind() != ElementKind.FIELD) {
return false;
}
// 是否拥有这些修饰符
ArrayList<Modifier> modifies = new ArrayList<>();
modifies.add(Modifier.PUBLIC);
modifies.add(Modifier.STATIC);
modifies.add(Modifier.FINAL);
if (!element.getModifiers().containsAll(modifies)) {
return false;
}
return true;
}
}
- 最终我们可以在build文件夹中得到通过apt创建的文件如下效果图