news 2026/9/13 19:42:49

Now in Android 模块化学习之旅:`app` / `feature` / `core` 三层 Gradle 模块划分实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Now in Android 模块化学习之旅:`app` / `feature` / `core` 三层 Gradle 模块划分实战解析

Now in Android 模块化学习之旅:app/feature/core三层 Gradle 模块划分实战解析

【免费下载链接】nowinandroidA fully functional Android app built entirely with Kotlin and Jetpack Compose项目地址: https://gitcode.com/GitHub_Trending/no/nowinandroid

本文以 Now in Android(NiA)官方仓库的 ModularizationLearningJourney.md 为主线,系统讲解该应用如何将功能拆分为appfeature(api/impl 双子模块)与core三类 Gradle 模块,并结合settings.gradle.ktsTopicNavKey等源码证据,帮助读者掌握模块依赖规则、导航解耦方式与依赖图维护流程,可直接迁移到自己的多模块 Android 项目中实践。

学习路径概览:为什么 NiA 要这样拆分模块

模块化(Modularization)是把大型应用拆分为多个独立、可复用模块的工程实践。Now in Android 是一个完全基于 Kotlin 与 Jetpack Compose 构建的完整功能型示例应用,其仓库中每个模块的 README 都附带一张模块依赖图(例如:app模块依赖图),可以用来快速理解整个项目的结构。有关模块化的完整官方理论,可参考 Android 官方模块化指南。

本文档的核心价值在于:它不止给出了 NiA 的模块清单,更明确了三类模块各自的边界、它们之间的依赖规则(谁可以依赖谁、谁绝不能依赖谁),以及这些规则在真实源码中的落地形态。下面的 Mermaid 图概括了 NiA 中appfeaturecore三类模块的典型关系(完整图例参见 docs/ModularizationLearningJourney.md 中的模块类型图):

图中图例约定如下:app通过虚线(implementation依赖)引用各个feature模块;android-library类型的core模块通过实线(api依赖)向jvm-library模块暴露 API。Top tip:在模块化规划阶段,先画出一张模块图来可视化各模块之间的依赖关系,是非常有效的规划手段——本仓库用 Mermaid 以代码形式维护这张图,天然适合评审与自动化校验。

NiA 的三类模块及其职责边界

Now in Android 将全部模块划分为三类,每一类都有明确的职责与依赖约束(完整模块清单见 settings.gradle.kts 中的include声明)。

app模块:一切代码的组装者

app模块包含应用级与脚手架类,负责把其余代码库"绑"在一起,典型实现包括:

  • MainActivity(app/src/main/.../MainActivity.kt):应用入口 Activity;
  • NiaApp(NiaApp.kt):根 Compose 组合,承载主题、渐变背景、离线 Snackbar 与顶部应用栏;
  • 应用级导航:通过NiaNavHostTopLevelDestination组织页面跳转。

NiaApp.kt中可以看到app模块如何"指挥"各 feature 模块:它通过entryProvider同时注册了forYouEntrybookmarksEntryinterestsEntrytopicEntrysearchEntry五个入口,并通过 TopLevelNavItem.kt 中的TOP_LEVEL_NAV_ITEMS映射ForYouNavKeyBookmarksNavKeyInterestsNavKey到对应的图标与标题资源——这些 NavKey 全部来自各 feature 的api模块,app 只负责把它们组合进导航体系,这正是"应用级脚手架"的体现。

app模块依赖所有feature模块以及必需的core模块。从 app/README.md 的完整依赖图可以看到:appimplementation方式指向:feature:*:api:feature:*:impl:sync:work以及:core:analytics:core:common:core:data:core:designsystem等,还与:benchmarks(Baseline Profile 测试 APK)存在双向虚线关联。

feature模块:单点职责 + api/impl 双子模块

feature 模块处理应用中单一职责的功能。例如ForYou功能负责 "ForYou" 屏幕的全部内容与 UI 状态(包括首次运行的引导流程)。NiA 中 feature 模块本身不是独立的 Gradle 模块,而是被拆成两个子模块:

  • api—— 只包含导航键(navigation keys)
  • impl—— 包含其余所有内容(UI 组件、ViewModel 等)。

这种拆分带来两个关键能力:

  1. 跨功能导航解耦:功能 A 可以通过目标功能 B 的api模块中的导航键,跳转到功能 B,而不需要了解 B 的任何内部实现。
  2. 复用与隔离api/impl子模块可以被任何应用使用,包括测试或带 flavor 的应用。一个类如果只被某一个 feature 用到,就留在该 feature 内;如果被多个功能共享,则应下沉到合适的core模块。

