文章目录

  • 前言
  • 一、gradle统一配置
    • 1. 多模块项目的构建
    • 2. 根项目的构建配置
    • 3. 常用公用的构建配置
  • 二、nexus与maven-publish
    • 1.安装nexus
    • 2.仓库
    • 3. maven-publish
  • 三、动态依赖
    • 1.依赖的传递性
    • 2.`project/module`依赖切换
    • 3. 总结与实践
  • 四、模块通信
    • 1.通信方式
    • 2.路由方式
    • 3.接口方式
    • 4.总结与实践
  • 总结

前言

提示:这里需要提前对Android-模块化-基本知识了解
本文主要分享个人在项目中实现Android模块化中的gradle统一配置、nexus、maven-publish、动态依赖、模块通信等思路


组件化项目demo

一、gradle统一配置

1. 多模块项目的构建

settings.gradle 是根模块项目以及模块描述文件,include '模块路径(分隔符是冒号)' 或如下别名引入

include 'VScreen_App' //不建议有冒号
project(":VScreen_App").projectDir = file("VScreen") //指定真实模块路径

include 子模块技巧 ,如下

def sub_father = ':' //子项目父工程名, 更为了能Find Usages//基础组件库
sub_father = ':--base_modules'
include '',"$sub_father:lib_arouter", //阿里路由"$sub_father:lib_baseAndroid", //安卓基础api"$sub_father:lib_comm_ui", // ui组件库"$sub_father:lib_component", // 常用组件库"$sub_father:lib_export_table_java", // export_table组件库"$sub_father:lib_glide", // img_glide"$sub_father:lib_okhttp", // net_okhttp//"$sub_father:lib_zxing",''

业务模块过多,include 业务模块技巧 ,约定在指定目录如下

