在 Android 应用开发中,面对多个定制客户版本的需求时,频繁修改包名、资源、Application 类等内容往往十分低效且易出错。为了解决这一痛点,本文介绍一种清晰、灵活的多客户模块化架构设计,能够有效应对不同客户的定制需求,同时保持代码结构清晰、可维护。
思路:
将主项目作为一个壳,配置包名以及需要Application需要配置的属性,具体实现全部放在业务module中,这样就做到类包名和具体实现的解耦,项目需要频繁改包名的时候就不需要动业务层逻辑了.
1:项目结构
project-root/
├── app/ # 主壳工程 只配置属性无业务逻辑
├── base/ # 公共工具模块
├── business/ # 通用业务模块(Activity、ViewModel 等)
├── clientA/ # 客户 A 定制模块
├── clientB/ # 客户 B 定制模块
2: 主项目配置(app)
主项目作为App启动的模块需要配置签名/启动类/网络/provider的适配,只需要处理bulid.gradle和AndroidManifest.xml
2.1 bulid.gradle配置
plugins {
//application
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
//kapt 我这里用的kapt apt 以及 ksp 自己适配
alias(libs.plugins.jetbrains.kotlin.kapt)
}
android {
namespace 'com.xxx.xx'
compileSdk libs.versions.compileSdk.get().toInteger()
defaultConfig {
applicationId 'com.xxx.xx'
minSdk libs.versions.minSdk.get().toInteger()
targetSdk libs.versions.targetSdk.get().toInteger()
versionCode libs.versions.versionCode.get().toInteger()
versionName libs.versions.versionName.get().toString()
multiDexEnabled true
vectorDrawables {
useSupportLibrary true
}
renderscriptSupportModeEnabled true
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
signingConfigs {
jks {
storeFile file(StoreFile)
storePassword StorePassword
keyAlias KeyAlias
keyPassword KeyPassword
}
}
//配置签名
buildTypes {
debug {
// 混淆开关
minifyEnabled false
debuggable true
signingConfig signingConfigs.jks
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
// 混淆开关
minifyEnabled false
// 移除无用的resource文件
shrinkResources false
debuggable false
signingConfig signingConfigs.jks
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
//配置 打包名字
android.applicationVariants.configureEach {
variant ->
variant.outputs.configureEach {
outputFileName = "Voice_version-"+libs.versions.versionName.get()+".apk"
}
}
}
dependencies {
// 配置 App 的实现module
implementation project(':app_a')
implementation project(':app_b')
}
2.2 AndroidManifest.xml配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name="xxx.xxx"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:networkSecurityConfig="@xml/network_security_config"
android:preserveLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:hardwareAccelerated="true"
android:supportsRtl="true"
android:theme="@style/Theme.Voice"
android:usesCleartextTraffic="true"
tools:replace="android:name,android:allowBackup"
tools:targetApi="31">
//配置 provider 适配文件读取
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
//配置 启动页面
<activity
android:name="xxx.WelcomeActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3. module配置
3.1 bulid.gradle配置
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.kotlin.kapt)
}
android {
namespace 'com.qianrun.voice'
compileSdk libs.versions.compileSdk.get().toInteger()
defaultConfig {
minSdk libs.versions.minSdk.get().toInteger()
}
buildTypes {
debug {
debuggable true
}
release {
debuggable false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
buildFeatures {
viewBinding true
buildConfig = true
}
}
dependencies {
testImplementation libs.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
implementation libs.androidx.core.ktx
implementation libs.androidx.lifecycle.runtime.ktx
implementation libs.androidx.appcompat
implementation libs.com.google.android.material
implementation libs.androidx.recyclerview
implementation libs.androidx.constraintlayout
implementation project(':lib_net')
implementation project(':lib_common')
implementation project(':lib_util')
implementation project(':lib_live')
}
2.2 AndroidManifest.xml配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
// 权限
...
<application
tools:replace="android:name"
>
//页面
<activity
android:name="com.qianrun.voice.MainActivity"
android:exported="true"
android:configChanges="uiMode|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTop" />
<activity android:name="com.qianrun.voice.ui.activity.RegisterInformationActivity" />
<activity android:name="com.qianrun.voice.ui.activity.PasswordAndRegisterActivity" />
<activity android:name="com.qianrun.voice.ui.activity.VerificationCodeActivity" />
<activity android:name="com.qianrun.voice.ui.activity.LoginActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicTitleActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicVbActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicVmActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicVbVmActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicTitleVbActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicTitleVmActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicTitleVbVmActivity" />
<activity android:name="com.qianrun.voice.test2.TestBasicFragmentActivity" />
<activity android:name="com.qianrun.voice.ui.activity.SearchActivity" />
<activity android:name="com.qianrun.voice.ui.activity.UserInfoActivity"
/>
<activity android:name="com.qianrun.voice.ui.activity.FFFActivity" />
<activity android:name="com.qianrun.voice.ui.activity.NoblePrivilegeActivity" />
<activity android:name="com.qianrun.voice.ui.youth.YouthModeActivity" />
<activity android:name="com.qianrun.voice.ui.ranking.RankingActivity" />
<activity android:name="com.qianrun.voice.ui.activity.PublishDynamicsActivity" />
</application>
</manifest>
总结
本文提出了一种适用于多客户场景的 Android 模块化架构设计方案。通过将主项目设计为“空壳”,业务逻辑与客户定制拆分为独立模块,并结合 Gradle 的 flavor、namespace 和 manifestPlaceholders 等机制,实现了如下优势:
- 客户版本切换无需手动改代码,构建高效可靠
- 客户资源、包名、Application 类等完全隔离,避免冲突
- 支持自动打包、CI/CD 集成,适合规模化运营
- 架构清晰,便于团队协作与后期维护
这种架构非常适用于有多个定制客户的 Android 应用项目,可显著提升开发效率与系统稳定性。