Android组件化Gradle插件Calces源码解析

2024-04-09 06:38

本文主要是介绍Android组件化Gradle插件Calces源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

随着很多公司的业务越来越多样化和复杂化,组件化开发也越来越流行,为我们调试代码和多人协助开发带来了巨大的好处,我们肯定会遇到下面几个痛点:

  • 组件是否单独运行
 if (isDebug) {apply plugin: 'com.android.application'} else {apply plugin: 'com.android.library'}

通过gradle.properties配置isDebug变量来处理组件作为app还是lib的存在。

  • 组件与组件之间的Manifest合并问题
sourceSets {main {if (rootProject.ext.isBuildApp) {manifest.srcFile 'src/main/debug/AndroidManifest.xml'} else {//移除debug资源manifest.srcFile 'src/main/release/AndroidManifest.xml'java {exclude 'debug/**'}}}}

通过config.gradle中的变量判断该moudle到底用哪个AndroidManifest.xml文件,这两个的区别在于进入的时候是否需要配置activity启动intent-filter。

其实上面的这些操作我们都可以通过Gradle插件来帮我们管理组件,已经有人帮我们实现了这个想法Gradle自动实现Android组件化模块构建,为了深入理解构建方式本文分析下该开源项目calces-gradle-plugin,阅读本文前可以先阅读关于Groovy的语法的文章Gradle自定义Plugin(上)。

我们先看下项目结构目录:
在这里插入图片描述
1处是定义的配置的moudle信息模型层,2是处理组件与组件之间的Manifest合并问题的,3是针对app壳工程编写的Plugin插件 ,4是针对非壳工程moudle编写的Plugin插件 AppConfigPlugin和ModulesConfigPlugin编写完后需要在下面的目录对应配置下。我们先看AppConfigExt类,这个一个gradle配置信息入口的类

class AppConfigExt {boolean isDebug = false //组件单独开启开关//容纳object的容器,它的特点是它的内部使用SortedSet实现的必须有一个public的构造函数,// 接受string作为一个参数,必须有一个叫做name 的propertyNamedDomainObjectContainer<AppExt> apps //宿主载体NamedDomainObjectContainer<LibraryExt> modules  //组件AppConfigExt(Project project){apps = project.container(AppExt) //创建object的容器对象modules = project.container(LibraryExt)}def isDebug(boolean isDebug){this.isDebug = isDebug}def apps(Closure closure){apps.configure(closure)}def modules(Closure closure){modules.configure(closure)}@OverrideString toString() {return "isDebug: $debugEnable\n" +"apps: ${apps.isEmpty()? "is empty" : "$apps"}"+"modules: ${modules.isEmpty()? "is empty" : "$modules"}"}
}

AppExt 这个类是宿主App

class AppExt extends ModulesExt{String dependMethod = "implementation" //默认依赖的方式List<String> modules = new ArrayList<>() //依赖组件的名称集合AppExt(String name) {super(name)}
......

各个组件的属性配置

class LibraryExt extends ModulesExt {boolean isRunAlone = false //是否独立运行String runAloneSuper //该moudle所依赖的子模块LibraryExt(String name) {super(name)}........

宿主App和各个组件的基类

class ModulesExt {String name  //组件名称String applicationId  //组件applicationIdString mainActivity   //该组件启动的activityModulesExt(String name){this.name = name //如果有父节点必须写构造方法}
}

先用AppConfigPlugin处理整个项目项目build.gradle的配置信息

public class AppConfigPlugin  implements Plugin<Project> {private static final String EXTENSION_NAME = "appConfig"@Overridevoid apply(Project project) {AppConfigExt appConfigExt = new AppConfigExt(project)// project.extensions,本扩展类其实就是只用于set、get的JavaBean类project.extensions.add(EXTENSION_NAME, appConfigExt)configApp(project)}void configApp(Project project) {List<String> moduleList = new ArrayList<>()NamedDomainObjectContainer<AppExt> appListAppConfigExt appConfigExt//当前project配置状态进行回调afterEvaluate(project开始配置前调用)//和beforeEvaluate,afterEvaluate(project配置完成后回调)project.afterEvaluate {//得到配置的信息beanappConfigExt = project.extensions.getByName(EXTENSION_NAME) as AppConfigExtappList = appConfigExt.appscheckRepeat(appConfigExt)checkModules(appConfigExt,moduleList)}initChildModules(moduleList, project)println("project child modules: $moduleList")}//运行后获取项目存在的所有组件void initChildModules(List<String> moduleList ,Project project){if (project.childProjects.isEmpty()){moduleList.add(project.toString().replace("project ","").replace('\'',''))return}//运行时遍历获取所有存在的组件(App壳工程和寄生组件)project.childProjects.entrySet().forEach{initChildModules(moduleList, it.value)}}//检查配置的模块是否重复static void checkRepeat(AppConfigExt appConfigExt){//取出App壳工程组件名称列表  以name字段为分组条件 //很重要的一点是NamedDomainObjectContainer容器节点的名称是唯一的,是按照节点的名称排序的,//如果有相同的节点名称 后面会覆盖前面的 (这个需要自行实验才能理解)//并且必须有一个public的构造函数,接受string作为一个参数,必须有一个叫做name 的property//这个property默认就是节点的名称 需要和项目路径对应,如果不填写默认为该配置的名字 //就是moudle节点的名称(如配置名为app的话,name则为:app)//倒入规则和setting.gradle中的include规则保持一致Map<String,List<AppExt>> appGroupMap =appConfigExt.apps.groupBy{ it.name.startsWith(':') ? it.name : new String(":" + it.name)}//k指的是name v指List<AppExt>   如果多个moudle节点的名称不同 但是里面的内容一样//当v.size() > 1的时候说明apps组件名称重复appGroupMap.forEach{k,v ->if (v.size() > 1){throw new IllegalArgumentException("app is repeat. app name: [$k]")}}//取出寄生组件名称列表Map<String,List<LibraryExt>> moduleGroupMap =appConfigExt.modules.groupBy{ it.name.startsWith(':') ? it.name : new String(":" + it.name)}//k指的是name v指List<LibraryExt>  如果多个moudle节点的名称不同 但是里面的内容一样//这个时候就表示v.size() > 1说明apps宿主名称名称重复moduleGroupMap.forEach{k,v ->if (v.size() > 1){throw new IllegalArgumentException("modules is repeat. modules name: [$k]")}}}//检查配置的模块名称是否合理正确static void checkModules(AppConfigExt appConfigExt,List<String> projectModules){Set<String> configSet = new HashSet<>() //配置的所有组件集合Set<String> modulesSet = new HashSet<>() //真实获取的组件集合if (projectModules != null){modulesSet.addAll(projectModules)}List<String> notFoundList = new ArrayList<>()List<String> appNameList = appConfigExt.apps.stream().map{it.name.startsWith(':') ? it.name : new String(":" + it.name)}.collect()List<String> moduleNameList =appConfigExt.modules.stream().map{String name = it.name.startsWith(':') ? it.name : new String(":" + it.name)//App壳工程的名字不能出现在其他组件当中if (appNameList.contains(name)){throw new IllegalArgumentException("$it.name already configured " +"as an application, please check appConfig")}name}.collect()println "moduleNameList = $moduleNameList"configSet.addAll(appNameList)configSet.addAll(moduleNameList)configSet.forEach{if(!modulesSet.contains(it)){notFoundList.add(it)}}//寄生组件配置的组件名称不存在if (notFoundList.size() > 0){throw  new IllegalArgumentException("not fount modules = " + notFoundList)}//App壳工程依赖的组件不存在appConfigExt.apps.stream().forEach{ app ->app.modules.stream().forEach{if (! configSet.contains(it)){throw  new IllegalArgumentException("appConfig error , can not find $app.name modules $it by project" )}}}println("modules: " + configSet)}}

然后处理每个moudle的build.gradle的配置信息的配置信息

public class ModulesConfigPlugin implements Plugin<Project> {private static final String PARENT_EXTENSION_NAME = "appConfig"@Overridevoid apply(Project project) {AppConfigExt appConfigExt = getAppConfigExtension(project)configModules(project, appConfigExt)}static void configModules(Project project, AppConfigExt appConfigExt){if (appConfigExt == null){throw new NullPointerException("can not find appConfig")}List<AppExt> filterList = appConfigExt.apps.stream().filter{ (it.name.startsWith(':') ? it.name : new String(":" + it.name)).endsWith(project.name) }.skip(0).collect()  //Java8 Stream 语法if (filterList != null && filterList.size() > 0){ //说明当前是已App壳工程编译AppExt appExt = filterList.get(0)AppPlugin appPlugin = project.plugins.apply(AppPlugin)//App壳工程的build.gradle文件设置ApplicationIdappPlugin.extension.defaultConfig.setApplicationId(appExt.applicationId)//检查配置app壳工程的Manifest文件信息new AppManifestStrategy(project).resetManifest(appExt)  //检查配置app壳工程的依赖问题dependModules(project, appExt, appConfigExt)}else {//检查配置子moudle独立运行modulesRunAlone(project,appConfigExt.modules, appConfigExt.isDebug)}}//app壳工程依赖static void dependModules(Project project, AppExt appExt, AppConfigExt appConfigExt){//遍历appConfigExt.modules数据和appExt.modules数据对比得到交集然后以map形式返回Map<String,LibraryExt> moduleExtMap = appConfigExt.modules.stream().filter{modules ->String modulesName = appExt.modules.stream().find{ it.contains(modules.name) }modulesName != null && !modulesName.isEmpty()}.collect(Collectors.toMap({ it.name},{ it -> it}))if (appExt.modules != null && appExt.modules.size() > 0){List<String> modulesList = appExt.modules.stream().filter{ //遍历数据并检查其中的元素时使用 类似if//是否开启debug模式 debug为true的时候 moudle的isRunAlone必须要是true才能被依赖appConfigExt.isDebug ? (moduleExtMap != null && !moduleExtMap[it].isRunAlone) : true }.map{ //map生成的是个一对一映射,for的作用//添加依赖 implementation XXXX模块project.dependencies.add(appExt.dependMethod, project.project(it))it}.collect()println("build app: [$appExt.name] , depend modules: $modulesList")}}//获取父节点的配置信息AppConfigExt getAppConfigExtension(Project project){try{//项目根节点下的配置信息return project.parent.extensions.getByName(PARENT_EXTENSION_NAME) as AppConfigExt }catch (UnknownDomainObjectException ignored){if (project.parent != null){getAppConfigExtension(project.parent)}else {throw new UnknownDomainObjectException(ignored as String)}}}//子moudle独立运行private static void modulesRunAlone(Project project, NamedDomainObjectContainer<LibraryExt> modules, boolean isDebug){List<LibraryExt> filterList = modules.stream().filter{ it.name.endsWith(project.name) }.skip(0).collect()if (filterList != null && filterList.size() > 0){//当前子moudle编译LibraryExt moduleExt = filterList.get(0)if (isDebug && moduleExt.isRunAlone){//开启debug模式并且isRunAlone为trueAppPlugin appPlugin = project.plugins.apply(AppPlugin)appPlugin.extension.defaultConfig.setApplicationId(moduleExt.applicationId) //设置App工程ApplicationIdif (moduleExt.runAloneSuper != null && !moduleExt.runAloneSuper.isEmpty()){ //project.dependencies.add("implementation", project.project(moduleExt.runAloneSuper))println("build run alone modules: [$moduleExt.name], runSuper = $moduleExt.runAloneSuper")}else{println("build run alone modules: [$moduleExt.name]")}if (moduleExt.mainActivity != null && !moduleExt.mainActivity.isEmpty()){//检查配置app壳工程的Manifest文件信息new AppManifestStrategy(project).resetManifest(moduleExt)}}else{project.plugins.apply(LibraryPlugin)//设置App依赖工程//检查配置依赖工程的Manifest文件信息new LibraryManifestStrategy(project).resetManifest(moduleExt)}}}}

处理每个moudle的时候需要处理每个moudle的AndroidManifest文件信息

abstract class ManifestStrategy {protected String pathprotected GPathResult manifestboolean edit = false //AndroidManifest配置文件是否修改过ManifestStrategy(Project project) {path = "${project.getBuildFile().getParent()}/src/main/AndroidManifest.xml"File manifestFile = new File(path) //找到moudle下的AndroidManifest.xml文件if (!manifestFile.getParentFile().exists() && !manifestFile.getParentFile().mkdirs()) {println "Unable to find AndroidManifest and create fail, please manually create"}manifest = new XmlSlurper().parse(manifestFile)//xml解析}//子类需要继承 设置启动Activity的IntentFilter属性abstract void setMainIntentFilter(def activity, boolean isFindMain)void resetManifest(ModulesExt moduleExt) {if (manifest.@package != moduleExt.applicationId && moduleExt.applicationId != null &&!moduleExt.applicationId.isEmpty()) {//重新设置manifest的包名(如果设置了 没用默认的applicationId)manifest.@package = moduleExt.applicationId edit = true}boolean isFindMain = false //是否找到了启动activityif (moduleExt.mainActivity != null && !moduleExt.mainActivity.isEmpty()) {manifest.application.activity.each { activity ->if (activity.@'android:name' == moduleExt.mainActivity) {def filter = activity.'intent-filter'.find {it.action.@'android:name' == "android.intent.action.MAIN"}isFindMain = true//如果没有设置IntentFilter条件 代码帮助其设置setMainIntentFilter(activity, filter != null && filter.size() > 0)}}}manifest.application.activity.each { activity ->def filter = activity.'intent-filter'.find {it.action.@'android:name' == "android.intent.action.MAIN"}if (filter != null&& moduleExt.mainActivity != null&& !moduleExt.mainActivity.isEmpty()&& activity.@'android:name' != moduleExt.mainActivity) {//如果设置了IntentFilter条件 但是启动activity和配置的不一样则清空IntentFilter条件filter.replaceNode {} edit = true}}//如果在AndroidManifest配置文件当中没有找到启动activity那么就手动设置if (!isFindMain) {addMainActivity(manifest.application, moduleExt)}if (edit) {buildModulesManifest(manifest)}}//代码添加启动activityvoid addMainActivity(def application, ModulesExt modulesExt) {if (modulesExt.mainActivity != null && !modulesExt.mainActivity.isEmpty()) {application.appendNode {activity('android:name': modulesExt.mainActivity) {'intent-filter' {action('android:name': "android.intent.action.MAIN")category('android:name': "android.intent.category.LAUNCHER")}}}edit = true}}//动态修改AndroidManifest节点下的配置问题void buildModulesManifest(def manifest) {def fileText = new File(path)StreamingMarkupBuilder outputBuilder = new StreamingMarkupBuilder() //创建XMLdef root = outputBuilder.bind {mkp.xmlDeclaration()mkp.declareNamespace('android': 'http://schemas.android.com/apk/res/android')mkp.yield manifest}String result = XmlUtil.serialize(root)fileText.text = result}
}//App壳工程的AndroidManifest配置文件  setMainIntentFilter中添加intent-filterclass AppManifestStrategy extends ManifestStrategy {AppManifestStrategy(Project project) {super(project)}@Overridevoid setMainIntentFilter(def activity, boolean isFindMain) {if (!isFindMain) {activity.appendNode {'intent-filter' {action('android:name': "android.intent.action.MAIN")category('android:name': "android.intent.category.LAUNCHER")}}edit = true}}}//依赖moudle工程的AndroidManifest配置文件  setMainIntentFilter中清除intent-filterclass LibraryManifestStrategy extends ManifestStrategy {LibraryManifestStrategy(Project project) {super(project)}@Overridevoid setMainIntentFilter(def activity, boolean isFindMain) {if (isFindMain) {println "build lib"activity.'intent-filter'.each {if (it.action.@'android:name' == "android.intent.action.MAIN") {it.replaceNode {}edit = true}}}}}

以上的代码注释信息便是这个项目的所有代码模块解析了,只有去阅读原作者相关文章和demo跑跑才能理解透彻。至于组件化全局的Application处理和组件通信这些方面的问题本文就不做深入探讨了。具体用法请查看相关说明calces-gradle-plugin。

这篇关于Android组件化Gradle插件Calces源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/887434

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

使用Java实现一个解析CURL脚本小工具

《使用Java实现一个解析CURL脚本小工具》文章介绍了如何使用Java实现一个解析CURL脚本的工具,该工具可以将CURL脚本中的Header解析为KVMap结构,获取URL路径、请求类型,解析UR... 目录使用示例实现原理具体实现CurlParserUtilCurlEntityICurlHandler

深入解析Spring TransactionTemplate 高级用法(示例代码)

《深入解析SpringTransactionTemplate高级用法(示例代码)》TransactionTemplate是Spring框架中一个强大的工具,它允许开发者以编程方式控制事务,通过... 目录1. TransactionTemplate 的核心概念2. 核心接口和类3. TransactionT

数据库使用之union、union all、各种join的用法区别解析

《数据库使用之union、unionall、各种join的用法区别解析》:本文主要介绍SQL中的Union和UnionAll的区别,包括去重与否以及使用时的注意事项,还详细解释了Join关键字,... 目录一、Union 和Union All1、区别:2、注意点:3、具体举例二、Join关键字的区别&php

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Spring IOC控制反转的实现解析

《SpringIOC控制反转的实现解析》:本文主要介绍SpringIOC控制反转的实现,IOC是Spring的核心思想之一,它通过将对象的创建、依赖注入和生命周期管理交给容器来实现解耦,使开发者... 目录1. IOC的基本概念1.1 什么是IOC1.2 IOC与DI的关系2. IOC的设计目标3. IOC