利用APT实现一个组件初始化库

Posted zhuliyuan丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用APT实现一个组件初始化库相关的知识,希望对你有一定的参考价值。

背景(为什么做)

在做组件化的过程中发现一个问题,之前我们都是把第三方库和一些初始化的逻辑写在Application,ok这样在单Module的项目是没有问题的,但是当拆成多Module项目的时候这个初始化逻辑在壳App的Application中得维护一份,然后在每个Module自己独立运行的时候也得维护一份,那么假如哪天需要修改初始化逻辑,这将是一个灾难。

为了解决这个问题,我们能不能把初始化逻辑划分给每个业务module自己维护自己所需的部分,在module独立运行或者组成app运行的时候自动触发初始化功能,答案是可以的。

怎么做

1. 方案选定

既然是把初始化逻辑划分到每个Module自己维护,然后在Application#onCreate()的时候自动触发,那我们要解决两个问题

  1. 如何找到每个Module需要执行的初始化逻辑
  2. 如何把找到的初始化逻辑在Application#onCreate()中触发

对于第一个问题很容易就能想到注解,用注解标注需要执行初始化逻辑的方法,便于找到

第二个问题的话则可以通过Transform + ASM或者APT将需要执行的逻辑在Application#onCreate()触发,这里我选择的是APT原因是ASM不太好上手而且是对字节码修改不好调试。

2. 大体思路

确定方案后,再来说说大体思路,每个业务Module创建一个类用来做初始化逻辑,方法名随意只需要标注上我们定义的注解,然后在编译期利用APT找到这些需要初始化的方法,利用JavaPoes生成的类去记录需要初始化的方法和所在类,再在运行时Application#onCreate()找到这些生成的类利用反射执行初始化逻辑。

3. 定义注解

接下来第一步是定义注解用来标注需要执行初始化操作的方法,考虑到不同模块的初始化逻辑可能有先后顺序和线程的要求,这里我在注解中加了两个参数

  • priority:优先级配置,越大优先级越高,默认为1
  • thread:运行线程配置,有MAIN, BACKGROUND可选,默认MAIN
/**
 * 标记需要初始化的方法
 * Created by rocketzly on 2019/7/17.
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Init 

    /**
     * 初始化优先级,越大优先级越大
     *
     * @return
     */
    int priority() default 1;

    /**
     * 初始化线程,默认主线程
     *
     * @return
     */
    ThreadMode thread() default ThreadMode.MAIN;

4. 自定义AbstractProcessor

注解定义好了后,接下来要自定义用来处理该注解的AnnotationProcess

