Android 架构 - 组件化 Modularzation(Kotlin版)

Groovy 语法迁移到 Kotlin DSL

libs.versions.toml 用法

一、概念

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

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

1.1 组件划分

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

对于业务组件由于存在页面跳转、方法调用、事件通信等问题需要使用路由通信,其它层组件不存在耦合问题封装成 Library 即可。

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 包,在打包时是一个库文件集成到项目中。
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" ,用于后续在业务组件中读取来动态切换组件模式(true为组件模式会编译成Application,false为集成模式会编译成Library),同步一下后就可以在每个组件的 build.gradle 中读取并做相应的动态处理。(见下方5.5)

[versions] isDebug ="false"

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

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

2.2.1 抽取

在项目上右键→New→File→命名为“commonModule.gradle.kts”。

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

2.2.2 引入

在每个组件的 build.gradle 中引入(写在顶部)。

apply(from = "$rootDir/commonModule.gradle")

三、基础组件

将一直作为 Library 存在,app壳和所有的业务组件都依赖它,主要封装公共功能(网络请求、数据存储、自定义控件、工具类)、版本管理和统一第三方依赖。

3.1 创建 lib_base 组件

3.2 提供 BaseApplication

其它组件都依赖于 lib_base,因此在其中提供一个全局的 BaseApplication 供其它组件在代码中使用(需要获取全局上下文的时候就使用这个BaseApplication),所有资源的初始化也在这里。

在集成模式(Library)中使用不需要注册,在App壳和组件模式(Application)中需要自定义一个类继承 BaseApplication 后注册到 AndroidManifest 中使得从 BaseApplication 中获取的全局上下文生效,还有初始化数据之用。

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

统一所有组件中用到的第三方库和jar包。使用 imlemntation 添加的依赖只对当前组件有效,使用 api 添加的依赖对其上层组件同样有效,这样依赖于基础组件的组件就不用再添加相同的依赖了。

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

仅用于当前组件可向上传递(引入这个组件的上层组件不用重复添加依赖项)

implementation

api
testImplementationtestApi
androidTestImplementationandroidTestApi
debugImplementationdebugApi
[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" }
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 文件,第一行添加如下代码引入。

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

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 注册。

class MyApp : App() {}

5.2.2 自定义入口 LauncherActivity

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

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 动态切换组件模式

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

android { defaultConfig { if (libs.versions.isDebug.get().toBoolean()) { applicationId = "com.example.module_main" } } }

5.3.3 动态加载 AndroidManifest

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

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

5.3.4 引入需要的功能模块

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 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 组件,在集成模式下必须依赖所有声明的业务组件。

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

dependencies { implementation(project(":library:lib_base")) if (!libs.versions.isDebug.get().toBoolean()) { implementation(project(":module:module_main")) } }