Now in Android Core 模块分析:共享能力是如何被抽离的?
核心观点
Now in Android 中的 Core 模块,本质上是在回答一个问题:
哪些能力属于整个应用,而不是某个具体 Feature?
Core 的存在并不是为了增加架构层次,而是为了避免多个 Feature 重复实现相同的能力。
因此,Core 关注的不是业务,而是:
- 数据访问;
- 基础设施;
- 共享 UI;
- 统一设计规范;
- 通用模型。
它代表的是:
整个应用的公共能力。
为什么需要 Core?
假设没有 Core。
每个 Feature 都自己实现:
Network
Database
Repository
Design System
那么:
feature:search
├─ Retrofit
├─ Room
└─ Button
feature:bookmarks
├─ Retrofit
├─ Room
└─ Button
feature:foryou
├─ Retrofit
├─ Room
└─ Button
问题很快出现:
- 重复代码增多;
- 数据逻辑分散;
- UI 风格不统一;
- 修改成本上升。
因此,需要一个地方来存放:
所有 Feature 都可能依赖的能力。
这就是 Core。
NIA 中的 Core 长什么样?
NIA 中常见的 Core 模块包括:
core:data
core:model
core:database
core:network
core:ui
core:designsystem
可以将它们理解为:
Core
├─ 数据能力
├─ 基础设施能力
└─ UI 能力
core:统一语言
它负责定义整个应用共享的数据模型。
例如:
NewsResource
Topic
UserData
其价值在于:
让不同 Feature 使用同一种语言。
如果没有统一模型:
SearchNews
BookmarkNews
TopicNews
同一种数据可能出现多个版本。
最终导致:
- 转换逻辑泛滥;
- 认知成本增加。
core:统一访问网络
负责:
- Retrofit 配置;
- API 定义;
- 网络请求相关能力。
Feature 不需要知道:
OkHttp
JSON 解析
请求头
它只关心:
"我要获取什么数据?"
core:统一访问数据库
负责:
- Room Database;
- DAO;
- Entity。
其目标是:
让数据库成为共享基础设施。
Feature 不直接操作数据库。
否则容易出现:
多个 Feature 修改同一张表
带来的耦合问题。
core:共享数据能力
这是 Core 中最重要的部分。
通常负责:
Repository
DataSource 协调
Offline First 策略
同步逻辑
它解决的问题是:
数据应该从哪里来?
例如:
Database?
Network?
缓存?
对于 Feature 来说:
这些细节并不重要。
Feature 只需要:
"给我需要的数据。"
core:共享 UI 能力
负责:
LoadingView
ErrorView
通用 Compose 组件
避免:
每个 Feature 自己实现一套 Loading。
其价值在于:
提高一致性。
core:统一设计语言
负责:
Theme
Typography
Color
Icon
Button Style
其目标是:
让整个 App 看起来像同一个产品。
否则:
Search 使用蓝色按钮
Bookmark 使用绿色按钮
用户会感受到明显的不一致。
Core 与 Feature 的关系
Feature 负责业务。
Core 提供能力。
可以抽象为:
#mermaid-svg-gBIN4HlArx6gKbNV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gBIN4HlArx6gKbNV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gBIN4HlArx6gKbNV .error-icon{fill:#552222;}#mermaid-svg-gBIN4HlArx6gKbNV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gBIN4HlArx6gKbNV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gBIN4HlArx6gKbNV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gBIN4HlArx6gKbNV .marker.cross{stroke:#333333;}#mermaid-svg-gBIN4HlArx6gKbNV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gBIN4HlArx6gKbNV p{margin:0;}#mermaid-svg-gBIN4HlArx6gKbNV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gBIN4HlArx6gKbNV .cluster-label text{fill:#333;}#mermaid-svg-gBIN4HlArx6gKbNV .cluster-label span{color:#333;}#mermaid-svg-gBIN4HlArx6gKbNV .cluster-label span p{background-color:transparent;}#mermaid-svg-gBIN4HlArx6gKbNV .label text,#mermaid-svg-gBIN4HlArx6gKbNV span{fill:#333;color:#333;}#mermaid-svg-gBIN4HlArx6gKbNV .node rect,#mermaid-svg-gBIN4HlArx6gKbNV .node circle,#mermaid-svg-gBIN4HlArx6gKbNV .node ellipse,#mermaid-svg-gBIN4HlArx6gKbNV .node polygon,#mermaid-svg-gBIN4HlArx6gKbNV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gBIN4HlArx6gKbNV .rough-node .label text,#mermaid-svg-gBIN4HlArx6gKbNV .node .label text,#mermaid-svg-gBIN4HlArx6gKbNV .image-shape .label,#mermaid-svg-gBIN4HlArx6gKbNV .icon-shape .label{text-anchor:middle;}#mermaid-svg-gBIN4HlArx6gKbNV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gBIN4HlArx6gKbNV .rough-node .label,#mermaid-svg-gBIN4HlArx6gKbNV .node .label,#mermaid-svg-gBIN4HlArx6gKbNV .image-shape .label,#mermaid-svg-gBIN4HlArx6gKbNV .icon-shape .label{text-align:center;}#mermaid-svg-gBIN4HlArx6gKbNV .node.clickable{cursor:pointer;}#mermaid-svg-gBIN4HlArx6gKbNV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gBIN4HlArx6gKbNV .arrowheadPath{fill:#333333;}#mermaid-svg-gBIN4HlArx6gKbNV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gBIN4HlArx6gKbNV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gBIN4HlArx6gKbNV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gBIN4HlArx6gKbNV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gBIN4HlArx6gKbNV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gBIN4HlArx6gKbNV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gBIN4HlArx6gKbNV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gBIN4HlArx6gKbNV .cluster text{fill:#333;}#mermaid-svg-gBIN4HlArx6gKbNV .cluster span{color:#333;}#mermaid-svg-gBIN4HlArx6gKbNV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gBIN4HlArx6gKbNV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gBIN4HlArx6gKbNV rect.text{fill:none;stroke-width:0;}#mermaid-svg-gBIN4HlArx6gKbNV .icon-shape,#mermaid-svg-gBIN4HlArx6gKbNV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gBIN4HlArx6gKbNV .icon-shape p,#mermaid-svg-gBIN4HlArx6gKbNV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gBIN4HlArx6gKbNV .icon-shape .label rect,#mermaid-svg-gBIN4HlArx6gKbNV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gBIN4HlArx6gKbNV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gBIN4HlArx6gKbNV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gBIN4HlArx6gKbNV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-gBIN4HlArx6gKbNV .feature>*{fill:#dff5df!important;stroke:#4caf50!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .feature span{fill:#dff5df!important;stroke:#4caf50!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .feature tspan{fill:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .data>*{fill:#fff4cc!important;stroke:#f4b400!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .data span{fill:#fff4cc!important;stroke:#f4b400!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .data tspan{fill:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .infra>*{fill:#ffd6d6!important;stroke:#db4437!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .infra span{fill:#ffd6d6!important;stroke:#db4437!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .infra tspan{fill:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .ui>*{fill:#d6eaff!important;stroke:#4a90e2!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .ui span{fill:#d6eaff!important;stroke:#4a90e2!important;color:#000!important;}#mermaid-svg-gBIN4HlArx6gKbNV .ui tspan{fill:#000!important;} Feature Modules
core:data
core:model
core:network
core:database
core:ui
core:designsystem
Feature 依赖 Core。
Core 不依赖 Feature。
这种依赖方向能够保证:
业务变化不会污染共享能力。
什么应该进入 Core?
适合进入 Core 的能力:
Repository
Database
Network
Theme
共享组件
Model
它们具有共同特征:
多个 Feature 都需要。
什么不应该进入 Core?
例如:
SearchViewModel
BookmarkScreen
TopicUiState
这些属于具体业务。
如果放入 Core:
Core 会逐渐演变成:
misc/
utils/
everything/
最终失去边界。
Core 是否越大越好?
不是。
Core 最大的风险在于:
成为新的上帝模块(God Module)。
当所有东西都被塞进 Core 时:
core
├─ user
├─ news
├─ search
├─ bookmark
├─ topic
└─ ...
Feature 与 Core 的边界开始模糊。
最终:
整个系统重新回到单体架构。
因此:
Core 应该抽离的是共享能力,而不是共享业务。
我的结论
Now in Android 的 Core 模块,本质上是在建立:
能力边界。
它试图回答的问题不是:
"哪些代码可以复用?"
而是:
"哪些能力属于整个应用?"
理解这一点之后,会发现:
Core 的价值不在于减少代码量。
而在于:
- 建立统一规范;
- 降低 Feature 耦合;
- 支撑大型团队协作。
好的 Core,不是最大的 Core。
而是:
只承载真正共享能力的 Core。