Now in Android Core 模块分析:共享能力是如何被抽离的?

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。

相关推荐
黄林晴2 小时前
绝了!Compose Multiplatform 也能实现 iOS26 液态玻璃的效果了
android·kotlin
2601_961767282 小时前
【分享】云视听快TV 快手电视版 手机电视都可以用
android·智能手机
数智工坊11 小时前
机器人运动控制:采样、优化与学习三大流派深度对比与实战
android·学习·机器人
故渊at13 小时前
第二板块:Android 四大组件标准化学理 | 第八篇:Service 后台执行实体与优先级
android·gitee·service·前台服务·后台服务
会Tk矩阵群控的小木13 小时前
安卓群控系统对于游戏工作室实战教程
android·运维·游戏·adb·开源软件·个人开发
qeen8714 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
故渊at14 小时前
第二板块:Android 四大组件标准化学理 | 第九篇:BroadcastReceiver 事件分发与有序广播
android·gitee·broadcast·广播·动态注册·静态注册
JohnnyDeng9415 小时前
【Android】Room 数据库高级用法与性能调优:从查询瓶颈到毫秒级响应
android·性能优化·kotlin·room