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"))
    }
}
相关推荐
明明明h2 小时前
【Unity3D】Android App Bundle(aab)打包上架Google Play介绍
android
花卷HJ2 小时前
Android 通用 RecyclerView Adapter 实现(支持 ViewBinding + 泛型 + 点击事件)
android
oMcLin2 小时前
如何在Ubuntu 22.04 LTS上配置并优化MySQL 8.0分区表,提高大规模数据集查询的效率与性能?
android·mysql·ubuntu
幸福的达哥3 小时前
安卓APP代码覆盖率测试方案
android·代码覆盖率
佛系打工仔3 小时前
绘制K线入门
android
川石课堂软件测试4 小时前
Android和iOS APP平台测试的区别
android·数据库·ios·oracle·单元测试·测试用例·cocoa
花卷HJ5 小时前
Android 通用 BaseDialog 实现:支持 ViewBinding + 全屏布局 + 加载弹窗
android
生产队队长5 小时前
Linux:awk进行行列转换操作
android·linux·运维
叶羽西5 小时前
Android15 EVS HAL中使用Camera HAL Provider接口
android