本文主要是介绍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源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!