//业务模块
def business_modules_name = new ArrayList<String>()
def business_modules_symbol = new ArrayList<String>()
for (f in file("business_modules").listFiles()) {if (f.isDirectory() && new File(f, "build.gradle").exists()) {def name = ":business_modules:${f.name}"business_modules_name.add("${name}")business_modules_symbol.add("'${name}'")}
}
//业务模块动态添加 (考虑的业务模块有很多)
def business_modules_dynamically_add = true
if (business_modules_dynamically_add) {//动态添加目录底下所有business_modules_name.forEach {include(it)}
} else {//手动按需添加def include_business_modules_str = "include '',\n"business_modules_symbol.forEach {include_business_modules_str += "$it,\n"}include_business_modules_str += "''"println "输出include脚本, 按需开启\n" + include_business_modules_str + "\n输出include脚本, 按需开启"//Gradle窗口: 输出include脚本, 按需开启//include '',//        ':business_modules:lib_attendance',//        ':business_modules:lib_consume',//        ':business_modules:lib_family_phone',//        ''}println "> Configure 业务模块 : ${business_modules_symbol}"

老项目工程庞大臃肿,一时无法分离。 一般我们会把这个app工程转化为核心库(下沉给其它工程依赖使用),添加新的壳工程。我们能不能做到不需要空壳app ?答案是肯定的

build.gradle 描述子模块的项目的插件、属性、依赖等。可以在settings.gradle 中自定义脚本文件名

project(":VScreen").buildFileName = "lib_core.gradle"  //改变脚本一个工程打两份工,实测ojbk

Gradle Event Log 提示重复工程,不友好,但是能节省了一个无意义的壳。

23:37    Duplicate content roots detected: Path [/Users/system/Work/projectcode/zippkgcode/vx-screen/VScreen] of module [vx-screen.VScreen] was removed from modules [vx-screen.VScreen_App]

gradle 命令时, 默认情况下总是会构建当前目录下的文件 build.gradle 可以添加-b 参数-p 参数

gradle xxxTask -b lib_core.gradle
gradle xxxTask -p 所在目录

2. 根项目的构建配置

根项目下build.gradle 描述根模块的项目的插件、属性、依赖等。 大家最熟悉的buildscript,里面也一般配置大家熟悉的repositories dependencies 属性

buildscript {ext.gradle_tools_version = '7.0.4' //可定义全局属性和函数repositories {}dependencies {}
}

另外allprojects 的下配置repositories 是不是也熟悉,这是配置此项目及其每个子项目属性。因此这里可以很灵活地配置项目所需属性。如统一编译配置、动态依赖


//配置此项目及其每个子项目。
allprojects { //此方法针对该项目及其子项目执行给定的闭包。目标Project作为闭包的委托传递给闭包。//配置此项目的存储库repositories {google()maven { url "https://jitpack.io" } //也可以使用nexus,下文会说到}configurations.all {  //目前只发现这里处理依赖相关的配置 [官网文档说明](https://docs.gradle.org/current/userguide/resolution_rules.html)//每隔24小时检查远程依赖是否存在更新resolutionStrategy.cacheChangingModulesFor 24, 'hours'//每隔10分钟..//resolutionStrategy.cacheChangingModulesFor 10, 'minutes'// 采用动态版本声明的依赖缓存10分钟resolutionStrategy.cacheDynamicVersionsFor 10 * 60, 'seconds'resolutionStrategy.dependencySubstitution {//project&module依赖关系切换处理 方式1substitute(module("cn.mashang.stub_modules:api_box:1.0.0")) using(project(":stub_modules:api_box"))substitute(module("cn.mashang.stub_modules:constant_box:1.0.0")) using(project(":stub_modules:constant_box"))}//transitive = false //默认为true,一般不会这样设,还可以指定Force、exclude等配置}//verbose javac: 开启java 编译loggradle.projectsEvaluated {tasks.withType(JavaCompile) {options.compilerArgs << "-Xlint" << "-verbose" << "-XprintRounds" << "-XprintProcessorInfo" << "-Xmaxerrs" << "2000"}}//:app 添加在评估此项目后立即调用的闭包。项目作为参数传递给闭包。当属于该项目的构建文件已执行时,此类侦听器会收到通知。例如,父项目可以将这样的监听器添加到其子项目。这样的侦听器可以在它们的构建文件运行后根据子项目的状态进一步配置这些子项目。project.afterEvaluate { Project p ->if (p.plugins.hasPlugin('com.android.application') || p.plugins.hasPlugin('com.android.library')) {android {compileSdkVersion 32defaultConfig {minSdkVersion 21 (默认)targetSdkVersion 32//构建project版本信息,此处能读取配置后的版本信息if (buildFeatures.buildConfig) {buildConfigField(intType, "BUILD_CODE", "${versionCode}")buildConfigField(str, "BUILD_VERSION_NAME", "\"${versionName}\"")}}compileOptions {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8}}}}
}

3. 常用公用的构建配置

一般我们会定义一些config.gradle和config.properties 配置文件,引用这些文件达到复用公用的配置信息。一般引用方式代码如下(示例):

apply from: rootProject.file('./buildConfig/baseAndroid.gradle') //这里建议用rootProject.file,和'./' 避免无法定位文件路径
//加载properties配置文件
def dict = new Properties()
dict.load(new FileInputStream(rootProject.file("./buildConfig/base.properties")))
def str = dict['DefString']

base.properties 定义了常用的变量值,相对gradle 更方便索引和维护以及覆盖属性 代码如下(示例):

#要用gbk 编码
# author rentianlong
#2020年 8月 7日 星期五 12时03分25秒 CST
#java basic type
DefString=String
DefInt=int
DefBool=boolean
DefLong=long
trueStr=true
falseStr=false
#android buildVersion
compileSdkVersion=30
minSdkVersion=19
targetSdkVersion=22
## 项目模块配置
# 所有模块app/lib切换开关, 集成相应模块:默认true
libModulesIsLib=true

如果是新项目,推荐buildSrc配置信息

baseAndroid.gradle 定义通用的配置, 更方便索引和维护 代码如下(示例):

/*** 作用描述:* Base-Android build file where you can add configuration options common to all sub-projects/modules.* Base - Android构建文件,您可以添加配置选项常见的所有子项目/模块。*///打印日志
println rootProject.file('./buildConfig/baseAndroid.gradle').getAbsolutePath()
//当前模块信息
def projectDir = getProjectDir()
def projectDirPath = projectDir.absolutePath
println projectDirPath + "\\build.gradle"
def projectName = project.getName()//Properties工具方法
static def getBool(Properties properties, String key) {return Boolean.parseBoolean(properties[key])
}
//加载配置文件
def dict = new Properties()
dict.load(new FileInputStream(rootProject.file("./buildConfig/base.properties")))def moduleConfig = new File(projectDir, 'debugConfig.properties')
if (moduleConfig.exists()) {println 'load submodule_customization configs: ' + moduleConfig.getAbsolutePath()dict.load(new FileInputStream(moduleConfig))
}def BUILD_COMPUTER_TIME = "BUILD_COMPUTER_TIME"
def str = dict['DefString']
def intType = dict['DefInt']
def longType = dict['DefLong']
def trueStr = dict['trueStr']
def compileSdkVersionVar = dict['compileSdkVersion'] as int
def minSdkVersionVar = dict['minSdkVersion'] as int
def targetSdkVersionVar = dict['targetSdkVersion'] as int//组件化application和library 动态切换
def hasAppPlugin = pluginManager.hasPlugin("com.android.application")
def libModulesIsLib = getBool(dict, 'libModulesIsLib')//是否是正式包 (BuildTypes)
boolean isReleaseBuildType() {for (String s : gradle.startParameter.taskNames) {if (s.contains("Release") | s.contains("release")) {return true}}return false
}def isRelease = isReleaseBuildType()
project.ext.isRelease = isRelease
//println(">>>>> isRelease:$isRelease") //打印日志//获取构建时间
long getBuildTime() {def calendar = Calendar.getInstance()if (!isRelease) { //编译优化策略calendar.set(Calendar.HOUR_OF_DAY, 0)calendar.set(Calendar.MINUTE, 0)calendar.set(Calendar.SECOND, 0)calendar.set(Calendar.MILLISECOND, 0)}return calendar.getTimeInMillis()
}def myBuildTime = "${getBuildTime()}"if (!hasAppPlugin) { //如果是非app模块if (libModulesIsLib) { //组件化切换调试常见方案plugins.apply("com.android.library")println 'apply lib'} else {hasAppPlugin = trueplugins.apply("com.android.application")println 'apply application'}
}
ext.set("hasAppPlugin", hasAppPlugin)
ext.set("libModulesIsLib", libModulesIsLib)//阿里路由框架启用, 像UI类库不需要路由增加编译压力
def hasLibARouter = ext.find("lib_arouter") == true
if (hasLibARouter) {apply plugin: 'com.alibaba.arouter' //arouter register plugin 实现自动注册println 'apply arouter '
}android {compileSdk compileSdkVersionVar//resourcePrefix "submodule_customization_todo" //子模块定制待办事项defaultConfig {multiDexEnabled trueminSdk minSdkVersionVartargetSdk targetSdkVersionVar//版本信息默认versionCode 1versionName "1.0.0"//资源配置resConfigs "en", "zh"//ndk配置ndk {//设置支持的so库框架abiFilters 'armeabi-v7a'}//阿里路由框架启用, 像UI类库不需要路由增加编译压力if (hasLibARouter) {// 阿里路由框架注解配置, 每个模块需要依赖javaCompileOptions {annotationProcessorOptions {arguments = [AROUTER_MODULE_NAME: projectName]}}}if (buildFeatures.buildConfig) {buildConfigField("boolean", "IS_APPLICATION", "${hasAppPlugin}")//构建时间buildConfigField(longType, BUILD_COMPUTER_TIME, "${myBuildTime}")//构建project版本信息,此处只能读取到版本1, 需要放在主脚本android闭包里//buildConfigField(intType, "BUILD_CODE", "${versionCode}")//buildConfigField(str, "BUILD_VERSION_NAME", "\"${versionName}\"")}}//apk签名配置signingConfigs {keystore {keyAlias 'xxx'keyPassword 'xxx'storeFile rootProject.file('./Release/xxx.jks')storePassword 'xxx'enableV1Signing trueenableV2Signing true//通过 APK v4 签名,您可以使用 Android 11 中的 ADB 增量 APK 安装快速部署大型 APK。此新标志负责部署过程中的 APK 签名步骤。enableV3Signing trueenableV4Signing true}}buildTypes {debug {zipAlignEnabled trueminifyEnabled falsesigningConfig signingConfigs.keystore//独立调试if (!libModulesIsLib) {applicationIdSuffix ".debug"sourceSets {main { //建立demo资源夹manifest.srcFile 'src/demo/AndroidManifest.xml'java.srcDirs = ['src/main/java', 'src/demo/java']res.srcDirs = ['src/main/res', 'src/demo/res']}}}}release {}}//java编译配置compileOptions {// Flag to enable support for the new language APIscoreLibraryDesugaringEnabled truesourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}//lint配置lintOptions {//不检查release版本的构建checkReleaseBuilds false//停用 出现错误时停止编译abortOnError false}lintOptions {checkDependencies true}//打包配置packagingOptions {merge "/arouter/config.properties"}//dex配置dexOptions {javaMaxHeapSize "4g"//是否支持大工程模式jumboMode = true//预编译preDexLibraries = true//线程数threadCount = 8maxProcessCount = 8 // this is the default value 4 //根据CPU核心设置//设置是否启用dx增量模式 debug时,开启有加速效果incremental true//是将 dx 编译器作为单独的进程运行还是在 Gradle 守护进程 JVM 中运行dexInProcess = true}//adb配置adbOptions {//timeOutInMs 5 * 1000 //超时//installOptions '-r'   //覆盖//installOptions '-r -t' //覆盖测试 ()//installOptions '-t' //测试 ()//installOptions '-d' //降级}buildFeatures {//feature enable state config }sourceSets {main {}}
}def autoDependencies = ext.find("auto_dependencies") == false  //自动依懒关闭 (默认开启)
def autoBasicLibDependencies = ext.find("auto_basiclib_dependencies") == null //自动依懒基本库开启 (默认开启)//公共依赖
dependencies {//api fileTree(include: ['*.jar'], dir: 'libs') //确保libs 都是要加入才开启注释if (autoDependencies) { //自动依懒关闭println("baseAndroid.gradle:auto_dependencies:close " + projectName)return null}rootProject.ext.dependencies.basicApi.each { implementation(it) }println("baseAndroid.gradle:basicApi:auto: " + projectName + " <<== " + rootProject.ext.dependencies.basicApi)if (autoBasicLibDependencies) {//本地lib工程rootProject.ext.dependencies.basicLibProject.each {String itemName = itif (!itemName.contains(projectName)) {println("baseAndroid.gradle:basicLibProject:auto: " + projectName + " <<== " + itemName)implementation project(itemName)}}//本地lib Nexusif ("lib_baseAndroid" != projectName) {//lib_baseAndroid 模块//implementation 'cn.mashang.base_modules:lib_baseAndroid:1.0.0'}}//阿里路由框架启用, 像UI类库不需要路由增加编译压力if (hasLibARouter) {println("baseAndroid.gradle:lib_arouter:auto: ==> " + projectName)implementation('com.alibaba:arouter-api:1.5.2') { // 阿里路由框架apiexclude group: 'com.android.support', module: 'support-v4'}annotationProcessor 'com.alibaba:arouter-compiler:1.5.2' // 阿里路由注解框架,每个模块需要依赖}//Java 8 及更高版本 API 脱糖支持coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
}

以上代码(示例): 它方便了 lib模块切换为app、签名配置、公共依赖等

  • lib模块切换为app实现关键:要根据开关配置应用的application或library插件 关键代码如下:
if (!hasAppPlugin) { //如果是非app模块if (libModulesIsLib) { //组件化切换调试常见方案plugins.apply("com.android.library") //等同于apply plugin: 'com.android.library'println 'apply lib'} else {hasAppPlugin = trueplugins.apply("com.android.application") //apply plugin: 'com.android.application'println 'apply application'}
}

base.properties 下libModulesIsLib 控制所有模块app/lib切换开关,false所有lib模块转换为app

# 所有模块app/lib切换开关, 集成相应模块:默认true
libModulesIsLib=true

工程app简单示例如下:

apply plugin: 'com.android.application'
apply from: rootProject.file('./buildConfig/baseAndroid.gradle')

模块lib简单示例如下:

//默认应用的是com.android.library
apply from: rootProject.file('./buildConfig/baseAndroid.gradle')

真实项目中,不需要所有模块都能单独调试和运行所以利用了properties 覆盖属性,其实properties是扩展Hashtable的,不难想象load其它配置后相当于map.put

def moduleConfig = new File(projectDir, 'debugConfig.properties')
if (moduleConfig.exists()) {println 'load submodule_customization configs: ' + moduleConfig.getAbsolutePath()dict.load(new FileInputStream(moduleConfig))
}

每个模块下创建debugConfig.properties 文件, 放置调试的配置信息

debugConfig.properties ,配置变动记得在View-Gradle 视图中reload gradle project

# 单模块app/lib切换开关, 集成相应模块: false或'',null为app, 默认true, 修改后需要 reload gradle project
#libModulesIsLib=false

.gitignore 小技巧,提交完debugConfig.properties 文件,使用它

/build
./debugConfig.properties
  • 公共依赖要注意依赖的合理性和传递性

二、nexus与maven-publish

Nexus 最为大家熟知的功能就是 maven 的依赖包管理器。

架设 Nexus 私服有很多优点,其中之一就是:

  • 方便上传团队内部的依赖,统一管理,共享

aar 大家最为熟悉,也称为本地静态aar依赖,对比远程仓库中的依赖包 implementation('com.squareup.retrofit2:retrofit:2.4.0') ,发现远程仓库中的依赖包的pom.xml文件已经包括相应okhttp的依赖关系,这里不展开说明,所以远程依赖包的好处如下:

  • 不用维护依赖传递
  • 代码不需暴露
  • 加快编译

1.安装nexus

官网地址
windows安装包

运行命令

bin/nexus.exe /run

注册账号,nexus相关配置不一一说明

2.仓库

这里的仓库是指项目中依赖的第三方库,这个库所在的位置叫做仓库。 在Maven 中,任何一个依赖、插件或者项目构建的输出,都可以称之为构件。

跟项目下build.gradle 声明仓库地址

 ext.maven_local_repo_url = "$projectDir/.repo" //本地仓库地址ext.maven_nexus_snapshots_repo_url = 'http://xx.cpolar.cn/repository/yourProj-snapshots/'ext.maven_nexus_releases_repo_url = 'http://xx.cpolar.cn/repository/yourProj-releases/'

repositories 配置

allprojects {repositories {maven {url maven_local_repo_url}maven {url maven_nexus_snapshots_repo_urlallowInsecureProtocol = truecredentials {username = "guest"password = "guest"}}maven {url maven_nexus_releases_repo_urlallowInsecureProtocol = truecredentials {username = "guest" //nexus游客,只允许访问password = "guest"}}google()jcenter()}

3. maven-publish

Android Gradle 插件 3.6.0 及更高版本支持 Maven Publish Gradle 插件
使用 Maven Publish 插件

多数模块都需要发布,maven-publish.gradle 示例:

/*** 作用描述: maven版本发布共享库管理,依赖maven可大大节省编译时间* 创建人 rentl* 创建日期 2022/1/30* 修改日期 2022/1/30*/
println 'Executing maven-publish...'
apply plugin: 'maven-publish'
def ENV = System.getenv()task generateSourcesJar(type: Jar) {from android.sourceSets.main.java.srcDirsclassifier 'sources'
}def groupIdStr = ext.find("groupId")
def artifactIdStr = ext.find("artifactId")
if (groupIdStr == null) {System.err.println('Executing maven-publish fail. groupId == null')throw new IllegalArgumentException('Executing maven-publish fail. groupId == null')
}
if (artifactIdStr == null) {System.err.println('Executing maven-publish fail. artifactId == null')throw new IllegalArgumentException('Executing maven-publish fail. artifactId == null')
}println "Executing maven-publish: groupId=$groupId, artifactId=$artifactId"afterEvaluate {publishing {publications {release(MavenPublication) {from components.releasegroupId = "$groupIdStr"artifactId = "$artifactIdStr"version = project.android.defaultConfig.versionNameartifact generateSourcesJar}}repositories {maven {url = rootProject.ext.maven_local_repo_url}maven {name = "nexus"url = project.android.defaultConfig.versionName.endsWith('SNAPSHOT') ? rootProject.ext.maven_nexus_snapshots_repo_url : rootProject.ext.maven_nexus_releases_repo_urlallowInsecureProtocol = true// 仓库用户名密码credentials {username = ENV['NEXUS_NAME']password = ENV['NEXUS_PWD']}}}}def publishTask = project.tasks.getByName('publishReleasePublicationToMavenRepository')if (publishTask != null) {publishTask.doLast {println "maven-publish to .repo, Usage:\nimplementation '${groupIdStr}:${artifactIdStr}:${project.android.defaultConfig.versionName}'"}}def publishTask2 = project.tasks.getByName('publishReleasePublicationToNexusRepository')if (publishTask2 != null) {publishTask2.doLast {println "maven-publish to nexus, Usage:\nimplementation '${groupIdStr}:${artifactIdStr}:${project.android.defaultConfig.versionName}'"}}
}

模块的build.gradle 示例:

apply from: rootProject.file('./buildConfig/baseAndroid.gradle')//maven
ext.groupId = "cn.mashang.base_modules"
ext.artifactId = project.getName()android {resourcePrefix "base_base_"defaultConfig {versionCode 1versionName "1.0.0"//versionName "1.0.1-SNAPSHOT"consumerProguardFiles "consumer-rules.pro" //lib-proguard}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}if (!hasAppPlugin) { //这里需要放在这里底下,需要获取版本信息apply from: rootProject.file('./buildConfig/maven-publish.gradle')
}

maven规约

这里还是要提及一下要遵maven规约, 大家认同的详细规定参考下方:
1)GroupID格式:com.{公司/BU }.业务线.[子业务线],最多4级
正例:com.joymef.platform 或 com.joymef.social.blog
2)ArtifactID格式:产品线名-模块名。语义不重复不遗漏,先到仓库中心去查证一下
3)正例:user-service / user-client / blog-service ) Version
4)开发阶段版本号定义为SNAPSHOT,发布后版本改为RELEASE(强制)

上面是安卓模块publish,java的模块需要稍微调整maven-jar-publish.gradle示例如下:

/*** 作用描述: maven jar版本发布共享库管理,依赖maven可大大节省编译时间* 组件描述:* 创建人 rentl* 创建日期 2022/2/26* 修改日期 2022/2/26* 版权 mashang*/
println 'Executing maven-java-publish...'compileJava.options.encoding = 'UTF-8'
javadoc.options.encoding = 'UTF-8'
apply plugin: 'maven-publish'
java {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8//withJavadocJar()withSourcesJar()
}components.java.withVariantsFromConfiguration(configurations.sourcesElements) {skip()
}def ENV = System.getenv()
def groupIdStr = ext.find("groupId")
def artifactIdStr = ext.find("artifactId")
def versionStr = ext.find("version")
afterEvaluate {publishing {publications {release(MavenPublication) {from components.javagroupId = "$groupIdStr"artifactId = "$artifactIdStr"version = versionStr}}repositories {maven {url = rootProject.ext.maven_local_repo_url}maven {name = "nexus"url = versionStr.endsWith('SNAPSHOT') ? rootProject.ext.maven_nexus_snapshots_repo_url : rootProject.ext.maven_nexus_releases_repo_urlallowInsecureProtocol = true// 仓库用户名密码credentials {username = ENV['NEXUS_NAME']password = ENV['NEXUS_PWD']}}}}def publishTask = project.tasks.getByName('publishReleasePublicationToMavenRepository')if (publishTask != null) {publishTask.doLast {println "maven-publish to .repo, Usage: implementation '${groupIdStr}:${artifactIdStr}:${versionStr}'"}}def publishTask2 = project.tasks.getByName('publishReleasePublicationToNexusRepository')if (publishTask2 != null) {publishTask2.doLast {println "maven-publish to nexus, Usage: implementation '${groupIdStr}:${artifactIdStr}:${versionStr}'"}}
}

另外对于gradle task不熟悉的同学可以打开Gradle 视图的 不启用Do not build task list..

注意发布依赖包时,注意模块的之间依赖关系,模块尽可能独立

三、动态依赖

  • 模块下build.gradleadnroid.dependencies{} 中配置当前项目的依赖信息,属于分离式配置的一种
  • 在项目业务复杂的情况下,业务A、B模块依赖关系大体差不多,对于上面的静态依赖,则不灵活,难以复用,故想办法动态构建依赖树

1.依赖的传递性

这里要明白依赖的传递性,依赖关系树的概念,示例如下:

C模块依赖于==> B模块
B模块依赖于==> A模块
由于传递性:C模块同样依赖于==>A模块

2.project/module依赖切换

基本实现示例如下:

if(op){api project(':base_modules:annotation_lib') //
}else{api('cn.mashang.base_modules:annotation_lib:1.0.0-SNAPSHOT') { changing = true }
}

3. 总结与实践

初步分析:

  • 模块的依赖对应关系应该采用map 数据结构构建关系,key 为project,value为上面的project/module依赖切换详情项
  • 依赖的传递性可以采用递归循环
  • 压缩project/module依赖信息为mapprojectmouduledep_option 字段必须

进步分析:

  • 依赖项配置: implementationapicompileOnlyruntimeOnlyannotationProcessordebugImplementation 等,其中api 具有传递性
  • 依赖信息为mapaarall_dep_optionmy_dep_optiondescriptionversiongroup 扩展以上字段
  • 建立初步的ext.modules_dependencies=[] key项目PATH, value依赖项等描述依赖关系,示例如下:
//全局依赖设置,只有project依赖或module依赖方式
ext.all_dep_option = "project" //module/project
ext.all_dep_map = [//依赖详情map"lib_face_detect"    : [   //project:项目PATH, module:maven, aar:aar文件, all_dep_option:全局参数, my_dep_option:单项参数"project"       : ":feature_face:lib_face_detect","module"        : "cn.mashang.feature_face:lib_face_detect:1.0.3","aar"           : "","all_dep_option": all_dep_option,//"my_dep_option" : "module", //打开注释可以单独生效"description"   : "","version"       : "","group"         : "",],//添加更多
]
//添加依赖方式,默认`implementation`
ext.addDep = { String score = "implementation", String key ->println "addDep: $key"Map<String, String> map = new HashMap<>(all_dep_map[key])map.put("score", score)return map
}
//基础依赖
def app_core_map = [//依赖详情mapaddDep("lib_face_detect"),addDep("api_face")addDep("multidex"),addDep("lib_comm_ui"),addDep("lib_arouter"),
]//项目中依赖关系,集中管理
ext.modules_dependencies = [//key项目PATH, value依赖项":app"  : app_core_map, //复用!!!":app1" : app_core_map,//复用!!!":app2" : app_core_map,//复用有点吊!!!":api_face"  : [//依赖详情mapaddDep("commons-net"),]
]
println "modules_dependencies: " + modules_dependencies
//添加应用依赖工具方法并返回执行结果
ext.utils = [applyDependency: { Project p1, Map<String, String> map ->def name = p1.namedef isApplication = p1.pluginManager.hasPlugin("com.android.application")def projectInfo = map.getOrDefault("project", "")def moduleInfo = map.getOrDefault("module", "")def aarInfo = map.getOrDefault("aar", "")def all_dep_option = map.getOrDefault("all_dep_option", "")def my_dep_option = map.getOrDefault("my_dep_option", "")def score = map.getOrDefault("score", "implementation")//println "> applyDependency: ${name} ${isApplication ? "isApp" : ""}, map: " + mapif (projectInfo.isBlank() && moduleInfo.isBlank() && aarInfo.isBlank()) {System.err.println("warning: projectInfo.isBlank() && moduleInfo.isBlank() && aarInfo.isBlank()")return}boolean applyProject = ("project" == my_dep_option) && !projectInfo.isBlank()boolean applyModule = ("module" == my_dep_option) && !moduleInfo.isBlank()boolean applyAAR = ("aar" == my_dep_option) && !aarInfo.isBlank()if (!(applyProject || applyModule || applyAAR)) {applyProject = ("project" == all_dep_option) && !projectInfo.isBlank()applyModule = ("module" == all_dep_option) && !moduleInfo.isBlank()applyAAR = ("aar" == all_dep_option) && !aarInfo.isBlank()}if (applyProject) {Project depProject = p1.findProject(projectInfo)if (depProject == null) {//按需处理,项目没有include时是否采用远程依赖//if (!moduleInfo.isBlank()) {//    p1.dependencies.add(score, moduleInfo, { changing = moduleInfo.contains('cn.mashang') })//    //println "> applyDependency: ${name} ${isApplication ? "isApp" : ""}, score:$score, addModule: " + moduleInfo//    return "$score '$moduleInfo'"//}throw new Exception("请检查 ${projectInfo}")}p1.dependencies.add(score, depProject)//println "> applyDependency: ${name} ${isApplication ? "isApp" : ""}, score:$score, addProject: " + projectInforeturn "$score project('$projectInfo')"} else if (applyModule) {p1.dependencies.add(score, moduleInfo, { changing = moduleInfo.contains('cn.mashang') })//println "> applyDependency: ${name} ${isApplication ? "isApp" : ""}, score:$score, addModule: " + moduleInforeturn "$score '$moduleInfo'"} else if (applyAAR) {ConfigurableFileCollection depProject = p1.files(aarInfo)if (depProject == null) {throw new Exception("请检查 ${aarInfo}")}p1.dependencies.add(score, depProject)//println "> applyDependency: ${name} ${isApplication ? "isApp" : ""}, score:$score, addAar: " + moduleInforeturn "$score files('$aarInfo')"}return ""}
]
  • 最后应用动态依赖, 配置项目阶段可以注入关系

项目build.gradle

project.afterEvaluate { Project p ->//println "> afterEvaluate: " + pdef list = modules_dependencies.get(p.path)if (list == null) {return}def logStr = new StringBuilder(p.name)logStr.append(",配置动态依赖阶段 (可参照输出日志,修改静态依赖)")logStr.append("\n")logStr.append("dependencies {")logStr.append("\n")//动态依赖list.each { e ->def ret = rootProject.ext.utils.applyDependenclogStr.append("     ")logStr.append(ret)logStr.append("\n")}logStr.append("}\n")println logStr
}

Build Project 查看gradle 依赖关系,欧凯

总结:通过上面分析和示例,大体实现了项目的动态依赖,集中管理,能一键切换所有本地模块和远程模块依赖方式(调整all_dep_option参数即可),也能单独切换某一项(调整my_dep_option参数即可),对于一些特殊模块也可以声明aar 参数强制本地依赖包。更多实践取决分析项目需要

四、模块通信

模块化目的是为了降低低耦合,提高独立性。集成模块时,业务间需要进行相互通信(调用),有经验的同学会立马想起路由、事件、接口方式

1.通信方式

  • 路由(是值得推荐的)
  • 事件(Eventbus、广播不值得使用,难追溯)
  • 接口(暴露接口,值得推荐)如微信 Android 模块化架构重构实践(上)暴露api

2.路由方式

对于新项目必须引用路由框架如ARouter、WMRouter、DRouter,必须用

Android-模块化-项目实践和探索分享相关推荐

  1. Android网络收音机项目(源码实例分享)

    最近喜欢听广播,但是搜索了一下,苦于网上没有Android的网络收音机项目的例子,于是自己动手实现了Android网络收音机项目. 前言,由于很多网络广播使用的协议是mms,来自微软,但是androi ...

  2. 网易 Android 游戏保护实践

    内容来源:2018 年 09 月 08 日,网易资深安全工程师张本梁在"RTC2018 实时互联网大会"进行<网易 Android 游戏保护实践>演讲分享.IT 大咖说 ...

  3. 经验分享:如何通过项目实践提升编程能力?

    本文经授权转载自微信公众号"crossin编程教室"(crossincode) 挑选编程实践题目的过程中,有些普遍被关注的问题,今天在这里集中聊一聊,包括我这些年指导学生的一点经验 ...

  4. DockOne微信分享( 九十):猎豹移动基于CoreOS在AWS上的项目实践

    本文讲的是DockOne微信分享( 九十):猎豹移动基于CoreOS在AWS上的项目实践[编者的话]本次分享介绍基于AWS的EC2服务如何设计和搭建适合自己业务的架构方案实现全球多region部署,介 ...

  5. android模块化 osgi,蚂蚁金融级移动应用 osgi 模块化架构实践.pdf

    蚂蚁金融级移动应用 osgi 模块化架构实践 To p 1 0 0 C a s e S t u d i e s O f T h e Ye a r s - Android To p 1 0 0 C a ...

  6. 上百个Android开源项目分享

    转载地址:[http://blog.csdn.net/bboyfeiyu/article/details/12234163] 上百个Android开源项目分享,希望对android开发有帮助. And ...

  7. DockOne微信分享(八十一):唯品会数据库备份恢复容器化项目实践经验总结

    本文讲的是DockOne微信分享(八十一):唯品会数据库备份恢复容器化项目实践经验总结[编者的话]本文分享了唯品会数据库Docker的异地容灾项目实践经验,项目中针对用户数据库的异地恢复场景的需求进行 ...

  8. Android项目总结之社会化分享

    Android项目总结之社会化分享 随着现在社交网络的日益繁多,众多的社交客户端已占据了人们的大量时间,所以在我们的应用中具有一键分享的功能对提高我们产品的知名度有很大的帮助.新浪微博.腾讯微博.腾讯 ...

  9. [转]Android开源项目收藏分享

    转自:http://blog.csdn.net/dianyueneo/article/details/40683285 Android开源项目分类汇总 如果你也对开源实现库的实现原理感兴趣,欢迎 St ...

最新文章

  1. 10个重要问题概览Transformer全部内容
  2. php手绘功能,基于纯CSS3的6种手绘涂鸦按钮效果
  3. 中山大学新华学院c语言试题,中山大学新华学院国际学院2019级学生入学测试
  4. 关于标签系统的又一点想法。
  5. 导致UPS蓄电池损坏的原因有哪些?
  6. SQL Server 数据库巡检脚本
  7. ubuntu创建wifi热点plasma-nm
  8. OPA 22 - sinor fake xml http request
  9. apache mediawiki 安装_如何在CentOS 7上安装MediaWiki
  10. Python之模块与包(下)
  11. CCF 201312-4 有趣的数
  12. 深度相机---(1)TOF总结
  13. 华为Mate 30 Pro前面板谍照曝光:继续刘海屏 左右侧边曲率惊人
  14. 时间选择插件ClockPicker
  15. time+dd测试硬盘读写速度
  16. 免费开源平台 CESIUM GIS、Worldwind、skyline、mapgis、mapinfo、ARCGIS、OSGEARTH、UNIGINE、unity3d、ossimplant
  17. web开发工程师面试题,CSS盒子模型居中方法
  18. Java处理时间,得到指定几天前的凌晨0点时间戳
  19. iOS 清理缓存方法
  20. linux数字版权管理,数字版权管理系统 DRM

热门文章

  1. html5 添加class,点击添加class 接着编写添加class的方法
  2. 数字系统实验—第13周任务(3位数码管动态扫描显示实验含工程与优化)
  3. android+pay进入中国,谷歌Android Pay登陆香港:何时进内地未知
  4. luogu 3413 SAC#1 - 萌数
  5. mysql如何高效批量插入数据
  6. Android应用--简、美音乐播放器实现专辑倒影效果
  7. 安卓系统微信可以修改提示音了,操作简单又实用
  8. 数据美化 | 超Cute 的 Python 手绘图形库!
  9. 深度学习入门demo mnist
  10. android动画水波纹外扩,Android实现水波纹外扩效果的实例代码