android 通过注解在编译器前生成代码

tech2024-09-27  20

api 依赖(可以传递依赖)

应用场景: 当app 想要通过一个lib1间接依赖lib1依赖的lib2时可以通过api进行间接依赖

框架图:

app dependencies { implementation project(path: ':mvplib') annotationProcessor project(path: ':mvplib:libMvpAnotation:compiler') } mvp dependencies { api project(path:':mvplib:MvpAnotationlib') } compiler dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) 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'//在编译器之前生成代码 implementation project(path: ':mvplib:libMvpAnotation') } sourceCompatibility = "8" targetCompatibility = "8" libMvpAnotation dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } sourceCompatibility = "8" targetCompatibility = "8"

##通过注解在编译期间生成代码 需求:当前lib需要得到App 的参数使用ApT 技术通过注解在编译器前生成代码来获取’app’中的参数

###1.自定义注解

根据需要得到的参数定义注解(可以是TYPE,PARAMETER,PACKAGE,METHOD)

/** * 可选参数 说明 RetentionPolicy.SOURCE 注解将被编译器丢弃 RetentionPolicy.CLASS 注解在class文件中可用, 但会被VM丢弃 RetentionPolicy.RUNTIME VM将运行期也保留注解信息,因此可用通过反射机制来读取注解的信息 @Target @Target: 表示该注解可以用于什么地方。 可选参数 说明 ElementType.CONSTRUCTOR 构造函数的声明 ElementType.FIELD 成名变量的声明(包含enum) LOCAL_VARIABLE 局部变量的声明 METHOD 方法的声明 PACKAGE 包声明 PARAMETER 参数声明 TYPE 类、接口(包括注解类型) 或enum声明 */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) public @interface BaseUrl { }

###2.创建处理注解器接口:

/** * 创建利用工厂模式 * 定义文件生成器 */ public interface FileGenerator { //所有使用注解的对象集合 Set<String> getSupportedAnnotationTypes(); //得到注解内容,通过注解拿到获取的内容 /** *使用注解的对象的操作 * @param elements * 通过注解得到的对象 * @param messager * 得到注解的信息 * @param filer * 创建文件的对象 * @param set * 得到使用注解对象的集合 * @param roundEnvironment * * @return */ 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.创建mvp.properties中配置文件

# 要生存的 MvpConfig 类的包名 mvpConfigPackageName = com.wds.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创建的文件

#如何在app 拿到数据在mvp 中使用(也就是上图MvPConfig 的变量的参数)

思路: 1.通过导入依赖startup 后使用的原理是 ContentProvider 来运行所有依赖项的初始化,避免每个第三方库单独使用 ContentProvider 进行初始化 2.创建MvpInitializer 类继承Initializer 3.来build,grald中获取mvp.properties 中的数据,因为到打包apk时系统不会将build打包到apk中, 4.在清单文件中获取build.grald中的数据 5.这样就可以获取app中build的变量 在使用。

1.通过导入依赖

implementation 'androidx.startup:startup-runtime:1.0.0-alpha01'

2.创建MvpInitializer:

public class MvpInitializer implements Initializer<Void> { @NonNull @Override public Void create(@NonNull Context context) { MvpManager.init(context); return null; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return new ArrayList<>(); } }

3…在build,gradle中获取mvp.properties 中的数据

apply plugin: 'com.android.library' def Properties properties = new Properties() properties.load(new FileInputStream(new File(getProjectDir(),"mvp.properties").getAbsolutePath())) android { compileSdkVersion 29 buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 22 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" manifestPlaceholders = [mvpConfigPackageNameValue: "${properties.get("mvpConfigPackageName")}" ,mvpConfigClassNameValue: "${properties.get("mvpConfigClassName")}" ,baseUrlValue: "${properties.get("baseUrlFieldName")}"] } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.core:core-ktx:1.1.0' implementation 'androidx.startup:startup-runtime:1.0.0-alpha01' api project(path: ':mvplib:libMvpAnotation') }

4.在清单文件中获取build.grald中的数据

<application> <provider android:authorities="${applicationId}.androidx-startup" android:name="androidx.startup.InitializationProvider" android:exported="false" tools:node="merge"> <meta-data android:name="com.wds.mvplib.MvpInitializer" android:value="androidx.startup"/> </provider> <meta-data android:name="mvpConfigPackageName" android:value="${mvpConfigPackageNameValue}"/> <meta-data android:name="mvpConfigClassName" android:value="${mvpConfigClassNameValue}"/> <meta-data android:name="baseUrl" android:value="${baseUrlValue}"/> </application>

5.使用

public class Reader { private static volatile Reader reader; private Reader(){} public static Reader getInstance(){ if (reader==null){ synchronized (Reader.class){ if (reader==null){ ApplicationInfo appInfo = null; try { appInfo = MvpManager.getContext().getPackageManager().getApplicationInfo(MvpManager.getContext().getPackageName(), PackageManager.GET_META_DATA); Class config = Class.forName(appInfo.metaData.getString("mvpConfigPackageName") + "." + appInfo.metaData.getString("mvpConfigClassName")); Field urlField = config.getDeclaredField(appInfo.metaData.getString("baseUrl")); String baseUrl = (String) urlField.get(null); } catch (Exception e) { e.printStackTrace(); } } } } return reader; } }
最新回复(0)