欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

组件化架构之组件初始化 Component Initializer(组件初始化器)开源项目 原理分析

程序员文章站 2022-03-14 10:34:13
Component Initializer(组件初始化器)开源项目 介绍Component Initializer(组件初始化器)介绍原理分析问题1:该框架是如何实现Component类的自动注册的刚开始我想到的方案是通过注解来实现。首先用@Component注解标记Component类,然后我们用注解处理器就可以拿到Component类的信息,然后利用注解处理器自动生成ComponentInitializer.java类,将收集到的Component类的初始化代码new Component...

Component Initializer(组件初始化器)开源项目 介绍

Component Initializer(组件初始化器)介绍

原理分析

  • 问题1:该框架是如何实现Component类的自动注册的

刚开始我想到的方案是通过注解来实现。首先用@Component注解标记Component类,然后我们用注解处理器就可以拿到Component类的信息,然后利用注解处理器自动生成ComponentInitializer.java类,将收集到的Component类的初始化代码new Component(),添加到ComponentInitializer类中,这样就可以完成自动注册的功能。

看着方案很完美,实际做的时候发现一个问题,就是对于多module的形式,每个module都是独立编译的,都会独立编译成一个aar文件,这也就是为什么我们如果用annotationProcessor的时候需要在每个用到的module上都添加annotationProcessor。这样就会导致我们通过annotationProcessor只能拿到当前module中的Component类的信息。无法拿到用户在各个组件module中配置的所有的Component类的信息。

所以通过annotationProcessor来实现自动注册是行不通的。

那么有没有其他方案呢?

后来我在想,这个问题做框架的是都需要考虑的,那么其他框架是怎么解决的呢。Arouter是如何解决这个问题的?从Arouter的介绍中我发现它是通过AutoRegister来解决的,然后通过AutoRegister的实现原理,我发现它是通过Transform API的技术方案来解决的。

Transform API是从Gradle 1.5.0版本之后提供的,它允许第三方在打包Dex文件之前的编译过程中修改.class字节码文件,这样在这个过程中,我们就可以搜集到所有的Component类,然后通过修改ComponentInitializer.class字节码文件,就可以把注册的代码添加到ComponentInitializer.class字节码文件里。

除了Transform API的方案,其实还有其他两种技术方案。(以下来自AutoRegister作者的分析)

  • ARouter的解决方案。
    AutoRegister在ARouter里是一个可选方案,ARouter本身还有一个解决方案:每个module都生成自己的java类,这些类的包名都是’com.alibaba.android.arouter.routes’,然后在运行时通过读取每个dex文件中的这个包下的所有类通过反射来完成映射表的注册,详见ClassUtils.java源码

  • ActivityRouter的解决方案(demo中有2个组件名为’app’和’sdk’):

    • 在主app module中有一个@Modules({“app”, “sdk”})注解用来标记当前app内有多少组件,根据这个注解生成一个RouterInit类
    • 在RouterInit类的init方法中生成调用同一个包内的RouterMapping_app.map
    • 每个module生成的类(RouterMapping_app.java 和 RouterMapping_sdk.java)都放在com.github.mzule.activityrouter.router包内(在不同的aar中,但包名相同)
    • 在RouterMapping_sdk类的map()方法中根据扫描到的当前module内所有路由注解,生成了调用Routers.map(…)方法来注册路由的代码
    • 在Routers的所有api接口中最终都会触发RouterInit.init()方法,从而实现所有路由的映射表注册

    这种方式用一个RouterInit类组合了所有module中的路由映射表类,运行时效率比扫描所有dex文件的方式要高,但需要额外在主工程代码中维护一个组件名称列表注解: @Modules({“app”, “sdk”})

我们最终选择的是利用AutoRegister的能力来实现自动注册。为了方便用户使用我自定义了一个Gradle plugin来实现此功能

  • 自定义Gradle plugin

class Plugin implements org.gradle.api.Plugin<Project> {

