Android 架构 - 组件化 Modularzation

Groovy 语法迁移到 Kotlin DSL

libs.versions.toml 用法

一、概念

组件化是基于可重用目的,对单个功能进行开发,提升复用性降低耦合度。多个功能组件起来就是一个业务组件,多个业务组件组合起来就是一个应用,因此去除了模块间的耦合,使得按业务划分的模块成了可单独运行的业务组件。(组件化只是一定程度上的独立,还是依附于整个项目中,想完全独立详见插件化)

  • 功能复用:每个组件都是一个单独的功能,可被不同的模块甚至不同的应用所使用。
  • 提高效率:每个组件都是一个可单独编译运行的APP,由于拆分使得代码量少,调试起来快。
  • 协同开发:组件之间相互独立,使得不同的开发人员能专注于各自的功能开发互不影响。

1.1 组件划分

依赖关系是上层依赖下层,修改频率是上层高于下层。

|------------------|------------------------------------------------------------------------------------------------------------|
| app壳 | 应用的入口:只依赖各业务组件,不包含具体业务代码。将业务组件打包成一个APP,做一些配置工作(打包环境、签名、混淆等)。 |
| 业务组件 module_XXX | 某个页面:根据业务拆分的独立模块,如主页模块、登录模块、商城模块。业务组件之间无直接关系,通过路由进行通信。既可以作为 Application 单独编译运行调试,又可作为 Library 集成到项目中。 |
| 功能组件 feature_XXX | 公共功能:是对公用的功能进行封装与实现,如网络请求、日志打印、地图、支付、广告。非必须层,业务组件可以直接依赖基础组件去实现功能。 |
| 基础组件 lib_XXX | 最底层:修改频率极低,统一依赖配置和资源文件:如引入第三方框架(Retrofit、Glide)、存放公用资源文件(strings、drawable、自定义View、工具类Utils)、实现路由(ARouter)。 |

1.2 组件模式

在 Module 的 build.gradle 中指定插件类型,指定为集成模式(Library)可以被其它组件调用,指定为独立模式(Application)可以单独运行调试。

|-------------------------|---------------------------------|
| com.android.application | 项目构建后输出 apk 包,在调试时是一个应用能单独编译运行。 |
| com.android.library | 项目构建后输出 aar 包,在打包时是一个库文件集成到项目中。 |

Kotlin 复制代码
plugins {
    alias(libs.plugins.android.library)
}

1.3 创建组件

File(或在Project上右键)→New→New Module。独立模式(业务组件)选择【Phone & Tablet】,集成模式(功能组件、基础组件)需要用到资源文件选【Android Library】纯代码选【Java or kotlin Library】。

使用目录来聚合同类型组件,使层次结构更清晰。语法是模块取名的时候使用":library:lib_base",这样 lib_base 就在根目录的 library 下了。

二、统一配置

2.1 动态模式切换

在 TOML 的节点 [versions] 中定义属性 isDebug = "false" ,用于后续在业务组件中读取来动态切换组件模式。

Kotlin 复制代码
[versions]
isDebug ="false"

2.2 抽取 build.gradle 中相同的配置

组件中有很多相同的配置,分别抽成【commonMoudle.gradle】【commonLib.gradle】 文件并在对应组件中引入,就可以删除重复的配置了。

  • commonMoudle:提供组件模式的切换,剔除各个 module 组件专有的 namespace、applicationId 属性。
  • commonLib:剔除各个 lib 组件专有的 namespace 属性。
Kotlin 复制代码
1

三、基础组件

所有组件都依赖 lib_base,提供全局的功能。

3.1 创建 lib_base 组件

3.2 提供 BaseApplication

其它组件都依赖于 lib_base,因此在其中提供一个全局的 BaseApplication 供其它组件在代码中使用。所有资源的初始化也在这里。

|-------------------|------------------------------------------------------------------------------------------------------------|
| 其它组件所处的模式 | 引入后如何使用 |
| 集成模式(Library) | 直接使用 BaseApplication,不是应用不需要注册。 |
| 独立模式(Application) | 在 debug 文件夹中创建一个子类继承自 BaseApplication 然后注册到 debug 文件夹中的 AndroidManifest 中(不需要使用自己定义的就注册为 BaseApplication)。 |
| app壳 | 创建一个子类继承自 BaseApplication 然后在 AndroidManifest 中注册(不需要使用自己定义的就注册为 BaseApplication) 。 |

