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

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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值