效果图
-
非正式包每个模块独立运行
-
正式包每个模块合并到主分支
点击视频浏览是可以正常跳转的
这里跳转到发布适配详情页面
一、项目概述
本文介绍如何从零开始搭建一个 Android 组件化/模块化工程,实现业务模块独立开发调试与整体打包发布的灵活切换。
核心思路
┌──────────────────────────────────────────────────────────┐
│ app (壳工程) │
│ ┌─────────┬─────────┬─────────┬─────────┬─────────┐ │
│ │app- │app- │app- │app- │app- │ │
│ │account │publish │video │store │setting │ │
│ └────┬────┴────┬────┴────┬────┴────┬────┴────┬────┘ │
│ └─────────┴─────────┼─────────┴─────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ app-base │ │
│ │ (基础组件层) │ │
│ └───────┬───────┘ │
│ ┌───────┬───────┬───┴───┬───────┬───────┐ │
│ │lib- │lib- │lib- │lib- │lib- │ │
│ │data │logging│perms │img │util │ │
│ └───────┴───────┴───────┴───────┴───────┘ │
│ (功能库层) │
└──────────────────────────────────────────────────────────┘
关键特性:
- 业务模块可独立运行调试,也可合并打包发布
- 通过
isRelease开关一键切换模式 - 模块间通过反射中介解耦通信
- 统一管理依赖版本,避免版本冲突
二、模块层级划分
2.1 模块分类
| 模块类型 | 命名规范 | 说明 | 示例 |
|---|---|---|---|
| 壳工程 | app | 最终打包入口,聚合所有业务模块 | app |
| 业务模块 | app-xxx | 可独立运行的业务模块 | app-account, app-video |
| 基础组件 | app-base | 所有模块共用的基础类 | app-base |
| 功能库 | lib-xxx | 纯功能库,不含业务 | lib-data, lib-util |
2.2 依赖关系原则
1. 上层模块可以依赖下层模块
2. 同层模块之间禁止直接依赖
3. 业务模块之间通过 ModuleMediator 间接通信
三、搭建步骤
3.1 创建基础工程结构
Step 1: 创建根项目
# settings.gradle
rootProject.name = "modulefy_architecture"
include ':app'
include ':app-base'
include ':app-account'
include ':app-setting'
include ':app-publish'
include ':app-video'
include ':app-store'
include ':lib-data'
include ':lib-logging'
include ':lib-permissions'
include ':lib-imgloader'
include ':lib-util'
Step 2: 配置根 build.gradle
// build.gradle (Project)
plugins {
id 'com.android.application' version '7.2.1' apply false
id 'com.android.library' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
// 引入全局环境变量
apply from: 'env.gradle'
task clean(type: Delete) {
delete rootProject.buildDir
}
3.2 全局环境变量配置 (env.gradle)
核心作用:
- 定义
isRelease开关控制打包模式 - 统一管理所有依赖版本
// env.gradle
ext {
// ★ 核心开关:true=合并打包,false=独立调试
isRelease = true
// SDK 版本统一配置
buildVersion = [
compileSdk: 32,
minSdk : 21,
targetSdk : 30
]
// AndroidX 官方库版本
androidx = [
ktx : "androidx.core:core-ktx:1.7.0",
appcompat : "androidx.appcompat:appcompat:1.3.1",
material : "com.google.android.material:material:1.4.0",
constraintlayout: "androidx.constraintlayout:constraintlayout:2.0.4",
// ... 其他依赖
]
// 第三方库版本
depends = [
okhttp : "com.squareup.okhttp3:okhttp:3.14.9",
retrofit2 : "com.squareup.retrofit2:retrofit:2.9.0",
gson : "com.google.code.gson:gson:2.8.7",
glide : "com.github.bumptech.glide:glide:4.12.0",
arouter : "com.alibaba:arouter-api:1.5.2",
// ... 其他依赖
]
// 测试库
test = [
junit : "junit:junit:4.13.2",
junit_android: "androidx.test.ext:junit:1.1.3",
espresso : "androidx.test.espresso:espresso-core:3.4.0"
]
}
使用方式:
// 在任意模块中引用
dependencies {
implementation androidx.appcompat
implementation depends.retrofit2
}
3.3 业务模块通用配置 (app.gradle)
核心设计:动态切换 application/library 类型
// app.gradle
apply {
if (isRelease) {
// 发布模式:作为 library 被主模块依赖
plugins.apply('com.android.library')
} else {
// 调试模式:作为独立 application 运行
plugins.apply('com.android.application')
}
plugins.apply('org.jetbrains.kotlin.android')
}
android {
compileSdk buildVersion.compileSdk
defaultConfig {
minSdk buildVersion.minSdk
targetSdk buildVersion.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// ★ 关键:根据模式切换 Manifest 文件
sourceSets {
main {
if (isRelease) {
// 发布模式使用精简 Manifest
manifest.srcFile 'src/main/AndroidManifest.xml'
// 排除调试代码
java.exclude '**/debug/**'
} else {
// 调试模式使用带启动入口的 Manifest
manifest.srcFile 'src/main/AndroidManifestDebug.xml'
}
}
}
buildFeatures {
viewBinding true
}
// ... 其他配置
}
dependencies {
// 业务模块统一依赖基础层
implementation project(':app-base')
implementation project(':lib-data')
}
3.4 双 Manifest 配置策略
问题背景:
- 独立调试时需要
LAUNCHER入口 - 合并打包时只能有一个启动入口
解决方案:为业务模块准备两份 Manifest
AndroidManifest.xml (发布模式)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.jeffery.account">
<application>
<!-- 无启动入口,仅注册组件 -->
<activity
android:name=".AccountActivity"
android:exported="false" />
</application>
</manifest>
AndroidManifestDebug.xml (调试模式)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.jeffery.account">
<application android:theme="@style/MyTheme">
<!-- 带启动入口,可独立运行 -->
<activity
android:name=".AccountActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
3.5 业务模块配置示例
// app-account/build.gradle
apply from: '../app.gradle' // 引入通用配置
android {
// ★ 资源前缀,防止资源名冲突
resourcePrefix 'account_'
defaultConfig {
if (!isRelease) {
// 调试模式才需要 applicationId
applicationId 'com.github.jeffery.account'
versionCode 1
versionName '1.0'
}
}
}
dependencies {
// 模块特有依赖
}
资源前缀说明:
- 每个模块设置独立前缀 (
account_,video_,store_等) - 防止多模块合并时资源文件名冲突
- IDE 会提示不符合前缀的资源命名
3.6 壳工程配置
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk buildVersion.compileSdk
defaultConfig {
applicationId "com.github.jeffery"
minSdk buildVersion.minSdk
targetSdk buildVersion.targetSdk
versionCode 1
versionName "1.0"
}
// ... 其他配置
}
dependencies {
// 始终依赖基础层
implementation project(':app-base')
// ★ 仅发布模式依赖业务模块
if (isRelease) {
implementation project(':app-account')
implementation project(':app-publish')
implementation project(':app-video')
implementation project(':app-store')
implementation project(':app-setting')
}
}
四、模块间通信
4.1 问题分析
┌─────────────┐ ┌─────────────┐
│ app-account │ ──×──→ │ app-setting │
└─────────────┘ └─────────────┘
↑ ↑
│ 同层模块不能 │
│ 直接相互依赖 │
└───────────────────────┘
限制原因:
- 同层模块相互依赖会造成循环依赖
- 独立调试时,其他业务模块可能不存在
4.2 解决方案:ModuleMediator 中介模式
// app-base 模块中定义
object ModuleMediator {
// 定义各模块 Activity 的全限定类名
const val ACTIVITY_ACCOUNT_CLASS = "com.github.jeffery.account.AccountActivity"
const val ACTIVITY_SETTING_CLASS = "com.github.jeffery.setting.SettingActivity"
const val ACTIVITY_PUBLISH_CLASS = "com.github.jeffery.publish.PublishActivity"
const val ACTIVITY_VIDEO_CLASS = "com.github.jeffery.video.VideoActivity"
const val ACTIVITY_STORE_CLASS = "com.github.jeffery.store.StoreActivity"
// 各模块 Application 初始化类
private const val APP_ACCOUNT_CLASS = "com.github.jeffery.account.App"
private const val APP_SETTING_CLASS = "com.github.jeffery.setting.App"
// ...
// 模块初始化接口
interface AppInitial {
fun init(app: Application)
}
// 反射初始化所有模块
fun init(app: Application) {
val appClasses = arrayOf(
APP_ACCOUNT_CLASS, APP_SETTING_CLASS,
APP_PUBLISH_CLASS, APP_VIDEO_CLASS, APP_STORE_CLASS
)
for (claName in appClasses) {
try {
val clz = Class.forName(claName)
val obj = clz.newInstance() as AppInitial
obj.init(app)
} catch (e: ClassNotFoundException) {
// 模块不存在时忽略(独立调试场景)
e.printStackTrace()
}
}
}
}
4.3 BaseActivity 封装跨模块跳转
open class BaseActivity : AppCompatActivity() {
// 通过类名字符串启动 Activity(反射方式)
protected fun startActivity(className: String, bundle: Bundle? = null) {
try {
val clazz = Class.forName(className)
val intent = Intent(this, clazz)
bundle?.let { intent.putExtras(it) }
startActivity(intent)
} catch (e: ClassNotFoundException) {
// 目标模块不存在时友好提示
Toast.makeText(this, "模块未安装", Toast.LENGTH_SHORT).show()
}
}
}
4.4 使用示例
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
// 跳转到账户模块
binding.btnAccount.setOnClickListener {
startActivity(ModuleMediator.ACTIVITY_ACCOUNT_CLASS)
}
// 跳转到设置模块
binding.btnSetting.setOnClickListener {
startActivity(ModuleMediator.ACTIVITY_SETTING_CLASS)
}
}
}
五、基础层设计 (app-base)
5.1 职责定义
app-base
├── BaseActivity.kt # Activity 基类
├── BaseFragment.kt # Fragment 基类
├── BaseViewModel.kt # ViewModel 基类
├── BaseApplication.kt # Application 基类
└── ModuleMediator.kt # 模块通信中介
5.2 依赖配置
// app-base/build.gradle
plugins {
id 'com.android.library' // 始终是 library
id 'org.jetbrains.kotlin.android'
}
dependencies {
// 使用 api 传递依赖给上层模块
api androidx.ktx
api androidx.appcompat
api androidx.material
// 测试依赖
testApi test.junit
androidTestApi test.junit_android
androidTestApi test.espresso
}
api vs implementation:
api:依赖会传递给依赖本模块的模块implementation:依赖不传递,仅本模块可用
六、功能库层设计 (lib-xxx)
6.1 lib-data 示例(网络/数据层)
// lib-data/build.gradle
dependencies {
// 网络请求
api depends.okhttp
api depends.okhttp_logging_interceptor
api depends.retrofit2
api depends.retrofit2_converter_gson
api depends.retrofit2_adapter_rxjava
// 数据处理
api depends.gson
api depends.rxjava
api depends.rxandroid
}
6.2 功能库设计原则
| 库名 | 职责 | 对外暴露 |
|---|---|---|
| lib-data | 网络请求、数据存储 | Retrofit Service、Repository |
| lib-logging | 日志打印 | Logger 工具类 |
| lib-permissions | 权限申请 | 权限检查/申请方法 |
| lib-imgloader | 图片加载 | 加载图片的扩展函数 |
| lib-util | 通用工具 | 各类工具方法 |
七、模式切换与调试
7.1 切换打包模式
// env.gradle
ext {
isRelease = true // true: 合并打包 | false: 独立调试
}
7.2 独立调试业务模块
- 设置
isRelease = false - Sync Gradle
- 在 Run Configurations 中选择要调试的模块(如
app-account) - 直接运行该模块
7.3 合并打包发布
- 设置
isRelease = true - Sync Gradle
- 运行
app模块即可打包完整应用
八、进阶优化建议
8.1 使用 ARouter 替代反射
// 添加依赖
implementation depends.arouter
kapt depends.arouter_compiler
// 定义路由
@Route(path = "/account/main")
class AccountActivity : BaseActivity()
// 跳转
ARouter.getInstance().build("/account/main").navigation()
优势:
- 编译期检查路由是否存在
- 支持参数自动注入
- 支持拦截器
8.2 接口下沉
将模块对外暴露的服务接口定义在 app-base,实现在各业务模块:
// app-base 定义接口
interface IAccountService {
fun getUserInfo(): UserInfo?
fun isLoggedIn(): Boolean
}
// app-account 实现
class AccountServiceImpl : IAccountService {
override fun getUserInfo() = ...
override fun isLoggedIn() = ...
}
8.3 组件化配置自动化
可使用 Gradle 插件自动配置,减少重复代码。
九、总结
核心要点回顾
- 模块分层:壳工程 → 业务模块 → 基础组件 → 功能库
- isRelease 开关:一键切换独立调试/合并打包
- 双 Manifest:解决启动入口冲突问题
- ModuleMediator:反射实现模块间解耦通信
- 资源前缀:防止多模块资源名冲突
- env.gradle:统一管理依赖版本
项目结构速览
android-multi-module-demo/
├── app/ # 壳工程
├── app-base/ # 基础组件层
├── app-account/ # 业务模块:账户
├── app-setting/ # 业务模块:设置
├── app-publish/ # 业务模块:发布
├── app-video/ # 业务模块:视频
├── app-store/ # 业务模块:商店
├── lib-data/ # 功能库:网络/数据
├── lib-logging/ # 功能库:日志
├── lib-permissions/ # 功能库:权限
├── lib-imgloader/ # 功能库:图片加载
├── lib-util/ # 功能库:工具类
├── build.gradle # 根构建脚本
├── env.gradle # 全局环境变量
├── app.gradle # 业务模块通用配置
└── settings.gradle # 模块注册
通过以上架构,可以实现:
- 多人并行开发不同业务模块
- 单模块独立编译运行,加快开发效率
- 一键合并打包,统一发布