Android组件化之原理分析

1,729 阅读4分钟

得到Android组件化出来已经两年多了,公司项目也集成了该方案,这里我就自己的理解分享一下大概的原理:

组件化的核心问题

组件化最基本的其实还是创建多个module,各自业务在各自的module中写,互不干扰,且互不依赖,代码隔离,完全解耦。

  1. 组件化的核心就是,每个module既可以独立运行,又可以被当成library进行依赖。那么这里就涉及到第一个核心问题:如何在独立运行和library之间进行转换。
  2. 如果一个应用独立运行必须有个入口Activity,然而在被依赖时这个入口又不需要,这里就涉及到第二个问题:需要有一些代码只有在单独运行的时候被加载,而被当做依赖时不能加载
  3. 在开发期间module之间完全隔离,无法相互引用,这里就涉及到第三个问题:通信。这里又包含两部分通信问题,一是UI跳转,二是逻辑上的通信,比如一个module需要调用另一个module中的东西等。

基于以上三点问题,就分析一下以下三点核心流程

核心流程

  • 动态转换module的类型插件com.android.application/com.android.library
  • 加载runalone中的代码和xml
  • 注册通信接口Service(IApplicationLike)及路由跳转

关键参数

  • isRunAlone:是否需要独立运行,如果为true,需要配置runalone文件夹
  • debugComponent:debug模式下运行需要依赖的其他组件
  • compileComponent:非debug模式下运行需要依赖的其他组件
  • applicationName:组件Application的全路径,比如:com.allin.medrecor ddossier.runalone.application.MedApplication
  • isRegisterCompoAuto:是否自动注册通信接口,如果为false,需要手动注册可以通过反射进行注册

一、动态转换module的类型插件

  1. 通过当前运行的taskName转化成组装任务
@Override
void apply(Project project) {
    ...
    AssembleTask assembleTask = getTaskInfo(project.gradle.startParameter.taskNames)
    ...
}

/**
     * 转化当前执行的module
     * @param taskNames
     * @return
     */
    private AssembleTask getTaskInfo(List<String> taskNames) {
        AssembleTask assembleTask = new AssembleTask()
        for (String task : taskNames) {
            if (task.toUpperCase().contains("ASSEMBLE")
                    || task.contains("aR")
                    || task.toUpperCase().contains("INSTALL")
                    || task.toUpperCase().contains("TINKERPATCH")
                    || task.toUpperCase().contains("RESGUARD")) {
                if (task.toUpperCase().contains("DEBUG")) {
                    assembleTask.isDebug = true
                }
                assembleTask.isAssemble = true
                String[] strs = task.split(":")
                assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "all")
                break
            }
        }
        return assembleTask
    }
  1. 获取控制是否可以独立运行的参数isRunAlone的值,并动态修改,默认每个module中的isRunAlone的值都为true,在打包模式下,当运行的是当前组件的时候,isRunAlone为true不变,其他module修改isRunAlone的值为false
@Override
void apply(Project project) {
    ...
    //需要独立运行的组件需要设置isRunAlone参数
        if (!project.hasProperty(IS_RUN_ALONE)) {
            throw new RuntimeException("你要在【" + module + "】的gradle.properties文件中配置isRunAlone参数")
        }

        boolean isRunAlone = Boolean.parseBoolean(project.properties.get(IS_RUN_ALONE))

        Log.i("isRunAlone = " + isRunAlone)
        //获取主项目名称, 下面两种方法都可以
        String mainModuleName = project.rootProject.property(MAIN_MODULE_NAME)
//        String mainModuleName = project.rootProject.properties.get("mainmodulename")

        //对于要编译的组件和主项目,isRunAlone修改为true,其他修改为false
        if (isRunAlone && assembleTask.isAssemble) {
            isRunAlone = module == compilerModuleName || module == mainModuleName
        }
        //将修改后的isRunAlone值重新赋值给当前组件
        project.setProperty(IS_RUN_ALONE, isRunAlone)
    ...
}
  1. 需要独立运行,则加载独立运行的必要但是非独立运行不需要的代码,也就是runalone文件夹下的代码,并动态设置module的插件类型com.android.application/com.android.library,及加载依赖包
@Override
void apply(Project project) {
    ...
    //根据配置添加各种组件依赖,并自动化生成组件加载代码
        if (isRunAlone) {
            //将当前运行的组件添加application插件
            project.apply plugin: 'com.android.application'
            //配置独立运行代码路径
            if (module != mainModuleName) {
                project.android.sourceSets {
                    main {
                        manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
                        java.srcDirs = ['src/main/java', 'src/main/runalone/java']
                        res.srcDirs = ['src/main/res', 'src/main/runalone/res']
                    }
                }
            }
            Log.i("apply plugin is com.android.application")

            if (assembleTask.isAssemble && module == compilerModuleName) {
                //开始打包组件,添加其他组件为依赖包
                compileComponent(assembleTask, project)
                //开始进行字节码插入,自动注册通信接口服务
//                project.android.registerTransform(new ComponentCodeTransform(project))
            }
        } else {
            //如果不是当前运行的组件,自动添加library插件
            project.apply plugin: 'com.android.library'
            Log.i("apply plugin is com.android.library")
        }
    ...
}

/**
     * 自动添加依赖,只有在运行assemble(装配)任务的时候才会添加依赖,所以在开发期间,各组件完全隔离,这里是解耦的关键
     * 支持两种语法:module或者groupId:artifactId:version(@aar),前者之间引用module工程,后者使用maven中已经发布的aar
     * 本地依赖 ==> debugComponent/compileComponent:组件1,组件2,...
     * 远程依赖 ==> com.luojilab.reader:readercomponent:1.0.0,androidx.appcompat:appcompat:1.1.0,...
     * @param assembleTask
     * @param project
     */
    private void compileComponent(AssembleTask assembleTask, Project project) {
        //需要依赖的组件
        String components
        if (assembleTask.isDebug) {
            components = project.properties.get(DEBUG_COMPONENT)
        } else {
            components = project.properties.get(COMPILE_COMPONENT)
        }
        if (components == null || components.length() == 0) {
            Log.i("当前组件没有依赖任何组件")
            return
        }
        //进行双重校验
        String[] compileComponents = components.split(",")
        if (compileComponents == null || compileComponents.length == 0) {
            Log.i("当前组件没有依赖任何组件")
            return
        }
        //开始添加依赖
        for(String str: compileComponents) {
            if (str.contains(":")) {
                project.dependencies.add("implementation", str)
                Log.i("添加远程依赖:" + str)
            } else {
                project.dependencies.add("implementation", project.project(':' + str))
                Log.i("添加本地依赖:" + str)
            }
        }
    }

至此,就完成了module插件的动态化配置,及动态依赖其他组件的配置

二、通信

UI跳转

这里本人自己的项目用的是阿里路由,ARouter

逻辑通信