功能模块与核心模块
划分模块边界
功能模块与核心模块 是 CoddyKit 上的免费 Android Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
两类模块
大多数经过模块化的 Android 应用会将代码组织为两大类模块:功能模块和核心模块,顶层则有一个精简的 :app 模块。
- 功能模块包含应用中面向用户的一部分功能(一个界面或一条流程)。
- 核心模块包含多个功能模块都会使用的共享基础设施。
在本课中,您将学习如何合理地划分这些边界。
功能模块的组成
像 :feature:profile 这样的功能模块包含某项功能所需的一切:其 Compose 界面、ViewModel 和界面状态。它是纵向的:负责从界面一直到底层视图模型的完整功能切片。
它依赖核心模块提供共享内容,但不应依赖其他功能模块。
// feature/profile/ProfileScreen.kt
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
when (state) {
is ProfileUiState.Loading -> CircularProgressIndicator()
is ProfileUiState.Success -> ProfileContent((state as ProfileUiState.Success).user)
is ProfileUiState.Error -> ErrorMessage()
}
}功能模块的 ViewModel
每个功能模块都拥有自己的 ViewModel。它通过核心模块中的存储库获取数据,并公开界面状态。功能模块无需了解数据如何获取,只需要了解存储库契约。
// feature/profile/ProfileViewModel.kt
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository // from :core:data
) : ViewModel() {
val uiState: StateFlow<ProfileUiState> =
userRepository.observeUser()
.map { ProfileUiState.Success(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ProfileUiState.Loading)
}核心模块的组成
核心模块是横向的:它提供可供多个功能模块使用的一项能力。常见的核心模块包括:
:core:model— 在各处共享的普通数据类:core:network— Retrofit/Ktor 客户端:core:database— Room 配置:core:data— 组合网络与数据库的存储库:core:designsystem— 主题和可复用组合项
// core/model/User.kt
data class User(
val id: String,
val name: String,
val avatarUrl: String
)设计系统模块
:core:designsystem 是复用率最高的模块之一。它包含您的 MaterialTheme、配色方案、排版设置,以及按钮和卡片等可复用组合项。每个功能模块都能使用相同的视觉样式,而无需复制代码。
// core/designsystem/AppTheme.kt
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
content = content
)
}数据模块负责存储库
:core:data 模块向功能模块公开其依赖的存储库接口,同时隐藏具体实现。它通常依赖 :core:network 和 :core:database,将两者组合成唯一可信数据源。
// core/data/UserRepository.kt
interface UserRepository {
fun observeUser(): Flow<User>
suspend fun refresh()
}
// core/data/OfflineFirstUserRepository.kt
internal class OfflineFirstUserRepository @Inject constructor(
private val api: UserApi, // :core:network
private val dao: UserDao // :core:database
) : UserRepository {
override fun observeUser(): Flow<User> = dao.observe().map { it.toUser() }
override suspend fun refresh() { dao.upsert(api.fetch().toEntity()) }
}保持模型和设计系统轻量
底层核心模块应尽量少依赖其他内容。理想情况下,:core:model 完全不依赖 Android——只包含普通的 Kotlin 数据类。这样它就能在任何地方使用,并且构建速度快。
如果 :core:model 开始依赖 Retrofit 或 Room,那么每个使用这些数据类的模块都会被迫引入这些体积庞大的库。
// core/model/build.gradle.kts
plugins {
id("myapp.jvm.library") // pure Kotlin, no Android
}
// No Retrofit, no Room, no Compose here — just data classes精简的 :app 模块
:app 模块负责组装。其中应尽量少包含业务逻辑,只需包含 Application 类、唯一的 MainActivity、顶层 NavHost 以及依赖注入的连接代码。所有真正的屏幕都应位于功能模块中。
保持应用模块精简意味着大多数更改都会发生在功能模块中,因此应用模块很少需要重新构建。
// app/MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme { // from :core:designsystem
AppNavHost() // routes into :feature:* screens
}
}
}
}划分良好的边界
如何决定哪些内容应成为一个模块?可以参考以下实用原则:
- 一个功能 = 用户能够说出名称的屏幕或流程(主页、个人资料、结算)。
- 一个核心模块 = 可被两个或更多功能复用的能力。
- 如果两个功能都需要同一段代码,就将它下移到核心模块中。
- 如果一个模块承担了太多互不相关的职责,就将它拆分。
可选:拆分 api 与 impl
大型应用有时会将一个功能拆分为公开的 :feature:profile:api(接口、导航路由)和私有的 :feature:profile:impl(屏幕、视图模型)。其他功能只依赖较小的 api,而不会依赖实现模块。
这是高级用法;对于大多数应用来说,每个功能使用一个模块就已经足够。只需了解,在超大型代码库中存在这种模式即可。
// Other features see only the contract, not the screens
// feature/home depends on :feature:profile:api
interface ProfileEntry {
val route: String
fun NavGraphBuilder.register(navController: NavController)
}组装整个图
下面展示了我们示例中的各层如何连接。请注意,依赖始终只指向下层:app -> feature -> data -> network/database -> model。
// :app -> :feature:home, :feature:profile
// :feature:home -> :core:data, :core:designsystem
// :feature:profile -> :core:data, :core:designsystem
// :core:data -> :core:network, :core:database, :core:model
// :core:network -> :core:model
// :core:database -> :core:model
// :core:model -> (nothing)快速检查
您的 :feature:home 和 :feature:profile 模块都需要获取并缓存用户数据。这些仓库代码应放在哪里?
回顾:功能模块与核心模块
您已经学会将代码拆分为两层:
- 功能模块是垂直切片(屏幕 + ViewModel + 界面状态)。
- 核心模块是横向能力(模型、网络、数据库、数据、设计系统)。
:app模块保持精简,只负责组装各个功能。- 共享代码向下移到核心模块;
:core:model保持不依赖 Android 且足够轻量。
接下来,您将管理这些模块之间的依赖关系,并保持依赖图整洁。
常见问题解答
「功能模块与核心模块」课时是免费的吗?
是的 — 「功能模块与核心模块」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「功能模块与核心模块」这节课中我会学到什么?
划分模块边界 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「功能模块与核心模块」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。