feature 模块的依赖铁律

  • api模块不得依赖其他 feature 的apiimpl模块;
  • impl模块只能依赖其他 feature 的api模块(不得依赖其他 feature 的impl);
  • 两个子模块都只能依赖它们确实需要core模块。

这条规则在源码中可以得到直接验证。以 feature/topic/impl/build.gradle.kts 为例,feature:topic:impl的依赖只有projects.core.dataprojects.feature.topic.api以及测试相关的core:testing,完全没有触碰其他 feature 的impl

core模块:跨模块共享的公共库

core模块是包含辅助代码与特定依赖的公共库,供应用内其他模块共享。它们可以依赖其他 core 模块,但绝不能依赖 feature 模块或 app 模块。NiA 的 core 层非常丰富,从 settings.gradle.kts 可以看到至少包括:analyticscommondatadata-testdatabasedatastoredatastore-protodatastore-testdesignsystemdomainmodelnavigationnetworknotificationsscreenshot-testingtestingui。注意其中commonmodeldatastore-proto是纯 JVM 库(jvm-library),在依赖图中以紫色标记,可以显著提升单元测试速度与构建缓存命中率。

杂项模块

除上述三类外,NiA 还有一批特殊模块:sync(含sync:worksync:sync-test,负责后台同步)、benchmarks(Macrobenchmark 基准测试)、ui-test-hilt-manifestlint,以及app-nia-catalog—— 一个专门用于快速展示设计系统的目录应用(可在 IDE 中通过app-nia-catalog运行配置查看 design system)。

模块示例对照表:从文档到源码逐一对号入座

原文档用一张对照表说明每个模块的职责与关键类,以下结合仓库源码逐一印证:

模块职责关键类与实例
app将应用正常运转所需的一切绑定在一起,包括 UI 脚手架与导航NiaAppMainActivity;通过NiaNavHostNiaAppStateTopLevelDestination(当前仓库中对应 TopLevelNavItem.kt 与 NiaAppState.kt)实现应用级导航
feature:*:api提供其他 feature 可用的导航键与导航函数TopicNavKey(见下节源码分析)
feature:*:impl特定功能或用户旅程的完整实现,通常包含 UI 组件与 ViewModel,并从其他模块读取数据feature:topic:implTopicScreenTopicViewModel(TopicScreen.kt、TopicViewModel.kt);feature:foryou:impl展示用户新闻流与首次运行引导
core:data从多个数据源获取应用数据,供不同 feature 共享TopicsRepository(TopicsRepository.kt)等仓库接口,其默认实现为OfflineFirstTopicsRepository
core:designsystem设计系统:核心 UI 组件(多为定制化的 Material 3 组件)、应用主题与图标,可通过app-nia-catalog运行配置预览NiaIconsNiaButtonNiaTheme
core:ui供 feature 模块使用的复合 UI 组件与资源(如新闻流)。与designsystem不同,它依赖数据层,因为要渲染NewsResource等模型NewsFeedNewsResourceCardExpanded
core:common模块间共享的通用类NiaDispatchersResult
core:network发起网络请求并处理来自远程数据源的响应RetrofitNiaNetworkApi
core:testing测试依赖、测试仓库与工具类NiaTestRunnerTestDispatcherRule
core:datastore使用 DataStore 存储持久化数据NiaPreferencesUserPreferencesSerializer
core:database使用 Room 的本地数据库存储NiaDatabaseDatabaseMigrations、各Dao类(数据库 schema 版本见 core/database/schemas/.../NiaDatabase)
core:model全应用共享的模型类Topic(Topic.kt)、EpisodeNewsResource

一个值得注意的细节:core:ui依赖数据层(如NewsResource模型)而core:designsystem不依赖,这说明 NiA 刻意把"纯展示组件"与"绑定了业务模型的复合组件"分成两个 core 模块,从而让设计系统保持最大程度的可复用性。

跨模块导航的解耦范式:TopicNavKey实例剖析

文档以:topic:api:interests:impl的交互作为 feature 间导航的标准范式。真实的 TopicNavKey.kt 实现如下:

@Serializable data class TopicNavKey(val id: String) : NavKey fun Navigator.navigateToTopic( topicId: String, ) { navigate(TopicNavKey(topicId)) }

可以看到:

  • TopicNavKey是一个@Serializable的 data class,实现androidx.navigation3.runtime.NavKey,携带目标话题的id作为导航参数;
  • navigateToTopicNavigator的扩展函数,内部直接navigate(TopicNavKey(topicId))

这个范式下,:interests:impl只需依赖:topic:api,即可在用户点击某个 topic 时调用navigateToTopic(topicId)InterestsScreen跳到TopicScreenNavigator与导航状态的定义在 core/navigation,并有对应的 NavigatorTest.kt 单元测试验证导航逻辑。整个链路验证了文档的结论:feature 间通过 api 模块暴露的导航键通信,实现彻底解耦,这正是 api/impl 拆分最重要的工程收益。

