0Pricing
Android Academy · 课时

功能模块与核心模块

划分模块边界

功能模块与核心模块 是 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 反馈 — 无需本地设置。

此课程中的所有课时

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