0Pricing
Android Academy · 课时

管理模块依赖

保持依赖图清晰且无环

管理模块依赖 是 CoddyKit 上的免费 Android Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。

依赖图

每个模块都会声明它所依赖的其他模块。这些依赖合在一起就构成了依赖图。健康的依赖图是DAG(有向无环图):依赖只沿一个方向指向,并且永远不会形成循环。

在本课中,您将学习如何整洁地声明依赖、选择正确的 Gradle 配置、共享版本并避免循环依赖。

声明模块依赖

您可以在 dependencies 代码块中使用 project(":path:to:module") 添加对另一个模块的依赖。该路径与文件夹结构一致,并且与您在 settings.gradle.kts 中声明的路径相匹配。

// feature/profile/build.gradle.kts
dependencies {
    implementation(project(":core:data"))
    implementation(project(":core:designsystem"))
    implementation(project(":core:model"))
}

implementation 与 api

您选择的配置决定了哪些内容会泄漏给使用者:

  • implementation:依赖是私有的。依赖于您的模块无法看到它。这是默认选择。
  • api:依赖会被重新公开(传递依赖)。仅当您的公开类型来自该依赖时才使用它。

几乎总是优先使用 implementation——这可以提高构建速度,因为隐藏依赖发生更改时,不会强制使用者重新编译。

// core/data/build.gradle.kts
dependencies {
    // Repository signatures return :core:model types,
    // so consumers need to SEE it -> api
    api(project(":core:model"))

    // Network is an internal detail -> implementation
    implementation(project(":core:network"))
}

implementation 为何能加快构建

使用 implementation 时,Gradle 知道隐藏依赖的更改不会影响模块的公开 ABI。因此,使用者无需重新编译。使用 api 时,更改会沿着传递依赖链影响每一个使用者。

经验法则是:只有当依赖出现在模块的公开类型中(返回类型、公开参数)时,才将它放入 api。否则请使用 implementation。

// Public -> needs api
fun observeUser(): Flow<User>   // Flow and User leak out

// Internal -> implementation is enough
private val client: OkHttpClient // never exposed

集中管理版本:版本目录

当模块很多时,您不会希望在各处重复填写库版本。Gradle 的版本目录(gradle/libs.versions.toml)可以统一定义版本和别名。每个模块都引用同一个别名。

# gradle/libs.versions.toml
[versions]
compose-bom = "2024.09.00"
retrofit = "2.11.0"

[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }

在模块中使用版本目录

模块随后通过生成的 libs 访问器引用库。构建文件中不再包含版本号,因此升级只需在一个地方进行。

// core/network/build.gradle.kts
dependencies {
    implementation(platform(libs.compose.bom))
    implementation(libs.retrofit)
}

大忌:循环依赖

当模块 A 依赖 B,而 B 又直接或通过一系列依赖返回依赖 A 时,就形成了循环。Gradle 会拒绝构建循环依赖。它也说明设计存在问题:这两个模块之间的边界划分不正确。

// :feature:cart  -> implementation(project(":feature:checkout"))
// :feature:checkout -> implementation(project(":feature:cart"))
//
// Gradle error:
// Circular dependency between the following tasks:
// :feature:cart:compile -> :feature:checkout:compile -> :feature:cart:compile

打破循环依赖

要打破循环依赖,请将共享部分提取到一个更低层的模块中,让两个模块都依赖它。如果两个功能需要彼此的数据,那么共享契约应放在核心模块中,而不是放在任一功能模块中。

这样就能恢复向下的依赖流:两个功能都指向核心模块,而核心模块永远不会反向指向上层。

// Before: cart <-> checkout (cycle)
// After:  cart -> :core:order  <-  checkout

// core/order/OrderContract.kt
data class Order(val items: List<CartItem>, val total: Double)

// feature/cart    -> implementation(project(":core:order"))
// feature/checkout-> implementation(project(":core:order"))

依赖反转:依赖抽象

有时,低层模块需要的行为位于更高层。此时不要向上依赖,而应在低层模块中定义一个接口,再让高层模块通过依赖注入提供实现。这就是依赖反转。

// core/analytics defines the contract
interface AnalyticsLogger {
    fun log(event: String)
}

// :app provides the real implementation and injects it down
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {
    @Provides
    fun logger(impl: FirebaseAnalyticsLogger): AnalyticsLogger = impl
}

可视化并守护依赖图

您可以让 Gradle 打印或渲染模块依赖图,甚至添加一个构建失败的测试,以便在出现被禁止的依赖时终止构建(例如核心模块依赖功能模块)。module-graph 等工具可以自动生成图表。

# Print the project structure
./gradlew projects

# Inspect why :feature:home pulls in a library
./gradlew :feature:home:dependencies --configuration debugRuntimeClasspath

整洁的无环示例

下面是一个健康的依赖图。请从上到下阅读;没有任何箭头会指回上层,也没有两个模块会彼此指向。这正是您应当实现的结构。

// :app
//   -> :feature:home   -> :core:data -> :core:network -> :core:model
//   -> :feature:profile -> :core:data -> :core:database -> :core:model
//   -> :core:designsystem
//
// Every path ends at :core:model. No cycles. Builds in parallel.

快速检查

您的 :core:network 模块仅在内部使用 OkHttpClient——它从未出现在任何公开函数签名中。声明 OkHttp 依赖时应使用哪种 Gradle 配置?

回顾:管理模块依赖

您已经学会如何保持模块依赖图健康:

  • 使用 project(":path") 声明模块依赖。
  • 默认使用 implementation;只有公开接口中包含相关类型时才使用 api。
  • 在版本目录(libs.versions.toml)中集中管理版本。
  • 永远不要创建循环依赖——Gradle 会拒绝它们;可以通过向下提取共享代码,或使用接口进行依赖反转来打破循环。

接下来,您将通过导航连接各个功能,同时避免让它们彼此耦合。

常见问题解答

「管理模块依赖」课时是免费的吗?

是的 — 「管理模块依赖」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。

「管理模块依赖」这节课中我会学到什么?

保持依赖图清晰且无环 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Android Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「管理模块依赖」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Android Academy 课中编写并运行代码吗?

能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为什么要模块化
  2. 功能模块与核心模块
  3. 管理模块依赖
  4. 跨模块导航
← 返回 Android Academy