Kotlin 复制代码
open class App : Application() {
    companion object {
        @SuppressLint("StaticFieldLeak")    //注解忽略警告
        lateinit var context: Context
            private set
    }
    override fun onCreate() {
        super.onCreate()
        context = applicationContext    //在APP创建的时候就赋值
    }
}

3.3 提供权限声明 AndroidManifest

声明了项目中用到的所有权限,这样其它组件就无需在自己的 AndroidManifest.xm 声明自己要用到的权限了。

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET"/>

</manifest>

3.4 提供依赖 build.gradle

使用 imlemntation 添加的依赖只对当前组件有效,使用 api 添加的依赖对其上层组件同样有效,这样依赖于基础组件的组件就不用再添加相同的依赖了。

项目中某个组件会被其它组件重复依赖,在集成构建应用时 Gradle 会自动剔除重复的 arr 包,这样就不会存在好几份重复的代码了。而第三方库和我们的项目可能都依赖了相同的库(例如 Glide 中也依赖了 OkHttp)就会导致重复加载,解决办法是找出相同的库根据组件名或包名排除。

|---------------------------|-----------------------------|
| 仅用于当前组件 | 可向上传递(引入这个组件的上层组件不用重复添加依赖项) |
| implementation | api |
| testImplementation | testApi |
| androidTestImplementation | androidTestApi |
| debugImplementation | debugApi |

Kotlin 复制代码
[versions]
agp = "8.13.1"
kotlin = "2.3.0"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.10.0"
composeActivity = "1.12.0"
composeBom = "2025.12.00"

[libraries]
#Compose
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
compose-material3 = { module = "androidx.compose.material3:material3" }
#预览
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
#测试
compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
#可选
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" }

#其它
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
junit = { module = "junit:junit", version.ref = "junit" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
java 复制代码
dependencies {
    //Compose
    //物料清单
    api(platform(libs.compose.bom))
    androidTestApi(platform(libs.compose.bom))
    //主题
    api(libs.compose.material3)
    //预览
    api(libs.compose.ui.tooling.preview)
    debugImplementation(libs.compose.ui.tooling)
    //UI测试
    androidTestImplementation(libs.compose.ui.test.junit4)
    debugImplementation(libs.compose.ui.test.manifest)
    //可选
    api(libs.compose.activity)
    
    //默认
    api(libs.androidx.core.ktx)
    api(libs.androidx.lifecycle.runtime.ktx)
    testApi(libs.junit)
    androidTestApi(libs.androidx.junit)
    androidTestApi(libs.androidx.espresso.core)
}

3.4.1 处理 jar 包 aar 包依赖

一般依赖的都是 Maven 仓库,如果提供的是 aar、jar 文件,第一行添加如下代码引入。

Kotlin 复制代码
dependencies {
    api(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
}

3.5 提供公共资源

自定义的类(Utils、Base、Constants、Ext、Widgets)、res资源目录(drawable、strings)、通用业务(Retrofit)、通用配置等都应该放在这里。

四、功能组件

3.1 创建 feature_common 组件

3.2 引入基础组件 lib_base

Kotlin 复制代码
dependencies {
    api(project(":library:lib_base"))
}

五、业务组件

5.1 创建 module_main 组件

现在都是单 Activity 的应用开发,创建的时候选 NoActivity。

5.2 创建 debug 包

在业务组件根目录中创建 debug 包,用于存放只在独立模式下使用的文件,在集成模式下会被剔除不参与打包。

在业务组件的 src/main/java 目录上右键→New→Package。(不推荐在组件的其它层级上创建目录存放,切换到Android视图不会显示)

5.2.1 自定义 Application

有的业务组件需要被传入数据,或是用到的资源需要初始化,自定义一个 TestApplication 继承 lib_base 中提供的 BaseApplication。如果用不到,就直接把 BaseApplication 注册。

Kotlin 复制代码
class MyApp : App() {}

5.2.2 自定义入口 LauncherActivity

现在都是单 Activity 的应用开发,业务模块在独立模式下需要有 Activity 承载显示,因此 debug 包下创建一个 LauncherActivity。

Kotlin 复制代码
class LauncherActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MsgScreen()
        }
    }
}

5.2.3 两份 AndroidManifest