依赖图的维护:README 图、Build.yaml 与graphUpdate任务

每个模块的README.md都包含一张模块图(例如:app模块依赖图)。这些图不是手工维护的静态图片,而是与代码同步的 Mermaid 文本块。

当模块依赖发生变化时,图会由 Build.yaml 工作流自动更新。具体机制(见该工作流第 81 行与第 93 行):

./gradlew graphUpdate
  • 工作流中运行./gradlew graphUpdate重新生成各模块 README 中的依赖图;
  • 若图与当前依赖不一致(例如开发者改了依赖但没更新图),第 93 行会输出错误提示Check Graphs failed, please update graphs with: ./gradlew graphUpdate并使 CI 失败,强制开发者提交同步的图。

开发者也可以在本地手动运行graphUpdate任务刷新所有 README 图。这一做法把"架构图漂移"问题变成了 CI 可检查项,值得在大型多模块项目中借鉴。

模块化程度的权衡:过度模块化 vs 面向未来扩展

NiA 的模块化方案是结合项目路线图、规划中的工作与新特性反复推敲后确定的。其核心目标是:在"过度模块化一个小型应用"与"用这个机会展示适合更大代码库、更接近生产环境真实应用的模块化模式"之间找到平衡

原文档(docs/ModularizationLearningJourney.md)给出的关键建议值得强调:

  • 模块化没有唯一正确答案:应用大小、代码库现状与团队偏好不同,方案必然不同。很少有哪一种方式适合所有场景。
  • 规划先行:动手前先明确目标、要解决的问题、未来工作与潜在障碍,是定义最合适结构的关键步骤。可以组织一次头脑风暴,把模块与依赖画成图来可视化规划。
  • 按需调节粒度(granularity):粒度指代码库由多少模块组成。数据层规模小的时候,放在单一模块完全没问题;一旦仓库与数据源数量增长,就值得考虑拆分为独立模块。
  • 保持演进心态:NiA 的方案并非一成不变的结构,未来仍可能演进,它只是一个"最适合本项目的通用指导方针",开发者可以在其上继续修改、扩展。例如通过提高代码库粒度来进一步拆分。

这也与仓库中core模块的丰富分层相互印证:NiA 把数据、数据库、网络、DataStore、设计系统、UI、领域层等都拆成了独立 core 模块,正是"按需扩展粒度"的具体示范。

小结:把 NiA 的模块化经验移植到自己的项目

回顾全文,Now in Android 的模块化方案可以提炼为四条可直接落地的经验:

  1. 三层职责划分app负责组装与脚手架,feature负责单点业务功能,core负责跨模块共享的基础能力,三者依赖方向严格单向(feature → core,app → 一切)。
  2. api/impl 双子模块:feature 对外只暴露导航键,用@SerializableNavKey+Navigator扩展函数实现无实现依赖的跨功能跳转,可测试、可复用、可替换。
  3. 依赖规则显式化api不依赖其他 feature,impl只依赖其他 feature 的api,core 不依赖 feature/app——这些约束在build.gradle.kts中可审计、可强制。
  4. 架构图自动化:用 Mermaid 在 README 中维护模块图,通过./gradlew graphUpdate与 Build.yaml 工作流保证图与代码一致。

最后请记住:这不是唯一正确的方案,而是 NiA 团队在社区反馈与项目实际约束下给出的一个高质量参照系。阅读源码时,可以从 settings.gradle.kts 的模块清单出发,对照 app/README.md 的完整依赖图,再逐个深入各 feature 与 core 模块的源码与测试,构建起对多模块 Android 应用架构的完整认知。

【免费下载链接】nowinandroidA fully functional Android app built entirely with Kotlin and Jetpack Compose项目地址: https://gitcode.com/GitHub_Trending/no/nowinandroid

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 19:36:52

pybind11 版本发布指南:从版本号规范到完整的发布流程实战

pybind11 版本发布指南:从版本号规范到完整的发布流程实战 【免费下载链接】pybind11 Seamless operability between C11 and Python 项目地址: https://gitcode.com/GitHub_Trending/py/pybind11 本篇指南以 pybind11 官方文档 docs/release.rst 为骨架&…

作者头像 李华
网站建设 2026/9/13 19:33:01

SSOP-20 MCU采购避坑指南:封装、电气与批次溯源三重校验

1. 为什么一颗SSOP-20封装的PIC24F16KA101,买回来却焊不上板子? “PIC24F16KA101-I/SS”这个型号,乍看只是Microchip官网上一串普通编号,但在我经手过的上百个MCU选型项目里,它堪称“表面最温和、实则最易翻车”的典型…

作者头像 李华