    @Override
    void apply(Project project) {
        /**
         * 注册transform接口
         */
        def isApp = project.plugins.hasPlugin(AppPlugin)
        if (isApp) {
            println 'project(' + project.name + ') apply com.jst.component.initializer plugin'
            def android = project.extensions.getByType(AppExtension)
            def transformImpl = new RegisterTransform(project)

            AutoRegisterConfig config = new AutoRegisterConfig()
            Map<String, Object> map = new HashMap<>()
            map.put('scanInterface','com.jst.compinit.IComponentInfo')
            map.put('codeInsertToClassName','com.jst.compinit.ComponentInitializer')
            map.put('registerMethodName','register')

            config.registerInfo.add(map)
            config.project = project
            config.convertConfig()
            transformImpl.config = config
            android.registerTransform(transformImpl)
        }
    }
}
  • 问题2:该框架是如何实现配置组件的初始化依赖的

首先该依赖信息通过注解的方式来配置是最方便的。所以我们定义了一个@Component注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Component {

    /**
     * Component的name
     *
     * 一般情况下该name可以不设置
     * 如果该Component被其他Component所依赖,则该name必须设置
     */
    String name() default "";

    /**
     * 依赖的Component的name列表
     */
    String[] dependencies() default {};
}
  • 自定义annotationProcessor

我们需要一个注解处理器来解析用户配置的注解信息

