@TOC
前言
提示:這里需要提前對Android-模塊化-基本知識了解
本文主要分享個人在項目中實現(xiàn)Android模塊化中的gradle統(tǒng)一配置字旭、nexus肘习、maven-publish芳绩、動態(tài)依賴缤弦、模塊通信等思路
一年叮、gradle統(tǒng)一配置
1. 多模塊項目的構(gòu)建
settings.gradle
是根模塊項目以及模塊描述文件砂轻,include '模塊路徑(分隔符是冒號)'
或如下別名引入
include 'VScreen_App' //不建議有冒號
project(":VScreen_App").projectDir = file("VScreen") //指定真實模塊路徑
include 子模塊技巧
胃珍,如下
def sub_father = ':' //子項目父工程名, 更為了能Find Usages
//基礎(chǔ)組件庫
sub_father = ':--base_modules'
include '',
"$sub_father:lib_arouter", //阿里路由
"$sub_father:lib_baseAndroid", //安卓基礎(chǔ)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",
''
業(yè)務(wù)模塊過多辰晕,include 業(yè)務(wù)模塊技巧
蛤迎,約定在指定目錄如下
//業(yè)務(wù)模塊
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}'")
}
}
//業(yè)務(wù)模塊動態(tài)添加 (考慮的業(yè)務(wù)模塊有很多)
def business_modules_dynamically_add = true
if (business_modules_dynamically_add) {
//動態(tài)添加目錄底下所有
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 業(yè)務(wù)模塊 : ${business_modules_symbol}"
老項目工程龐大臃腫,一時無法分離含友。
一般我們會把這個app工程轉(zhuǎn)化為核心庫(下沉給其它工程依賴使用)替裆,添加新的殼工程校辩。我們能不能做到不需要空殼app ?答案是肯定的
build.gradle
描述子模塊的項目的插件辆童、屬性宜咒、依賴等“鸭可以在settings.gradle
中自定義腳本文件名
project(":VScreen").buildFileName = "lib_core.gradle" //改變腳本一個工程打兩份工,實測ojbk
Gradle Event Log 提示重復(fù)工程故黑,不友好,但是能節(jié)省了一個無意義的殼庭砍。
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 命令時, 默認(rèn)情況下總是會構(gòu)建當(dāng)前目錄下的文件 build.gradle
可以添加-b 參數(shù)
或 -p 參數(shù)
gradle xxxTask -b lib_core.gradle
gradle xxxTask -p 所在目錄
2. 根項目的構(gòu)建配置
根項目下build.gradle 描述根模塊的項目的插件场晶、屬性、依賴等怠缸。
大家最熟悉的buildscript
,里面也一般配置大家熟悉的repositories
dependencies
屬性
buildscript {
ext.gradle_tools_version = '7.0.4' //可定義全局屬性和函數(shù)
repositories {}
dependencies {}
}
另外allprojects
的下配置repositories
是不是也熟悉诗轻,這是配置此項目及其每個子項目屬性。因此這里可以很靈活地配置項目所需屬性揭北。如統(tǒng)一編譯配置扳炬、動態(tài)依賴
//配置此項目及其每個子項目。
allprojects { //此方法針對該項目及其子項目執(zhí)行給定的閉包搔体。目標(biāo)Project作為閉包的委托傳遞給閉包恨樟。
//配置此項目的存儲庫
repositories {
google()
maven { url "https://jitpack.io" } //也可以使用nexus,下文會說到
}
configurations.all { //目前只發(fā)現(xiàn)這里處理依賴相關(guān)的配置 [官網(wǎng)文檔說明](https://docs.gradle.org/current/userguide/resolution_rules.html)
//每隔24小時檢查遠(yuǎn)程依賴是否存在更新
resolutionStrategy.cacheChangingModulesFor 24, 'hours'
//每隔10分鐘..
//resolutionStrategy.cacheChangingModulesFor 10, 'minutes'
// 采用動態(tài)版本聲明的依賴緩存10分鐘
resolutionStrategy.cacheDynamicVersionsFor 10 * 60, 'seconds'
resolutionStrategy.dependencySubstitution {
//project&module依賴關(guān)系切換處理 方式1
substitute(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 //默認(rèn)為true,一般不會這樣設(shè)疚俱,還可以指定Force劝术、exclude等配置
}
//verbose javac: 開啟java 編譯log
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint" << "-verbose" << "-XprintRounds" << "-XprintProcessorInfo" << "-Xmaxerrs" << "2000"
}
}
//:app 添加在評估此項目后立即調(diào)用的閉包。項目作為參數(shù)傳遞給閉包计螺。當(dāng)屬于該項目的構(gòu)建文件已執(zhí)行時夯尽,此類偵聽器會收到通知。例如登馒,父項目可以將這樣的監(jiān)聽器添加到其子項目匙握。這樣的偵聽器可以在它們的構(gòu)建文件運行后根據(jù)子項目的狀態(tài)進(jìn)一步配置這些子項目。
project.afterEvaluate { Project p ->
if (p.plugins.hasPlugin('com.android.application') || p.plugins.hasPlugin('com.android.library')) {
android {
compileSdkVersion 32
defaultConfig {
minSdkVersion 21 (默認(rèn))
targetSdkVersion 32
//構(gòu)建project版本信息,此處能讀取配置后的版本信息
if (buildFeatures.buildConfig) {
buildConfigField(intType, "BUILD_CODE", "${versionCode}")
buildConfigField(str, "BUILD_VERSION_NAME", "\"${versionName}\"")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
}
}
}
3. 常用公用的構(gòu)建配置
一般我們會定義一些config.gradle和config.properties
配置文件,引用這些文件達(dá)到復(fù)用公用的配置信息陈轿。一般引用方式代碼如下(示例):
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
更方便索引和維護(hù)以及覆蓋屬性 代碼如下(示例):
#要用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切換開關(guān), 集成相應(yīng)模塊:默認(rèn)true
libModulesIsLib=true
如果是新項目麦射,推薦buildSrc配置信息
baseAndroid.gradle
定義通用的配置, 更方便索引和維護(hù) 代碼如下(示例):
/**
* 作用描述:
* Base-Android build file where you can add configuration options common to all sub-projects/modules.
* Base - Android構(gòu)建文件,您可以添加配置選項常見的所有子項目/模塊蛾娶。
*/
//打印日志
println rootProject.file('./buildConfig/baseAndroid.gradle').getAbsolutePath()
//當(dāng)前模塊信息
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 動態(tài)切換
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") //打印日志
//獲取構(gòu)建時間
long getBuildTime() {
def calendar = Calendar.getInstance()
if (!isRelease) { //編譯優(yōu)化策略
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) { //組件化切換調(diào)試常見方案
plugins.apply("com.android.library")
println 'apply lib'
} else {
hasAppPlugin = true
plugins.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 實現(xiàn)自動注冊
println 'apply arouter '
}
android {
compileSdk compileSdkVersionVar
//resourcePrefix "submodule_customization_todo" //子模塊定制待辦事項
defaultConfig {
multiDexEnabled true
minSdk minSdkVersionVar
targetSdk targetSdkVersionVar
//版本信息默認(rèn)
versionCode 1
versionName "1.0.0"
//資源配置
resConfigs "en", "zh"
//ndk配置
ndk {
//設(shè)置支持的so庫框架
abiFilters 'armeabi-v7a'
}
//阿里路由框架啟用, 像UI類庫不需要路由增加編譯壓力
if (hasLibARouter) {
// 阿里路由框架注解配置, 每個模塊需要依賴
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: projectName]
}
}
}
if (buildFeatures.buildConfig) {
buildConfigField("boolean", "IS_APPLICATION", "${hasAppPlugin}")
//構(gòu)建時間
buildConfigField(longType, BUILD_COMPUTER_TIME, "${myBuildTime}")
//構(gòu)建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 true
enableV2Signing true
//通過 APK v4 簽名,您可以使用 Android 11 中的 ADB 增量 APK 安裝快速部署大型 APK潜秋。此新標(biāo)志負(fù)責(zé)部署過程中的 APK 簽名步驟蛔琅。
enableV3Signing true
enableV4Signing true
}
}
buildTypes {
debug {
zipAlignEnabled true
minifyEnabled false
signingConfig signingConfigs.keystore
//獨立調(diào)試
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 APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
//lint配置
lintOptions {
//不檢查release版本的構(gòu)建
checkReleaseBuilds false
//停用 出現(xiàn)錯誤時停止編譯
abortOnError false
}
lintOptions {
checkDependencies true
}
//打包配置
packagingOptions {
merge "/arouter/config.properties"
}
//dex配置
dexOptions {
javaMaxHeapSize "4g"
//是否支持大工程模式
jumboMode = true
//預(yù)編譯
preDexLibraries = true
//線程數(shù)
threadCount = 8
maxProcessCount = 8 // this is the default value 4 //根據(jù)CPU核心設(shè)置
//設(shè)置是否啟用dx增量模式 debug時,開啟有加速效果
incremental true
//是將 dx 編譯器作為單獨的進(jìn)程運行還是在 Gradle 守護(hù)進(jìn)程 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 //自動依懶關(guān)閉 (默認(rèn)開啟)
def autoBasicLibDependencies = ext.find("auto_basiclib_dependencies") == null //自動依懶基本庫開啟 (默認(rèn)開啟)
//公共依賴
dependencies {
//api fileTree(include: ['*.jar'], dir: 'libs') //確保libs 都是要加入才開啟注釋
if (autoDependencies) { //自動依懶關(guān)閉
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 = it
if (!itemName.contains(projectName)) {
println("baseAndroid.gradle:basicLibProject:auto: " + projectName + " <<== " + itemName)
implementation project(itemName)
}
}
//本地lib Nexus
if ("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') { // 阿里路由框架api
exclude 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實現(xiàn)關(guān)鍵:要根據(jù)開關(guān)配置應(yīng)用的application或library插件
關(guān)鍵代碼如下:
if (!hasAppPlugin) { //如果是非app模塊
if (libModulesIsLib) { //組件化切換調(diào)試常見方案
plugins.apply("com.android.library") //等同于apply plugin: 'com.android.library'
println 'apply lib'
} else {
hasAppPlugin = true
plugins.apply("com.android.application") //apply plugin: 'com.android.application'
println 'apply application'
}
}
base.properties 下libModulesIsLib 控制所有模塊app/lib切換開關(guān)罗售,false所有l(wèi)ib模塊轉(zhuǎn)換為app
# 所有模塊app/lib切換開關(guān), 集成相應(yīng)模塊:默認(rèn)true
libModulesIsLib=true
工程app簡單示例如下:
apply plugin: 'com.android.application'
apply from: rootProject.file('./buildConfig/baseAndroid.gradle')
模塊lib簡單示例如下:
//默認(rèn)應(yīng)用的是com.android.library
apply from: rootProject.file('./buildConfig/baseAndroid.gradle')
真實項目中辜窑,不需要所有模塊都能單獨調(diào)試和運行
所以利用了properties
覆蓋屬性,其實properties
是擴(kuò)展Hashtable
的寨躁,不難想象load
其它配置后相當(dāng)于map.put
def moduleConfig = new File(projectDir, 'debugConfig.properties')
if (moduleConfig.exists()) {
println 'load submodule_customization configs: ' + moduleConfig.getAbsolutePath()
dict.load(new FileInputStream(moduleConfig))
}
每個模塊下創(chuàng)建debugConfig.properties
文件, 放置調(diào)試的配置信息
debugConfig.properties
,配置變動記得在View-Gradle 視圖中reload gradle project
# 單模塊app/lib切換開關(guān), 集成相應(yīng)模塊: false或'',null為app, 默認(rèn)true, 修改后需要 reload gradle project
#libModulesIsLib=false
.gitignore
小技巧穆碎,提交完debugConfig.properties
文件,使用它
/build
./debugConfig.properties
- 公共依賴要注意依賴的合理性和傳遞性
二职恳、nexus與maven-publish
Nexus 最為大家熟知的功能就是 maven 的依賴包管理器所禀。
架設(shè) Nexus 私服有很多優(yōu)點,其中之一就是:
- 方便上傳團(tuán)隊內(nèi)部的依賴放钦,
統(tǒng)一管理色徘,共享
aar
大家最為熟悉,也稱為本地靜態(tài)aar
依賴操禀,對比遠(yuǎn)程倉庫中的依賴包 implementation('com.squareup.retrofit2:retrofit:2.4.0')
贺氓,發(fā)現(xiàn)遠(yuǎn)程倉庫中的依賴包的pom.xml
文件已經(jīng)包括相應(yīng)okhttp的依賴關(guān)系,這里不展開說明床蜘,所以遠(yuǎn)程依賴包的好處如下:
- 不用維護(hù)依賴傳遞
- 代碼不需暴露
- 加快編譯
1.安裝nexus
運行命令
bin/nexus.exe /run
注冊賬號,nexus相關(guān)配置不一一說明
2.倉庫
這里的倉庫是指項目中依賴的第三方庫蔑水,這個庫所在的位置叫做倉庫邢锯。 在Maven 中,任何一個依賴搀别、插件或者項目構(gòu)建的輸出丹擎,都可以稱之為構(gòu)件。
跟項目下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_url
allowInsecureProtocol = true
credentials {
username = "guest"
password = "guest"
}
}
maven {
url maven_nexus_releases_repo_url
allowInsecureProtocol = true
credentials {
username = "guest" //nexus游客歇父,只允許訪問
password = "guest"
}
}
google()
jcenter()
}
3. maven-publish
Android Gradle 插件 3.6.0 及更高版本支持 Maven Publish Gradle 插件
使用 Maven Publish 插件
多數(shù)模塊都需要發(fā)布蒂培,maven-publish.gradle 示例:
/**
* 作用描述: maven版本發(fā)布共享庫管理,依賴maven可大大節(jié)省編譯時間
* 創(chuàng)建人 rentl
* 創(chuàng)建日期 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.srcDirs
classifier '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.release
groupId = "$groupIdStr"
artifactId = "$artifactIdStr"
version = project.android.defaultConfig.versionName
artifact 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_url
allowInsecureProtocol = 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 1
versionName "1.0.0"
//versionName "1.0.1-SNAPSHOT"
consumerProguardFiles "consumer-rules.pro" //lib-proguard
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
if (!hasAppPlugin) { //這里需要放在這里底下,需要獲取版本信息
apply from: rootProject.file('./buildConfig/maven-publish.gradle')
}
maven規(guī)約
這里還是要提及一下要遵maven規(guī)約
, 大家認(rèn)同的詳細(xì)規(guī)定參考下方:
1)GroupID格式:com.{公司/BU }.業(yè)務(wù)線.[子業(yè)務(wù)線]榜苫,最多4級
正例:com.joymef.platform 或 com.joymef.social.blog
2)ArtifactID格式:產(chǎn)品線名-模塊名护戳。語義不重復(fù)不遺漏,先到倉庫中心去查證一下
3)正例:user-service / user-client / blog-service ) Version
4)開發(fā)階段版本號定義為SNAPSHOT,發(fā)布后版本改為RELEASE(強(qiáng)制)
上面是安卓模塊publish垂睬,java的模塊需要稍微調(diào)整
媳荒,maven-jar-publish.gradle示例如下:
/**
* 作用描述: maven jar版本發(fā)布共享庫管理,依賴maven可大大節(jié)省編譯時間
* 組件描述:
* 創(chuàng)建人 rentl
* 創(chuàng)建日期 2022/2/26
* 修改日期 2022/2/26
* 版權(quán) 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_8
targetCompatibility = 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.java
groupId = "$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_url
allowInsecureProtocol = 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不熟悉的同學(xué)可以打開Gradle 視圖的 不啟用Do not build task list..
注意發(fā)布依賴包時,注意模塊的之間依賴關(guān)系驹饺,模塊盡可能獨立
三钳枕、動態(tài)依賴
- 模塊下
build.gradle
在adnroid.dependencies{}
中配置當(dāng)前項目的依賴信息,屬于分離式配置的一種 - 在項目業(yè)務(wù)復(fù)雜的情況下赏壹,業(yè)務(wù)A鱼炒、B模塊依賴關(guān)系大體差不多,對于上面的靜態(tài)依賴蝌借,則不靈活昔瞧,難以復(fù)用指蚁,故想辦法動態(tài)構(gòu)建依賴樹
1.依賴的傳遞性
這里要明白依賴的傳遞性,依賴關(guān)系樹的概念硬爆,示例如下:
C模塊依賴于==> B模塊
B模塊依賴于==> A模塊
由于傳遞性:C模塊同樣依賴于==>A模塊
2.project/module
依賴切換
基本實現(xiàn)示例如下:
if(op){
api project(':base_modules:annotation_lib') //
}else{
api('cn.mashang.base_modules:annotation_lib:1.0.0-SNAPSHOT') { changing = true }
}
3. 總結(jié)與實踐
初步分析:
- 模塊的依賴對應(yīng)關(guān)系應(yīng)該采用
map
數(shù)據(jù)結(jié)構(gòu)構(gòu)建關(guān)系欣舵,key
為project,value
為上面的project/module
依賴切換詳情項 - 依賴的傳遞性可以采用
遞歸循環(huán)
- 壓縮
project/module
依賴信息為map
缀磕,project
缘圈、moudule
、dep_option
字段必須
進(jìn)步分析:
-
依賴項配置:
implementation
袜蚕、api
糟把、compileOnly
、runtimeOnly
牲剃、annotationProcessor
遣疯、debugImplementation
等,其中api
具有傳遞性 - 依賴信息為
map
凿傅,aar
缠犀、all_dep_option
、my_dep_option
聪舒、description
辨液、version
、group
擴(kuò)展以上字段 - 建立初步的
ext.modules_dependencies=[]
key項目PATH, value依賴項等描述依賴關(guān)系箱残,示例如下:
//全局依賴設(shè)置滔迈,只有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:全局參數(shù), my_dep_option:單項參數(shù)
"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" : "",
],
//添加更多
]
//添加依賴方式,默認(rèn)`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
}
//基礎(chǔ)依賴
def app_core_map = [
//依賴詳情map
addDep("lib_face_detect"),
addDep("api_face")
addDep("multidex"),
addDep("lib_comm_ui"),
addDep("lib_arouter"),
]
//項目中依賴關(guān)系被辑,集中管理
ext.modules_dependencies = [
//key項目PATH, value依賴項
":app" : app_core_map, //復(fù)用A呛贰!盼理!
":app1" : app_core_map,//復(fù)用L干健!宏怔!
":app2" : app_core_map,//復(fù)用有點吊9戳ā!举哟!
":api_face" : [
//依賴詳情map
addDep("commons-net"),
]
]
println "modules_dependencies: " + modules_dependencies
//添加應(yīng)用依賴工具方法并返回執(zhí)行結(jié)果
ext.utils = [
applyDependency: { Project p1, Map<String, String> map ->
def name = p1.name
def 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: " + map
if (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時是否采用遠(yuǎn)程依賴
//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: " + projectInfo
return "$score project('$projectInfo')"
} else if (applyModule) {
p1.dependencies.add(score, moduleInfo, { changing = moduleInfo.contains('cn.mashang') })
//println "> applyDependency: ${name} ${isApplication ? "isApp" : ""}, score:$score, addModule: " + moduleInfo
return "$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: " + moduleInfo
return "$score files('$aarInfo')"
}
return ""
}
]
- 最后
應(yīng)用動態(tài)依賴
, 配置項目階段可以注入關(guān)系
項目build.gradle
project.afterEvaluate { Project p ->
//println "> afterEvaluate: " + p
def list = modules_dependencies.get(p.path)
if (list == null) {
return
}
def logStr = new StringBuilder(p.name)
logStr.append(",配置動態(tài)依賴階段 (可參照輸出日志,修改靜態(tài)依賴)")
logStr.append("\n")
logStr.append("dependencies {")
logStr.append("\n")
//動態(tài)依賴
list.each { e ->
def ret = rootProject.ext.utils.applyDependenc
logStr.append(" ")
logStr.append(ret)
logStr.append("\n")
}
logStr.append("}\n")
println logStr
}
Build Project
查看gradle 依賴關(guān)系,歐凱
總結(jié):通過上面分析和示例妨猩,大體實現(xiàn)了項目的動態(tài)依賴潜叛,集中管理,能一鍵切換所有本地模塊和遠(yuǎn)程模塊依賴方式(調(diào)整
all_dep_option
參數(shù)即可),也能單獨切換某一項(調(diào)整my_dep_option
參數(shù)即可)威兜,對于一些特殊模塊也可以聲明aar
參數(shù)強(qiáng)制本地依賴包销斟。更多實踐取決分析項目需要
四、模塊通信
模塊化目的是為了降低低耦合椒舵,提高獨立性蚂踊。集成模塊時,業(yè)務(wù)間需要進(jìn)行相互通信(調(diào)用)笔宿,有經(jīng)驗的同學(xué)會立馬想起路由犁钟、事件、接口方式
1.通信方式
- 路由(是值得推薦的)
- 事件(Eventbus泼橘、廣播不值得使用涝动,難追溯)
- 接口(暴露接口,值得推薦)如微信 Android 模塊化架構(gòu)重構(gòu)實踐(上)暴露api
2.路由方式
對于新項目必須引用路由框架如ARouter炬灭、WMRouter醋粟、DRouter,必須用??考慮一番重归,下面簡單介紹一下
-
ARouter 穩(wěn)定米愿、易上手,但使用IProvider 接口的
Service
是單例鼻吮,且生成代碼增量編譯以及多線程掃描方面一直沒改進(jìn)迭代吗货,不支持SPI
是最大的雞肋,作為一款UI路由倒十分合適 -
WMRouter 易調(diào)試狈网,提供了
ServiceLoader
模塊, 對AGP7.0
插件Transform不友好,并同ARouter
注解生成多數(shù)代碼用的全是反射 - DRouter 功能強(qiáng)大笨腥,不僅支持ServiceLoader拓哺,還支持增量編譯,多線程掃描脖母,提升編譯效率士鸥,較難掌握,類似多維過濾器谆级、跨進(jìn)程烤礁、共享內(nèi)存方面不一定需要使用
3.接口方式
- 微信api化,需要自定義插件肥照,在開始編譯時脚仔,復(fù)制
.api
文件并重命名.java
-
api lib工程
放入基本數(shù)據(jù)結(jié)構(gòu)和接口 (模塊多時維護(hù)頻繁變)
4.總結(jié)與實踐
從上面分析知,模塊化通信解決方案各有優(yōu)劣舆绎,如路由框架的笨重難以維護(hù)鲤脏,微信api化需要維護(hù)
gradle插件
,api文件容易被誤修改等,所以要針對自身項目靈活運用猎醇,如下分析一個項目情況:
- 實踐模塊化項目比較老窥突,已經(jīng)采用了
ARouter
, 不可能再引用路由或者重寫路由框架來解決SPI支持問題
- 實踐模塊化項目剛起步,逐步分離20個模塊硫嘶,模塊之間通信較少
- 項目開啟
proguard
阻问,如果某些類方法沒有KEEP
,會導(dǎo)致反射調(diào)用失敗沦疾,另外項目注重效率称近,盡量避免反射 -
java spi
在Android
上需要讀取整個apk,十分耗時曹鸠,且安卓上是apk煌茬,dex,定義的Service
可能會發(fā)生沖突彻桃,難以定位
通過分析項目情況坛善,需要大概的解決方案
如下:
- 輸出一套簡單實用的
android spi
機(jī)制.export_table
- 輸出一套簡單好維護(hù)的
api化
方案,api_box
總結(jié)
以上就是要講的Android-模塊化-項目實踐和探索分享內(nèi)容邻眷,本文僅僅簡單分享了模塊中的靈活運用一些方法眠屎,如有疑問,請和我聯(lián)系肆饶,交流學(xué)習(xí)改衩。