/**
 * 组件初始化注解处理器
 * Created by rocketzly on 2019/7/17.
 */
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions(InitConstant.INITIALIZER_MODULE_NAME)
@SupportedAnnotationTypes(InitConstant.SUPPORT_ANNOTATION_QUALIFIED_NAME)
public class ComponentInitializerProcessor extends AbstractProcessor 

    private String moduleName;
    private Filer filer;
    private Messager messager;
    private Types typeUtils;
    private Elements elementUtils;
    private final String APPLICATION_QUALIFIED_NAME = "android.app.Application";
    private List<InitMethodInfo> syncList = new ArrayList<>(20);
    private List<InitMethodInfo> asyncList = new ArrayList<>(20);

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) 
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        typeUtils = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        if (processingEnv.getOptions() == null) 
            return;
        
        moduleName = processingEnv.getOptions().get(InitConstant.INITIALIZER_MODULE_NAME);
        if (moduleName == null || moduleName.isEmpty()) 
            messager.printMessage(Diagnostic.Kind.ERROR, InitConstant.NO_MODULE_NAME_TIPS);
        
    

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 
        if (annotations == null || annotations.size() == 0) 
            return false;
        
        for (Element element : roundEnv.getElementsAnnotatedWith(Init.class)) 
            if (element == null) 
                continue;
            
            check(element);
            analyze(element);
        
        generate();
        return true;
    

    /**
     * 检查element合法性
     * 1. 检查element是否为ExecutableElement
     * 2. 检查element是否为成员方法
     * 3. 检查方法参数类型要么空参,要么只有一个参数Application
     * 4. 检查方法所在类是否有空参构造方法
     */
    private void check(Element methodElement) 
        //1.检查element是否为ExecutableElement
        if (ElementKind.METHOD != methodElement.getKind()) 
            messager.printMessage(Diagnostic.Kind.ERROR, methodElement.getSimpleName() + "使用错误,@init只能用在方法上", methodElement);
        
        //2.检查element是否为成员方法
        if (ElementKind.CLASS != methodElement.getEnclosingElement().getKind()) 
            messager.printMessage(Diagnostic.Kind.ERROR, methodElement.getSimpleName() + "方法无法使用,@init只能用在成员方法上", methodElement);
        
        //3.检查方法参数类型要么空参,要么只有一个参数Application
        List<? extends VariableElement> parameters = ((ExecutableElement) methodElement).getParameters();
        if (parameters.size() > 1) 
            messager.printMessage(Diagnostic.Kind.ERROR, methodElement.getSimpleName() + "方法参数个数有误,@init最多只支持一个参数的方法", methodElement);
        
        if (parameters.size() != 0) 
            TypeElement typeElement = elementUtils.getTypeElement(APPLICATION_QUALIFIED_NAME);
            if (!typeUtils.isSameType(parameters.get(0).asType(), typeElement.asType())) 
                messager.printMessage(Diagnostic.Kind.ERROR, methodElement.getSimpleName() + "方法参数类型有误,@init标注的方法只支持一个参数并且类型必须为Application", methodElement);
            
        
        //4.检查方法所在类是否有空参构造方法
        List<? extends Element> allMembers = elementUtils.getAllMembers((TypeElement) methodElement.getEnclosingElement());
        boolean hasEmptyConstructor = false;
        for (Element e : allMembers) 
            if (ElementKind.CONSTRUCTOR == e.getKind() && ((ExecutableElement) e).getParameters().size() == 0) 
                hasEmptyConstructor = true;
                break;
            
        
        if (!hasEmptyConstructor)
            messager.printMessage(Diagnostic.Kind.ERROR, methodElement.getEnclosingElement().getSimpleName() + "没有空参构造方法,@init标注方法所在类必须有空参构造方法", methodElement.getEnclosingElement());
    

    /**
     * 分析被标注的方法,获取相关信息
     * 1. 方法所在类的完全路径名
     * 2. 方法的名字
     * 3. 方法是否有参数
     * 4. 方法上注解中的调用线程和优先级信息
     * 封装为 @link InitMethodInfo
     * 再根据线程区分是存储在同步或者异步list中
     *
     * @param element
     */
    private void analyze(Element element) 
        //获取该方法所在类的完全路径名
        String className = ((TypeElement) element.getEnclosingElement()).getQualifiedName().toString();
        //返回该方法的名字
        String methodName = element.getSimpleName().toString();
        //确定方法是否有参数
        boolean isParams = ((ExecutableElement) element).getParameters().size() > 0;
        //获取到方法上的注解
        Init annotation = element.getAnnotation(Init.class);
        //拿到调用线程
        ThreadMode thread = annotation.thread();
        //拿到调用优先级
        int priority = annotation.priority();
        if (ThreadMode.MAIN.equals(thread)) 
            syncList.add(new InitMethodInfo(className, methodName, isParams, priority, thread));
         else 
            asyncList.add(new InitMethodInfo(className, methodName, isParams, priority, thread));
        
    

    /**
     * 生成代码
     * <p>
     * 例如:
     * public final class ComponentInitializerHelper_moduleA implements IInitMethodContainer 
     * private List syncList = new ArrayList<InitMethodInfo>();
     * <p>
     * private List asyncList = new ArrayList<InitMethodInfo>();
     * <p>
     * public ComponentInitializerHelper_moduleA() 
     * syncList.add(new InitMethodInfo("com.rocketzly.modulea.ModuleAInit","sync10",true,10,ThreadMode.MAIN));
     * asyncList.add(new InitMethodInfo("com.rocketzly.modulea.ModuleAInit","async30",true,30,ThreadMode.BACKGROUND));
     * 
     *
     * @Override public List<InitMethodInfo> getSyncInitMethodList() 
     * return syncList;
     * 
     * @Override public List<InitMethodInfo> getAsyncInitMethodList() 
     * return asyncList;
     * 
     * 
     */
    private void generate() 
        //生成字段
        FieldSpec fieldSyncList = generateField(InitConstant.GENERATE_FIELD_SYNC_LIST);
        FieldSpec fieldAsyncList = generateField(InitConstant.GENERATE_FIELD_ASYNC_LIST);

        //初始化构造方法
        MethodSpec.Builder constructorBuild = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC);
        initConstructor(constructorBuild, InitConstant.GENERATE_FIELD_SYNC_LIST);
        initConstructor(constructorBuild, InitConstant.GENERATE_FIELD_ASYNC_LIST);
        MethodSpec constructorMethod = constructorBuild.build();

        //生成方法
        MethodSpec syncMethod = generatorMethod(InitConstant.GENERATE_METHOD_GET_SYNC_NAME);
        MethodSpec asyncMethod = generatorMethod(InitConstant.GENERATE_METHOD_GET_ASYNC_NAME);

        TypeSpec typeSpec = TypeSpec.classBuilder(InitConstant.GENERATE_CLASS_NAME_PREFIX + moduleName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addField(fieldSyncList)
                .addField(fieldAsyncList)
                .addMethod(syncMethod)
                .addMethod(asyncMethod)
                .addMethod(constructorMethod)
                .addSuperinterface(ClassName.get(IInitMethodContainer.class))
                .build();

        JavaFile javaFile = JavaFile.builder(InitConstant.GENERATE_PACKAGE_NAME, typeSpec)
                .build();
        try 
            javaFile.writeTo(filer);
         catch (IOException e) 
            e.printStackTrace();
        
    

    /**
     * 生成字段
     * <p>
     * 例如:
     * private List syncList = new ArrayList<InitMethodInfo>();
     */
    private FieldSpec generateField(String fieldName) 
        return FieldSpec.builder(List.class, fieldName)
                .addModifiers(Modifier.PRIVATE)
                .initializer("new $T<$T>()", ArrayList.class, InitMethodInfo.class)
                .build();
    

    /**
     * 初始化构造函数
     * <p>
     * 例如:
     * public ComponentInitializerHelper_moduleA() 
     * syncList.add(new InitMethodInfo("com.rocketzly.modulea.ModuleAInit","sync10",true,10,ThreadMode.MAIN));
     * asyncList.add(new InitMethodInfo("com.rocketzly.modulea.ModuleAInit","async30",true,30,ThreadMode.BACKGROUND));
     * 
     */
    private void initConstructor(MethodSpec.Builder builder, String initFieldName) 
        for (InitMethodInfo methodInfo : initFieldName.equals(InitConstant.GENERATE_FIELD_SYNC_LIST) ? syncList : asyncList) 
            builder.addStatement("$N.add(new $T($S,$S,$L,$L,$T.$L))",
                    initFieldName,
                    InitMethodInfo.class,
                    methodInfo.className,
                    methodInfo.methodName,
                    methodInfo.isParams,
                    methodInfo.priority,
                    ThreadMode.class,
                    methodInfo.thread
            );
        
    

    /**
     * 生成方法
     * <p>
     * 例如:
     * @Override public List<InitMethodInfo> getSyncInitMethodList() 
     * return syncList;
     * 
     */
    private MethodSpec generatorMethod(String methodName) 
        return MethodSpec.methodBuilder(methodName)
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(ParameterizedTypeName.get(ClassName.get(List.class), ClassName.get(InitMethodInfo.class)))
                .addStatement("return $N", methodName.equals(InitConstant.GENERATE_METHOD_GET_SYNC_NAME) ? InitConstant.GENERATE_FIELD_SYNC_LIST : InitConstant.GENERATE_FIELD_ASYNC_LIST)
                .build();
    



我先检查了注解的用法,如果不符合约定则报错,然后扫描到每个Module中用@init标注的方法,根据@init中线程属性的不同存放在不同的list中,然后利用javapoet生成一个类去存储该Module需要执行初始化的类和方法,这里可以大概看一下生成的代码

public final class InitializerContainer_moduleA implements IInitMethodContainer 
    private List syncList = new ArrayList<InitMethodInfo>();

    private List asyncList = new ArrayList<InitMethodInfo>();

    public InitializerContainer_moduleA() 
        syncList.add(new InitMethodInfo("com.rocketzly.modulea.ModuleAInit", "sync10", true, 10, ThreadMode.MAIN));
        asyncList.add(new InitMethodInfo("com.rocketzly.modulea.ModuleAInit", "async30", true, 30, ThreadMode.BACKGROUND));
    

    @Override
    public List<InitMethodInfo> getSyncInitMethodList() 
        return syncList;
    

    @Override
    public List<InitMethodInfo> getAsyncInitMethodList() 
        return asyncList;
    

可以发现生成的类是实现了一个IInitMethodContainer接口,重写了它的方法对外提供了获取初始化方法的能力,之所以要实现该接口也是为了后面利用反射找到该类能转换为接口进行调用。

5. 调用

既然前面把需要初始化的方法都已经找到并存储在生成的类中了,那么接下来就是找到生成类获取到需要进行初始化的方法,然后根据优先级排序再在Application#onCreate()调用,对于找到生成类、排序、调用初始化方法逻辑我全封装在ComponentInitializer类中了

public class ComponentInitializer 

    private boolean isDebug;
    private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>(16);

    public ComponentInitializer(boolean isDebug) 
        this.isDebug = isDebug;
        Logger.setDebug(isDebug);
    

    public void start(Application application) 
        if (application == null) 
            Logger.e("Application不能为null");
            return;
        
        Set<String> fileNameByPackageName = null;
        try 
            fileNameByPackageName = ClassUtils.getFileNameByPackageName(application, InitConstant.GENERATE_PACKAGE_NAME);
            Logger.i("通过包名找到的类:" + fileNameByPackageName.toString());
         catch (PackageManager.NameNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
         catch (InterruptedException e) 
            e.printStackTrace();
        
        if (fileNameByPackageName == null) 
            Logger.e("未找到初始化方法容器类");
            return;
        

        List<InitMethodInfo> syncMethodList = new ArrayList<>();
        List<InitMethodInfo> asyncMethodList = new ArrayList<>();
        for (String className : fileNameByPackageName) 
            try 
                IInitMethodContainer initMethodContainer = (IInitMethodContainer) Class.forName(className).newInstance();
                syncMethodList.addAll(initMethodContainer.getSyncInitMethodList());
                asyncMethodList.addAll(initMethodContainer.getAsyncInitMethodList());
             catch (ClassNotFoundException e) 
                e.printStackTrace();
             catch (IllegalAccessException e) 
                e.printStackTrace();
             catch (InstantiationException e) 
                e.printStackTrace();
            
        
        Collections.sort(syncMethodList);
        Collections.sort(asyncMethodList);
        Logger.i("同步初始化方法:" + syncMethodList.toString(告别KAPT,使用KSP为Android编译提速

任何 Python 库都会生成发布样式回归表

告别KAPT,使用KSP为Android编译提速

微搭低代码小程序中利用弹窗组件实现城市切换选择

微搭低代码小程序中利用弹窗组件实现城市切换选择

React进阶(十一)实战演练之Button组件(1)