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 属性。
12.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 |
| testImplementation | testApi |
| androidTestImplementation | androidTestApi |
| debugImplementation | debugApi |
[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、同一个权限无须多次申请)。
- 将自动创建的 AndroidManifest 复制一份到 debug 包下,对 <manifest> 标签添加 package 属性指定为组件的包名,将 .MainActivity 改成自定义的启动Activity。然后像单工程那样用就行。
- 再修改自动创建的 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
- 将 app 中的 MainActivity.class 和 activity_main.xml(Compose没有这个) 剪切至 module_main 中。
- 将 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")) } }