@SupportedAnnotationTypes({"com.jst.compinit.annotation.Component"})
@AutoService(Processor.class)
public class ComponentProcessor extends AbstractProcessor {
    private Elements elementUtils;
    Filer filer;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        for (Element element : roundEnvironment.getElementsAnnotatedWith(Component.class)) {

            //接下来的代码所做的事情:被注解类所在的包名下,以 被注解类的类名$$IComponentInfoImpl 生成java文件


            //取出注解对象
            Component component = element.getAnnotation(Component.class);
            //获取包名
            PackageElement packageElement = elementUtils.getPackageOf(element);
            String packageName = packageElement.getQualifiedName().toString();
            //获取被注解类的类名
            String annotatedClassName = ((TypeElement)element).getSimpleName().toString();
            //生成的类的类名
            String generatedClassName = annotatedClassName + "$$IComponentInfoImpl";

            String literal;
            if (component.dependencies().length == 0) {
                literal = "{}";
            }else {
                literal = "{\"" + String.join("\",\"", component.dependencies()) + "\"}";
            }

            ArrayTypeName stringArray = ArrayTypeName.of(String.class);
            ClassName annotatedClass = ClassName.get((TypeElement)element);
            ClassName iComponentEntityClass = ClassName.get("com.jst.compinit", "IComponentInfo");
            ClassName iComponentClass = ClassName.get("com.jst.compinit", "IComponent");

            //getName方法
            MethodSpec getNameMethod = MethodSpec.methodBuilder("getName")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(String.class)
                    .addStatement("return $S",component.name())
                    .build();

            //getDependencies方法
            MethodSpec getDependenciesMethod = MethodSpec.methodBuilder("getDependencies")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(stringArray)
                    .addStatement("return new $T$L",stringArray,literal)
                    .build();

            //getComponent方法
            MethodSpec getComponentMethod = MethodSpec.methodBuilder("getComponent")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(iComponentClass)
                    .addStatement("return new $T()",annotatedClass)
                    .build();

            //类
            TypeSpec generatedClass = TypeSpec.classBuilder(generatedClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(iComponentEntityClass)
                    .addMethod(getNameMethod)
                    .addMethod(getDependenciesMethod)
                    .addMethod(getComponentMethod)
                    .build();

            try {
                JavaFile.builder(packageName, generatedClass).build().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

该注解处理器主要的作用是:

  1. 解析用户配置的注解信息
  2. 使用解析完的注解信息生成一个IComponentInfo实现类。
    IComponentInfo的作用是存储注解的信息,主要有三部分信息
    1. 用户配置的@Component注解里的name
    2. 用户配置的@Component注解里的dependencies
    3. 用户配置的@Component注解所注解的类

生成java类的能力需要用到javapoet

IComponentInfo类定义:

/**
 * 用户的Component的相关信息
 */
public interface IComponentInfo {
    /**
     * 用户配置的@Component注解里的name
     */
    String getName();
    /**
     * 用户配置的@Component注解里的dependencies
     */
    String[] getDependencies();
    /**
     * 用户配置的@Component注解所注解的类
     */
    IComponent getComponent();
}

注解处理器会在被注解类所在的包名下,以 “被注解类的类名$$IComponentInfoImpl” 作为类名生成java文件
我们来看看注解处理器生成的IComponentInfo实现类的示例:

public class Module1Component$$IComponentInfoImpl implements IComponentInfo {
  public String getName() {
    return "Module1Component";
  }

  public String[] getDependencies() {
    return new String[]{"Module2Component","Module3Component"};
  }

  public IComponent getComponent() {
    return new Module1Component();
  }
}

生成的类在什么位置呢,我们使用AndroidStudio的Android视图模式可以很方便的看到编译过程中自动生成的代码

组件化架构之组件初始化 Component Initializer(组件初始化器)开源项目 原理分析
然后我们自定义的Gradle plugin会将所有的IComponentInfo接口的实现类收集起来,然后注册到ComponentInitializer类中

  • 生成组件的依赖关系图

组件的依赖关系可以抽象成有向无环图,我们要保证被依赖的组件先被初始化,实际上就是要生成有向无环图的拓扑排序。

/**
 * 拓扑排序的工具类
 */
public class TopSortUtil {
    public static List<IComponentInfo> topSort(List<IComponentInfo> componentInfoList){
        //将图初始化出来
        List<Vertex> vertexList = new ArrayList<>();
        for (IComponentInfo componentInfo : componentInfoList) {
            Vertex vertex = new Vertex();
            vertex.componentInfo = componentInfo;
            vertexList.add(vertex);
        }
            //初始化邻接表
        for (Vertex vertex : vertexList) {
            String[] dependencies = vertex.componentInfo.getDependencies();
            for (String dependency : dependencies) {
                for (Vertex vertex1 : vertexList) {
                    if (dependency.equals(vertex1.componentInfo.getName())) {
                        vertex1.adjList.add(vertex);
                        vertex.indegree++;
                    }
                }
            }
        }

        List<IComponentInfo> sortedList = new ArrayList<>();
        int counter = 0;

        /*
        拓扑排序大致步骤:
        1.初始化一个入度为零顶点的队列
        2.出队一个顶点  (该顶点为拓扑排序的下一个顶点,可以将该顶点保存在list里面)
        3.删除该顶点及该顶点的边
        (这里说的删除顶点及边是直观上理解,对应到的代码逻辑就是,
        删除顶点:实际上在上一步出队时已经做过删除了;
        删除边:将该顶点的邻接表里的顶点入度-1)
        4.将入度-1后入度等于0的顶点添加到队列中
        5.重复 2-4 步骤

        循环退出条件:队列为空

        有环判断条件:队列里出队的顶点的计数 小于 顶点的总数
        */


        //初始化一个入度为零顶点的队列
        Queue<Vertex> queue = new LinkedList<>();
        for (Vertex vertex : vertexList) {
            if (vertex.indegree == 0) {
                queue.add(vertex);
            }
        }

        while (!queue.isEmpty()) {
            Vertex vertex = queue.remove();
            counter++;
            sortedList.add(vertex.componentInfo);
            for (Vertex vertex1 : vertex.adjList) {
                if (--vertex1.indegree == 0) {
                    queue.add(vertex1);
                }
            }
        }

        //判断是否有环
        if (counter < vertexList.size()){
            //构成环的List
            List<IComponentInfo> hasLoopList = new ArrayList<>();

            for (IComponentInfo componentInfo : componentInfoList) {
                if (!sortedList.contains(componentInfo)) {
                    hasLoopList.add(componentInfo);
                }
            }

            StringBuilder sb = new StringBuilder();
            sb.append("有一个dependency循环依赖存在于下列component中:");
            for (IComponentInfo componentInfo : hasLoopList) {
                sb.append(componentInfo.getComponent().getClass().getName())
                        .append(",");
            }
            sb.deleteCharAt(sb.lastIndexOf(","));
            throw new IllegalArgumentException(sb.toString());
        }

        return sortedList;
    }
}

该算法是有向无环图拓扑排序的标准算法。
其中还增加了对循环依赖的检测和通过抛异常来提示给用户。循环依赖相当于该有向图中存在了环。

最后

到此,基本上该框架的原理都讲完了,欢迎大家评论,提问题。

Github地址

https://github.com/ShuangtaoJia/ComponentInitializer

欢迎大家 Star , Fork

参考文章:

  1. AutoRegister
  2. Arouter

本文地址:https://blog.csdn.net/taotao110120119/article/details/110231007