美文网首页
APT在编译期间生成代码

APT在编译期间生成代码

作者: 夜沐下的星雨 | 来源:发表于2020-09-03 20:41 被阅读0次

    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;
        }
    
    }
    
    

    效果图:

    image

    最终我们可以在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;
        }
    }
    

    相关文章

      网友评论

          本文标题:APT在编译期间生成代码

          本文链接:https://www.haomeiwen.com/subject/okarsktx.html