处于独立模式时,需要为了<application>标签配置、自定义Application 、启动入口 Activity 而在 AndroidManifest 中进行注册,但在集成模式下打包到一起时存在重复和冲突(如桌面上好几个启动图标、只能注册一个自定义的Application、同一个权限无须多次申请)。

  1. 将自动创建的 AndroidManifest 复制一份到 debug 包下,对 <manifest> 标签添加 package 属性指定为组件的包名,将 .MainActivity 改成自定义的启动Activity。然后像单工程那样用就行。
  2. 再修改自动创建的 AndroidManifest,也对 <manifest> 标签添加 package 属性指定为组件的包名。若该业务组件不是程序入口,将 <application> 的属性和子标签都删除,只存放四大组件的注册。

5.3 修改 build.gradle

根据在 TOML 中定义的 isDebug 来动态切换插件模式、设置applicationId、加载AndroidManifest。引入需要功能组件。

5.3.1 动态切换组件模式

Kotlin 复制代码
plugins {
    if (libs.versions.isDebug.get().toBoolean()) {
        alias(libs.plugins.android.application)
    } else {
        alias(libs.plugins.android.library)
    }
}

5.3.2 动态设置 applicationId

这里会提示找不到 applicationId、versionCode、versionName,注释掉能正常执行。20260112

Kotlin 复制代码
android {
    defaultConfig {
        if (libs.versions.isDebug.get().toBoolean()) {
            applicationId = "com.example.module_main"
        }
    }
}

5.3.3 动态加载 AndroidManifest

这里剔除 debug 包下文件有问题,用了AI也找不到相应语法,报错方法不正确。20260112

Kotlin 复制代码
sourceSets {
    named("main") {
        if (isDebug) {
            manifest.srcFile("src/main/java/debug/AndroidManifest.xml")
        } else {
            manifest.srcFile("src/main/AndroidManifest.xml")
        }
    }
}

5.3.4 引入需要的功能模块

Kotlin 复制代码
dependencies {
    implementation(project(":library:lib_base"))
}

六、搬空 app 壳

6.1 搬运程序入口 MainActivity

  1. 将 app 中的 MainActivity.class 和 activity_main.xml(Compose没有这个) 剪切至 module_main 中。
  2. 将 AndroidManifest 中的 MainActivity 注册(包含程序入口)也剪切至 module_core 中只保留 <application> 标签。

6.2 搬运资源目录 res

将 app 中的整个 res 目录剪切至 lib_base 中。

6.3 自定义 Application

需要用到就自定义一个 MyApp 继承 lib_base 中提供的 BaseApplication,用不到就直接把 BaseApplication 注册。

6.4 修改 AndroidManifest

<application>标签的属性都是在app壳配置的,而其它组件也会有自己的清单,在集成模式下最终会打包合并成一个文件,需要解决属性重复(在上面5.4中用于集成模式的AndroidManifest的<application>标签并不会进行配置,但还是会配置theme),分别对 app 的 <manifest> 和 <application> 添加如下代码。

|-----------------|-------------------------------------------------------------------------------------------|
| <manifest> | xmlns:tools="http://schemas.android.com/tools" |
| <application> | tools:replace="android:name,android:label,android:icon,android:theme,android:allowBackup" |

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="com.example.lib_base.App"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.WAModularization"
        tools:replace="android:name,android:label,android:icon,android:theme,android:allowBackup"
        >
    </application>

</manifest>

6.5 修改 build.gradle

应用的打包签名、buildTypes、defaultConfig 都需要在这里配置,而它的 dependencies 则需要根据 isDebug 的值分别依赖不同的组件,在独立模式下app壳只需要依赖 lib_common 组件,在集成模式下必须依赖所有声明的业务组件。

当需要同时引入好几个组件时,可以像"分组"一样一次性引入,避免每次新建组件引入多个其他组件的时候出错。像多渠道打包、测试某几个固定组件协同时使用。

Kotlin 复制代码
dependencies {
    implementation(project(":library:lib_base"))
    if (!libs.versions.isDebug.get().toBoolean()) {
        implementation(project(":module:module_main"))
    }
}
相关推荐
阿巴斯甜1 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker2 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95273 小时前
Andorid Google 登录接入文档
android
黄林晴4 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab16 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿19 小时前
Android MediaPlayer 笔记
android
Jony_20 小时前
Android 启动优化方案
android
阿巴斯甜20 小时前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇20 小时前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android