Agent 开发入门:工具调用循环、上下文与工程化落地

写 Agent 时,很多人一上来就纠结「用哪个框架」。更值得先想清楚的是:模型负责推理,工具负责改世界;中间那一圈「观察 → 决策 → 调用 → 再观察」怎么收口。 框架只是把这圈循环写得更省事。本文按工程视角拆开这套循环,并落到 Python / TypeScript、MCP、Skill(可复用流程说明书)上常见的实现选择。

本文不讲「万能自主 AI」。默认场景是:IDE / CLI 里由人触发、有权限边界、可审计的编码与运维助手——和博客站里那篇 MCP 与 Skill 驱动下的跨端转换 同一类问题域。


1. Agent 到底多出来一层什么

聊天补全:用户一问,模型一答,结束。

Agent:模型在回答之前(或之间)可以 多次调用工具,把工具结果写回上下文,再继续推理,直到它认为可以给出最终答复,或被策略强制停下。

一张极简结构(内联 SVG,避免 Mermaid 中文裁切):

Agent 循环 用户任务进入 LLM,LLM 可调用工具并读回结果,直到输出最终答复。 用户任务 一句话需求 / 指令 LLM 计划 · 选工具 · 汇总结果 可多次循环 工具 读文件 · 跑命令 · 查 API 有权限与超时 输出最终答复 tool call observation

差别不在「会不会说话」,而在:

维度 普通对话 Agent
上下文来源 主要靠用户粘贴 模型可主动拉仓库、日志、文档
副作用 通常没有 写文件、开 PR、调外部系统(需权限)
失败处理 用户再追问 循环里读错误信息、改参数重试
停机条件 一轮结束 预算(步数 / token / 时间)+ 明确结束态

若某次任务 不需要工具,硬套 Agent 只会更慢、更贵;有时「单次推理 + 你自己写好的脚本」反而更稳。


2. 工具调用循环:实现上绕不开的几件事

无论用 LangChain / LangGraph、自研,还是 IDE 内置 Agent,内核多半长这样:

  1. 组装消息:系统提示 + 历史 + 当前任务 +(可选)可用工具 schema。
  2. 调模型:要求返回「纯文本」或「结构化 tool call」。
  3. 若有 tool call:校验参数 → 执行 → 把结果以 tool 角色写回消息列表。
  4. 若无 tool call:视为最终答案,退出。
  5. 防护:最大步数、单工具超时、敏感路径黑名单、是否允许联网。

伪代码(语言无关):

1
2
3
4
5
6
7
8
9
10
messages = [system, user_task]
for step in 1..MAX_STEPS:
resp = llm(messages, tools=TOOL_SCHEMAS)
if resp.tool_calls:
for call in resp.tool_calls:
result = run_tool(call.name, call.args) # 这里要做权限与超时
messages.append(tool_result(call.id, result))
continue
return resp.text
raise BudgetExceeded

落地时容易踩的坑:

  • 工具描述含糊:模型会乱选工具或乱填参数。schema 的 description 和「什么时候不该用」写清楚,比堆工具数量重要。
  • 结果太长:把整份 build.log 塞回上下文,下一步推理会糊。要对日志做截断、摘要、或只回错误附近几行。
  • 无退出条件:模型反复「再试一下」。步数上限 +「连续 N 次同类失败则停并汇报」比指望它自觉收手可靠。
  • 把规划写进提示却不写进状态:复杂任务需要显式 todo / 状态机(LangGraph 一类),否则长对话里「计划」会被冲掉。

3. 上下文:窗口有限,策略才是能力

Agent 强不强,经常取决于 塞进上下文的东西是否刚好够用。常见策略:

  1. 按需检索:向量库 / 关键字搜仓库,而不是整库灌进 prompt。
  2. 工作区摘要:当前分支、最近 diff、相关文件列表先给一页「地图」,再按需 read_file。
  3. 会话记忆分层:短期(本轮 tool 结果)、中期(本任务的结论)、长期(用户偏好、项目约定)分开存,不要全挤在同一条 system 里。
  4. Skill / 说明书:把「本仓库怎么跑测试、怎么写 commit、评审看什么」写成可加载文档,需要时再注入——比每次口头重复稳定。

和「记忆」相关的务实结论:

  • 对话历史 不等于 可靠记忆;长会话要压缩或外部化。
  • 写进仓库的 AGENTS.md / Skill / 规则,往往比塞进某次聊天更可复用。
  • 敏感信息(密钥、token)不要进模型上下文;工具侧用环境变量与密钥管理。

4. MCP 与 Skill:协议层和流程层

4.1 MCP

MCP(Model Context Protocol) 把「工具 / 资源怎么接到模型宿主」标准化了:同一套客户端可以接 Git、浏览器、内部 HTTP、数据库只读接口等。对 Agent 开发者的意义是:

  • 少写一轮「每个 IDE 各接一遍」的胶水;
  • 权限与审计可以做在 MCP Server 边界上;
  • 换模型或换宿主时,工具面相对稳。

它不负责替你设计「何时调用、失败如何重试」——那仍是 Agent 循环与产品策略的事。

4.2 Skill

Skill(或同类「可复用流程说明书」)更偏 人写的、可版本管理的操作手册:触发条件、步骤、检查清单、输出格式。适合:

  • 代码评审口径统一;
  • 发布前检查;
  • 跨端迁移的质量门禁(见站内 MCP + Skill 一文)。

经验上:MCP 解决「能碰到什么」;Skill 解决「碰到之后按什么规矩做」。 二者叠在一起,Agent 才不至于每次从零猜项目习惯。


5. 技术选型怎么挑(够用即可)

需求 常见选择 备注
Python 快速原型 官方 SDK + 自写循环,或 LangChain 工具层 先跑通 3~5 个工具再上图编排
多步骤状态 / 分支 / 人工确认 LangGraph、自研状态机 显式节点比「全靠长 prompt」好调试
TypeScript / Node 全栈 Vercel AI SDK、OpenAI Agents SDK、自研 与前端同栈时排错成本低
IDE 内编码助手 Cursor Agent、Copilot Workspace 类 重点变成规则、MCP、仓库约定,而不是自建 UI
只要批处理脚本 不要 Agent cron + 确定性脚本更合适

Python 侧最小可运行思路(示意,非完整项目):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 示意:一轮 tool-calling;真实项目还要错误分级、日志、鉴权
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "读取仓库内文本文件。path 为相对仓库根路径。",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
}
]

def run_agent(client, messages, max_steps=8):
for _ in range(max_steps):
resp = client.chat.completions.create(
model="gpt-4.1-mini", # 换成你实际用的模型 id
messages=messages,
tools=tools,
)
msg = resp.choices[0].message
if not msg.tool_calls:
return msg.content
messages.append(msg)
for tc in msg.tool_calls:
# 在这里做路径白名单、大小限制
payload = execute(tc.function.name, tc.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": payload[:8000],
})
return "已达步数上限,请缩小任务或提高权限边界后重试。"

选型原则只有一条:先让「一个工具 + 一步循环」在你自己的仓库里跑通,再加编排框架。反过来很容易变成框架文档驱动开发。


6. 评测与上线:比 demo 更要紧的部分

Agent demo 好看,不等于能进业务。上线前至少钉死:

  1. 任务集:准备 20~50 个真实小任务(修测试、改文案、查日志),固定输入,看成功率和平均步数。
  2. 回归:改 prompt / 换模型后重跑同一集合,避免「感觉变聪明了」其实是样本变简单了。
  3. 安全默认:写操作为准拒绝或二次确认;生产凭据不进工具默认环境;命令带 timeout。
  4. 可观测:每次 tool call 记名称、参数摘要、耗时、成败;出事故时能复盘「卡在第几步」。
  5. 人机边界:哪些步骤必须人点头(合并主分支、发版、付钱),写进产品而不是写进「模型应该懂」。

7. 和日常开发怎么叠

若你主要在 Cursor / 同类环境里用 Agent,工程化抓手通常是:

  • 仓库级规则与 Skill:构建命令、目录约定、禁止事项;
  • MCP:接内部文档、工单、浏览器、专用校验脚本;
  • 短反馈环:能本地 test / lint 的,就让 Agent 自己跑,少靠「我觉得编译过了」。

自研 Agent 服务时,则额外要做:会话存储、配额、多租户隔离、工具沙箱。那是另一篇文章的体量;本文把骨架定在 循环 + 上下文 + 协议/说明书 + 评测 四层,一般够支撑从 demo 走到可用。


8. 小结:动手时盯这几条

  • Agent = 带工具的推理循环,不是换皮聊天。
  • 工具 schema、结果裁剪、步数上限,比「再堆一个 Agent 框架」更能提升稳定性。
  • MCP 管连接面,Skill 管项目规矩;上下文策略决定上限。
  • 没有固定任务集和日志,就谈不上迭代模型或 prompt。

下一步若要加深,可以按方向拆:只做「编码 Agent 的仓库门禁」、或只做「LangGraph 状态机里的人工确认节点」。有具体仓库结构时,把工具列表和失败日志贴出来,比空谈框架版本有用得多。

MCP 与 Skill 驱动下的跨端代码转换与质量门禁

把 Android / iOS / 鸿蒙(HarmonyOS) 之间的工程迁移交给「大模型 + 工具」时,真正决定上限的往往不是单次对话写得多漂亮,而是:上下文从哪来、结果怎么验、每一轮提交怎么收口。本文从实践角度串一条链路:用 MCP 把仓库与外部能力接到 Agent 上,再用 Skill 把「怎么评转换质量、怎么总结变更、怎么对照需求、怎么看代码质量」固化成可重复执行的检查步骤。

说明:下文中的「转换」泛指在统一业务语义下,把一侧平台的实现(Kotlin/Swift/ArkTS 等)迁到另一侧或生成可对照骨架;不是宣称存在某种全自动、零成本的万能翻译器。MCP 与 Skill 解决的是流程与门禁,不是替代架构决策与人工验收。


1. 问题长什么样

典型诉求可以概括成四类:

方向 常见场景
Android → iOS 业务模块、网络层、存储、部分 UI 逻辑迁移到 Swift / SwiftUI
iOS → Android 对照实现 Kotlin / Jetpack,对齐生命周期与线程模型
与鸿蒙互转 ArkTS / ArkUI 与双端对齐能力差异(线程、权限、分布式、声明式 UI)
多向迭代 同一需求在三端分支并行改,需要统一的「需求—实现—差异」视图

纯靠聊天窗口贴代码,很快会遇到:上下文截断、无法读全仓库、无法跑构建与静态检查、无法稳定对比两次提交的差异。这就是 MCP 要接进来的原因。


2. MCP 在这条链路里干什么

MCP(Model Context Protocol) 做的是:让模型通过标准协议调用「工具」和拉取「资源」,而不是把整台电脑或整库文件糊进提示词。

在跨端迁移/转换场景里,常见有价值的 MCP 能力包括(按优先级大致排序):

  1. 代码与版本:读文件、搜索符号、git diff / git log、关联 issue / 需求文档(若你们有对应 MCP 或 HTTP 封装)。
  2. 构建与检查:触发 Gradle / xcodebuild / hvigor(鸿蒙)的非交互命令,采集编译错误与警告(需沙箱与安全策略)。
  3. 静态分析:对接 linter、格式化结果、甚至你们内部的二进制接口规范检查脚本。
  4. 知识库:设计文档、接口契约、历史 CR 结论——用 resource 或检索类工具喂给模型当约束,而不是当闲聊材料。

一张很粗的协作关系如下:

Agent(IDE / CLI) 对话与计划 MCP 服务 Git / 文件 构建与日志 Lint / 测试 需求与文档 工程仓库 Android / iOS / Harmony 模块

要点:MCP 负责「能持续拿到真实状态」;模型负责在约束下改代码。没有 MCP,跨端转换很容易变成「语法像、跑不通」的演示。

项目实践里 MCP 真正帮上忙的一块

在自己项目里跑下来,MCP 带来的提效很实在的一点,是支撑 大块功能在端之间的直接互转:网络、状态、一条完整业务链路里的多个类/模块,在接好仓库与构建/日志之后,Agent 能按「功能点」连续改,而不是只在聊天里贴片段。这和「整仓库一键翻译」不是一回事,但已经能覆盖不少成块迁移的工作。

转换落地之后,仍然需要人按需求自己多走几遍:主路径、边界条件、各端差异(权限、后台、埋点)往往要手动点一点才暴露。这里不必讳言——人工走查仍是验收的一环。差别在于:一旦发现问题,把报错、堆栈、复现步骤丢回给 AI,在 MCP 能读到工程上下文的前提下,修一轮的成本明显低于从零手改。整体仍是:大功能先转过去 → 人按需求验收 → 卡点再交给 AI 迭代,提效很多,但预期是「加速器」,不是「免验收」。


3. 转换工作流建议(可落地的一版)

下面是一条在团队里较容易推广的最小闭环,可按你们 CI 成熟度裁剪。

对齐需求与范围 建立对照表:模块/接口/平台差异 分支策略:按端或按业务垂直切 大功能点互转 + 本地构建 人按需求走查多遍 把现象与日志交给 AI 修复 门禁:Skill 清单跑一遍 PR:说明对照与风险 发现问题 修复后回到转换 通过 不通过 通过
  1. 对齐需求与范围:哪些类/模块要迁、哪些只做适配层、哪些必须行为一致(加密、存储、埋点)。
  2. 对照表:把「Android 类 / iOS 类型 / ArkTS 组件」映射到同一张表,避免三端各写各的命名与语义漂移。
  3. 分支:要么「一需求一分支三端子目录」,要么「按端拆分支但共享契约文档」——关键是 diff 可读、review 可追责。
  4. 大功能点转换 + 构建优先:先争取「能编过、主链路能跑」;报错与日志进 Agent 上下文(MCP),便于迭代大块改动而不是零散粘贴。
  5. 人工按需求走查:对照验收条目多点几遍;出问题再 提醒 AI 修(复现步骤 + 日志/截图),形成短闭环,而不是指望一次生成就结项。
  6. 门禁再走 PR:下文 Skill 部分把这一步拆成可执行检查项。

4. 用 Skill 做「质量门禁」:评什么、怎么评

Skill 在这里不是替你做代码审查的人,而是把「每次该怎么审」写成稳定流程,让 Agent 和人类用同一套清单。可以与 .cursor/rules 分工:规则偏常驻约束,Skill 偏「进入迁移任务时按需加载」。

下面四类,对应你提到的诉求。

4.1 评估「转换质量」(对照与语义)

建议在 Skill 里写清输入与输出:

  • 输入:源端关键文件路径、目标端对应路径、需求编号或用户故事摘要。
  • 输出:一张结构化表,至少包含:
    • API / 行为对照:异步模型(Kotlin 协程 vs Swift async vs Promise)、主线程约束、错误类型是否可对应。
    • 平台差异显式声明:例如 Keychain vs Keystore vs 鸿蒙安全存储、推送与后台任务差异。
    • 已知妥协:哪些为赶进度采用临时方案,必须带 TODO(@owner, deadline)。

转换质量不是「看起来像」,而是「在约束下可维护且可验证」。

4.2 总结「每次提交的变更内容」

与其让模型自由发挥写 PR 描述,不如在 Skill 里规定:

  • 固定调用 git diff / git show(通过 MCP)得到事实;
  • 再按模板归纳:文件级摘要 → 行为变化 → 风险点 → 测试建议;
  • 禁止把「推测的业务意图」写进摘要当事实(除非文档里有依据)。

这样每一轮提交都有可比对、可审计的变更说明,也方便后面做需求完成度对照。

4.3 评估「需求完成度」

把需求拆成可勾选条目(验收标准),在 Skill 中要求:

  • 每条对应 commit 或 PR 中的证据(实现位置、测试用例、或文档更新);
  • 未完成项必须标 阻塞原因(依赖、接口未就绪、平台能力缺失)。

没有结构化条目,「差不多做完了」无法复盘。

4.4 评估「提交代码的质量」

建议在 Skill 里分三层,由浅入深:

层级 内容示例
机械层 格式化、命名、明显死代码、错误处理分支是否完整
工程层 日志是否泄露敏感信息、并发与生命周期是否匹配平台习惯
语义层 是否与对照表一致、边界条件是否与源端测试意图对齐

静态检查能覆盖机械层与部分工程层;语义层需要测试或人工 spot check。Skill 里应写明:哪些必须跑命令、哪些允许仅人工。


5. 示例:Skill 里可以长什么样(片段)

下面是一段示意性的 Markdown 结构,便于你落到仓库里的 SKILL.md(真实项目请补全 name、description 与触发条件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
name: cross-platform-migration-review
description: Android/iOS/鸿蒙互迁时的对照审查与提交摘要
---

## 何时使用
用户提到跨端迁移、对照实现、ArkTS/Kotlin/Swift 同步修改时加载本 Skill。

## 步骤
1. 用 MCP 读取当前分支相对 main 的 diff 与最近 N 次提交信息。
2. 对照 `docs/platform-mapping.md`(若存在)检查涉及模块是否登记。
3. 按「转换质量 / 变更摘要 / 需求条目 / 代码质量」四节输出,缺信息则列出待补充问题。
4. 若涉及鸿蒙,显式检查线程与权限相关 API 是否与文档一致。

## 输出模板(节选)
### 变更摘要(基于 diff)
- 范围:...
- 行为变化:...
- 风险:...

### 需求完成度
| 条目 | 状态 | 证据 |

实际仓库里你会把路径、分支名、文档名换成自己的约定。


6. 局限与诚实边界

  • MCP 不是魔法:没有构建环境、没有测试、没有清晰需求,再好的协议也只能产出「更像代码的文本」。
  • 三端互转在工程上常卡在 能力不对等(系统服务、商店政策、声明式 UI 数据流),Skill 应要求把这些写进「已知妥协」,而不是一笔带过。
  • 安全:能执行命令的 MCP 必须限权、审计;不要把生产密钥放进可被模型读取的资源。

7. 小结

  • MCP 解决的是:让 Agent 稳定读仓库、跑检查、对齐事实,跨端转换才有可持续的闭环。
  • Skill 解决的是:把 转换质量、变更摘要、需求完成度、代码质量 变成可重复执行的评审步骤,减少「同一类问题每次重新口头讲一遍」。

若你已经在用 Cursor / Claude Code 一类工具,优先落地的往往是:一个最小 MCP 集合(git + 终端或 CI 日志)+ 一个迁移审查 Skill;再逐步把需求条目与对照表接进来。和自己在项目里的做法一样:大功能点先互转、人按需求走查、问题再丢给 AI,比一上来追求「全自动三端零走查」更实在,也更容易稳定提效。


本文为工程流程向说明,具体工具链与 MCP Server 名称以各厂商文档为准;实施时请结合团队安全与合规要求。

Android KMP(Kotlin Multiplatform)

由浅入深,从基本概念到源码原理,再到实战示例与生产级应用案例,系统梳理 Kotlin 跨平台开发之道


一、基础概念

1.1 什么是 Kotlin Multiplatform(KMP)?

Kotlin Multiplatform(又称 KMP 或 KMM)是 JetBrains 推出的跨平台代码共享方案,其核心理念是:共享业务逻辑,保留原生 UI。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────────────────┐
│ KMP 架构:共享逻辑,原生 UI │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Android │ │ iOS │ │ Web │ │
│ │ (Kotlin) │ │ (Swift) │ │ (JS/WasM) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ │ 原生 UI 层 │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 共享模块 (shared / commonMain) │ │
│ │ 业务逻辑 · 网络请求 · 数据模型 · 存储 · ViewModel │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

与 Flutter、React Native 等「一套 UI 到处跑」的方案不同,KMP 让你:

  • 用 Kotlin 写共享的业务逻辑、网络、数据层
  • 用 Jetpack Compose 写 Android UI,用 SwiftUI 写 iOS UI
  • 得到的是 原生体验,而非 WebView 或自绘引擎

1.2 为什么需要 KMP?

痛点 KMP 的解决方式
Android / iOS 重复实现业务逻辑 共享 ViewModel、UseCase、Repository 等
双端行为不一致(如计算逻辑) 同一套 Kotlin 代码,编译到各平台
双端各自维护一套网络/序列化代码 共享 Ktor + kotlinx.serialization
希望跨平台但不牺牲原生体验 保留原生 UI 框架
团队已有 Kotlin 基础 复用技能栈,降低学习成本

1.3 KMP 与主流跨平台方案对比

维度 KMP Flutter React Native
UI 方案 原生 UI(Compose / SwiftUI) 自绘引擎(Skia) 原生组件 + Bridge
共享范围 业务逻辑、网络、数据 UI + 逻辑全部共享 UI + 逻辑共享
性能 接近原生 接近原生 依赖 JS Bridge
包体积 共享库较小 需携带 Flutter 引擎 需携带 RN 运行时
生态 Kotlin 生态、官方支持 Dart/Flutter 生态 JS/React 生态
学习曲线 Kotlin 开发者友好 需学 Dart 需学 React/JS

适用场景:KMP 更适合「业务逻辑复杂、希望 UI 保持原生」的应用,如金融、电商、工具类 App。


二、核心原理

2.1 编译模型

KMP 将 Kotlin 代码编译成各平台的原生产物,而不是通过虚拟机或解释器运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                    ┌──────────────────┐
│ commonMain │
│ (共享 Kotlin) │
└────────┬─────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ JVM / Android │ │ Kotlin/Native │ │ Kotlin/JS │
│ (.class/DEX) │ │ (LLVM → .a) │ │ (JS/Wasm) │
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
▼ ▼ ▼
Android APK iOS Framework Web Bundle
  • Android:Kotlin → JVM 字节码 → DEX → APK
  • iOS:Kotlin → Kotlin/Native(LLVM)→ 静态库/框架
  • Web:Kotlin → JavaScript 或 WebAssembly

因此,共享代码在运行时就是本地代码,无额外解释层。

2.2 expect / actual 机制

当共享代码需要调用平台特有 API 时(如文件系统、UUID、日期格式化),KMP 使用 expect / actual 做编译期抽象:

在 commonMain 中声明 expect(契约):

1
2
3
4
5
// commonMain/kotlin/platform/Platform.kt
package platform

expect fun currentTimeMillis(): Long
expect fun randomUUID(): String

在各平台提供 actual 实现:

1
2
3
4
5
6
7
// androidMain/kotlin/platform/Platform.android.kt
package platform

import java.util.UUID

actual fun currentTimeMillis(): Long = System.currentTimeMillis()
actual fun randomUUID(): String = UUID.randomUUID().toString()
1
2
3
4
5
6
7
8
// iosMain/kotlin/platform/Platform.ios.kt
package platform

import platform.Foundation.NSDate
import platform.Foundation.NSUUID

actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong()
actual fun randomUUID(): String = NSUUID().UUIDString()

原理要点:

  • 编译时,编译器将 expect 与对应平台的 actual 匹配
  • 每个目标平台都必须有且仅有一个 actual 实现
  • 保证 common 代码只能依赖抽象,无法引用平台专属 API

2.3 Source Sets(源集)与层级结构

KMP 用 Source Set 组织代码,每个源集对应一组目标平台:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────────┐
│ Source Sets 层级 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ commonMain ◄── 编译到所有目标,只可写平台无关代码 │
│ │ │
│ ├── androidMain ◄── 仅 Android │
│ │ │
│ ├── iosMain (中间源集) ◄── 所有 iOS 目标共享 │
│ │ ├── iosArm64Main (真机) │
│ │ └── iosSimulatorArm64Main (模拟器) │
│ │ │
│ └── appleMain (中间源集) ◄── iOS + macOS + watchOS + tvOS │
│ │
└─────────────────────────────────────────────────────────────────┘

依赖方向:androidMain → commonMain,iosMain → commonMain,commonMain 不能依赖平台源集。

典型目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
shared/
├── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ ├── domain/
│ │ ├── data/
│ │ └── di/
│ ├── androidMain/
│ │ └── kotlin/
│ └── iosMain/
│ └── kotlin/
└── build.gradle.kts

三、源码与实现原理

3.1 expect / actual 的编译期处理

expect/actual 并非运行时多态,而是编译期替换:

  1. 在 common 编译时,expect 仅作为「占位声明」参与类型检查
  2. 在平台编译时,编译器用对应平台的 actual 替换 expect
  3. 最终产物中只存在 actual 实现,无额外抽象开销

这保证了共享代码在目标平台上等价于「直接调用平台 API」。

3.2 中间源集(Hierarchical Source Sets)

当多个平台共享同一套「非 common」逻辑时,可引入中间源集,避免重复:

1
2
3
4
5
6
7
// 声明目标
kotlin {
android()
iosArm64()
iosSimulatorArm64()
macosArm64()
}

Kotlin 插件会自动生成:

  • appleMain:所有 Apple 平台共享(iOS + macOS + watchOS + tvOS)
  • iosMain:iOS 真机 + 模拟器共享

在 appleMain 中可以使用 Apple 专属 API(如 platform.Foundation.NSUUID),而无需在 iosArm64Main、iosSimulatorArm64Main 里各写一遍。

3.3 依赖解析规则

1
2
3
4
5
6
7
8
9
10
11
12
13
commonMain 只能依赖:
- Kotlin 标准库(多平台版本)
- kotlinx-* 多平台库(coroutines, serialization, datetime 等)
- 其他 commonMain 或「包含当前目标」的中间源集

androidMain 可额外依赖:
- Android SDK
- Jetpack 库
- 纯 JVM 库

iosMain 可额外依赖:
- Kotlin/Native 平台库
- CocoaPods 依赖

四、实战示例

4.1 项目搭建

根 build.gradle.kts:

1
2
3
4
5
plugins {
kotlin("multiplatform") version "2.0.0" apply false
kotlin("plugin.serialization") version "2.0.0" apply false
id("com.android.application") version "8.2.0" apply false
}

shared/build.gradle.kts(共享模块):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
}

kotlin {
androidTarget()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}

sourceSets {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.6")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.6")
}
}
}

4.2 网络层:Ktor + kotlinx.serialization

commonMain - 数据模型与 API:

1
2
3
4
5
6
7
8
9
10
11
12
// commonMain/kotlin/data/model/User.kt
import kotlinx.serialization.Serializable

@Serializable
data class User(
val id: Long,
val name: String,
val email: String
)

@Serializable
data class ApiResponse<T>(val data: T)
1
2
3
4
5
6
7
8
9
10
11
12
13
// commonMain/kotlin/data/remote/UserApi.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*

class UserApi(private val client: HttpClient) {
suspend fun getUser(id: Long): User {
return client.get("https://api.example.com/users/$id") {
contentType(ContentType.Application.Json)
}.body()
}
}

commonMain - Ktor 客户端工厂:

1
2
3
4
5
6
7
8
9
10
11
12
// commonMain/kotlin/di/NetworkModule.kt
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

expect fun createHttpClient(): HttpClient

fun createJson(): Json = Json {
ignoreUnknownKeys = true
isLenient = true
}

androidMain - OkHttp 引擎:

1
2
3
4
5
6
7
8
9
10
// androidMain/kotlin/di/NetworkModule.android.kt
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.contentnegotiation.*

actual fun createHttpClient(): HttpClient = HttpClient(OkHttp) {
install(ContentNegotiation) {
json(createJson())
}
}

iosMain - Darwin 引擎:

1
2
3
4
5
6
7
8
9
10
// iosMain/kotlin/di/NetworkModule.ios.kt
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
import io.ktor.client.plugins.contentnegotiation.*

actual fun createHttpClient(): HttpClient = HttpClient(Darwin) {
install(ContentNegotiation) {
json(createJson())
}
}

4.3 ViewModel 共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// commonMain/kotlin/presentation/UserViewModel.kt
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.*

class UserViewModel(
private val userApi: UserApi,
private val scope: CoroutineScope
) {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()

fun loadUser(id: Long) {
scope.launch {
_user.value = userApi.getUser(id)
}
}
}
  • Android:注入 viewModelScope,通过 viewModel() 获取
  • iOS:注入 MainScope() 或 ViewModelScope,通过 KMP 的 StateFlow 订阅

两端共享同一套 ViewModel 逻辑,仅 CoroutineScope 由各平台注入。

4.4 expect/actual 实用示例:日期格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// commonMain
expect fun formatDate(timestamp: Long): String

// androidMain
actual fun formatDate(timestamp: Long): String {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return sdf.format(Date(timestamp))
}

// iosMain
actual fun formatDate(timestamp: Long): String {
val formatter = NSDateFormatter().apply {
dateFormat = "yyyy-MM-dd"
}
return formatter.stringFromDate(NSDate(timestamp = timestamp / 1000.0))
}

五、实际项目应用案例

5.1 Cash App(Block 旗下金融应用)

项目规模 50+ 移动工程师,约 3000 万月活
策略 业务共享、UI 原生
时间线 自 2018 年起试验 KMP,逐步通过 Feature Flag 落地
效果 移除有问题的 JavaScript 共享代码;双端维护单一业务逻辑库;服务器团队(Kotlin)可直接参与共享库开发
使用库 SQLDelight、Wire、CrashKiOS

启示:大型金融场景下,KMP 在「持久化」和「纯函数业务逻辑」上收益最大,且便于服务端参与共享代码。

5.2 Netflix

  • 在移动工作室 App 中共享逻辑,减少重复开发
  • 在影视制作的高节奏迭代下,提升交付效率与稳定性

5.3 McDonald’s

  • 共享应用内支付等复杂业务逻辑
  • 支撑每月 650 万+ 笔支付
  • 验证 KMP 在电商/支付场景的可行性

5.4 Forbes

  • 在 iOS 与 Android 间共享 80%+ 逻辑
  • 双端可同步上线新功能,缩短发布周期

5.5 其他采用者

  • Wrike、Bilibili、Feres 等使用 KMP + Compose Multiplatform
  • Vouched、Karma 等采用 KMP 共享核心业务

六、最佳实践与注意事项

6.1 共享范围建议

适合共享 不建议共享
数据模型、DTO 平台特有 UI 组件
网络请求、序列化 复杂平台动画、手势
Repository、UseCase、ViewModel 平台特定系统 API 封装
业务规则、校验逻辑 第三方 SDK 深度集成
本地存储(SQLDelight、DataStore) 推送、支付等强平台相关逻辑

6.2 常见陷阱

  1. 在 commonMain 中引用平台 API:编译器会报错,应使用 expect/actual 抽象
  2. expect/actual 签名不一致:必须保持完全一致(包名、函数名、参数、返回值)
  3. 并发与线程:Kotlin/Native 对线程模型有约束,需注意 @ThreadLocal、freeze 等
  4. 依赖版本:多平台库需保证各目标使用兼容版本

6.3 学习路线建议

  1. 搭建最小 KMP 工程,跑通 Android + iOS
  2. 用 expect/actual 封装 1~2 个平台 API
  3. 接入 Ktor + kotlinx.serialization 实现共享网络层
  4. 共享一个 ViewModel,在双端展示数据
  5. 按业务模块逐步迁移,避免一次性大改

七、小结

KMP 以 「共享逻辑、原生 UI」 为核心,通过 expect/actual 和 Source Sets 实现跨平台抽象,在不牺牲原生体验的前提下显著降低双端重复开发。从 Cash App、Netflix、McDonald’s 等实践来看,KMP 已可用于生产环境,特别适合业务逻辑复杂、对一致性与性能要求较高的应用。结合 Jetpack Compose 与 SwiftUI,KMP 正成为 Android 开发者拓展 iOS 能力的重要路径。


参考资料

跨平台状态管理:React / Next.js / Flutter / Swift / RxSwift / SwiftUI / ArkTS / RxJava / Agera / Jetpack Compose

由浅入深,从基本概念到源码原理,再到实战案例,系统梳理 Web、移动、原生各平台的状态管理方案


一、什么是状态管理?

1.1 状态(State)的本质

状态是应用在某一时刻的数据快照,它决定了 UI 的展示内容和行为。任何会随时间变化的数据——用户输入、网络请求结果、权限、主题——都可视为状态。

1
2
3
4
5
6
7
8
9
+------------+   +------------+   +------------+   +------------+
| User Input | | Biz Data | | UI State | | Server |
| Form/Search| | List/Detail| |Modal/Load | | Cache |
+------+-----+ +------+-----+ +------+-----+ +------+-----+
| | | |
+-----------------+--------+--------+-----------------+
|
v
UI 渲染 / 副作用执行

用户输入·业务数据·UI状态·服务端 → 汇聚为统一的 UI 渲染与副作用执行

1.2 为什么需要状态管理?

痛点 说明 状态管理的价值
散落 状态分散在各处,难以追踪 集中、可预测的数据流
同步 多处 UI 依赖同一数据,容易不一致 单一数据源(Single Source of Truth)
生命周期 状态何时创建、何时销毁、何时持久化 与组件/页面生命周期绑定
跨层级 深层级组件需要访问顶层状态 提供 Context / Provider / 依赖注入
可测试 业务逻辑与 UI 耦合,难以单测 状态逻辑可独立测试

1.3 各平台状态管理方案概览

平台/框架 主要方案 核心机制 特点
React Hooks (useState/useReducer) 虚拟 DOM diff + 依赖收集 函数式、声明式、生态丰富
Next.js Server Components + Client State 服务端/客户端分离 减少客户端 JS、流式渲染
Flutter Provider / Riverpod / Bloc InheritedWidget / Listenable Dart 异步流、可组合
Swift Combine / 手动 KVO 发布-订阅 系统级、与 SwiftUI 整合
RxSwift Observable / Subject 响应式流 操作符丰富、事件驱动
SwiftUI @State / @Binding / @Observable 声明式 + 属性包装器 数据驱动视图、自动重绘
ArkTS @State / @Prop / @Link 装饰器 + 响应式 鸿蒙声明式 UI
RxJava Observable / Subject 响应式流 异步编排、背压支持
Agera Observable / Updatable 推事件-拉数据 Google 早期方案,已归档
Jetpack Compose remember / mutableStateOf 重组(Recomposition) 声明式、与 Kotlin 协程整合

二、状态管理的核心原则与模式

2.1 单向数据流(Unidirectional Data Flow)

大多数现代框架采用「单向数据流」:状态向下流动,事件向上反馈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────┐
│ State │ ← 单一数据源
└──────┬──────┘
│ 只读传递
▼
┌─────────────┐
│ View │ → 用户操作
└──────┬──────┘
│ 事件 / Action
▼
┌─────────────┐
│ Reducer / │ → 计算新状态
│ setState │
└──────┬──────┘
│
└──────────► 更新 State → 重新渲染

React、Redux、Flutter Bloc、Jetpack Compose 的 ViewModel 均遵循此模式。

2.2 观察者模式:统一的底层基石

状态管理大多基于观察者模式:被观察对象变化时,通知所有订阅者。

平台 被观察者 观察者 通知方式
React useState 返回值 组件函数 调度重渲染
SwiftUI @State / @Published 视图 body 自动重算 body
Jetpack Compose mutableStateOf Composable 重组(Recomposition)
Flutter ChangeNotifier addListener notifyListeners
RxJava Observable Observer onNext
ArkTS @State 变量 组件 build 框架触发重绘

2.3 局部状态 vs 全局状态

类型 范围 典型方案 场景
局部状态 单组件/单页面 useState / @State / remember 输入框、折叠面板、动画
共享状态 多组件/跨页面 Context / Provider / ViewModel 主题、用户信息、购物车
服务端状态 与后端同步 React Query / SWR / 自定义 列表、详情、缓存

三、Web 前端:React 与 Next.js

3.1 React 状态管理

3.1.1 基本概念:useState

useState 是 React 最基础的状态 Hook,返回当前值和更新函数:

1
2
3
4
5
6
7
8
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}

原理简述:React 在内部维护 Fiber 树,每个组件对应一个 Fiber 节点,useState 将状态存储在 Fiber 的 memoizedState 链表中。更新时调度 setState,标记组件需要重渲染,之后执行 diff 并提交 DOM 变更。

3.1.2 派生状态与 useReducer

当状态逻辑复杂时,用 useReducer 集中处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
default: return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}

3.1.3 跨组件共享:Context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ThemeContext = createContext('light');

function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Main />
</ThemeContext.Provider>
);
}

function Header() {
const { theme } = useContext(ThemeContext);
return <div className={theme}>...</div>;
}

注意:Context 变化会导致所有消费该 Context 的组件重渲染,可配合 useMemo / 拆分 Provider 优化。

3.1.4 副作用与依赖:useEffect

1
2
3
4
5
6
7
8
9
10
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);

useEffect(() => {
if (keyword.length < 2) return;
const timer = setTimeout(() => {
fetchUsers(keyword).then(setUsers);
}, 300);
return () => clearTimeout(timer); // 清理函数
}, [keyword]);

3.2 Next.js 状态管理

Next.js 引入 Server Components 与 Client Components 的区分,状态管理策略随之变化。

3.2.1 服务端 vs 客户端状态

类型 组件 可用能力 典型场景
Server Component 默认 直接 fetch、访问 DB、无 hooks 静态内容、SEO、首屏数据
Client Component 'use client' useState、useEffect、事件处理 交互、表单、实时更新

3.2.2 组合模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// app/page.tsx - Server Component(默认)
async function Page() {
const data = await fetchFromDB(); // 服务端直接拉取
return (
<Layout>
<StaticContent data={data} />
<InteractiveCart /> {/* Client Component,内部用 useState */}
</Layout>
);
}

// components/InteractiveCart.tsx
'use client';
export function InteractiveCart() {
const [items, setItems] = useState([]);
return <CartUI items={items} onAdd={...} />;
}

3.2.3 Context 在 Next.js 中的使用

Context Provider 必须放在 Client Component 中:

1
2
3
4
5
6
7
8
9
10
// providers/ThemeProvider.tsx
'use client';
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}

四、跨平台移动:Flutter

4.1 基本概念

Flutter 的 UI 是声明式的:给定状态,构建对应 Widget 树。状态变化触发 setState 或 notifyListeners,进而重建 Widget。

4.2 内置方案

4.2.1 StatefulWidget + setState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => setState(() => _count++),
child: Text('$_count'),
);
}
}

4.2.2 ValueNotifier + ValueListenableBuilder(局部重建)

1
2
3
4
5
6
7
final counter = ValueNotifier<int>(0);

ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, child) => Text('$value'),
)
// 只有 ValueListenableBuilder 会重建,而非整棵子树

4.2.3 ChangeNotifier + Provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
void add(Item item) {
_items.add(item);
notifyListeners();
}
}

// 根节点
ChangeNotifierProvider(create: (_) => CartModel(), child: MyApp())

// 子节点消费
context.watch<CartModel>(); // 监听变化并重建
context.read<CartModel>(); // 仅读取,不监听

4.3 进阶:Riverpod / Bloc

  • Riverpod:编译期安全、可测试、不依赖 BuildContext
  • Bloc:事件 → 状态 的显式映射,适合复杂业务流

五、iOS 原生:Swift、RxSwift、SwiftUI

5.1 Swift 传统方式

  • 属性观察器:willSet / didSet
  • KVO:observe(_:options:changeHandler:)
  • 通知:NotificationCenter
  • Delegate / 闭包回调

5.2 RxSwift 响应式状态

RxSwift 将状态抽象为流,通过 Observable / Subject 管理:

1
2
3
4
5
6
7
8
9
10
11
// 文本框输入 → 搜索
let searchResults = searchTextField.rx.text.orEmpty
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { api.search($0) }
.observe(on: MainScheduler.instance)

searchResults
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { _, model, cell in
cell.textLabel?.text = model.name
}
.disposed(by: disposeBag)

Subject 可同时作为观察者和被观察者,常用于「桥接」命令式代码与响应式流:

1
2
3
4
5
6
7
8
let buttonTaps = PublishSubject<Void>()
buttonTaps
.flatMapLatest { api.fetchData() }
.subscribe(onNext: { updateUI($0) })
.disposed(by: disposeBag)

// 命令式触发
button.rx.tap.bind(to: buttonTaps).disposed(by: disposeBag)

5.3 SwiftUI 声明式状态

5.3.1 @State:组件内部值类型状态

1
2
3
4
5
6
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") { count += 1 }
}
}

5.3.2 @Binding:父子双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
struct ParentView: View {
@State private var isOn = false
var body: some View {
ToggleView(isOn: $isOn) // 传递 Binding
}
}

struct ToggleView: View {
@Binding var isOn: Bool
var body: some View {
Toggle("", isOn: $isOn)
}
}

5.3.3 @ObservedObject / @StateObject:引用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var isLoading = false
}

struct ProfileView: View {
@StateObject private var viewModel = UserViewModel() // 创建并持有
var body: some View {
Text(viewModel.name)
}
}

struct ChildView: View {
@ObservedObject var viewModel: UserViewModel // 从父组件传入
var body: some View { ... }
}

5.3.4 @Observable(iOS 17+)

新宏 @Observable 可替代 ObservableObject,更简洁:

1
2
3
4
5
6
7
8
9
10
11
12
@Observable
class User {
var name: String = ""
var age: Int = 0
}

struct UserView: View {
@State private var user = User()
var body: some View {
Text(user.name) // 自动追踪依赖
}
}

六、Android 原生:RxJava、Agera、Jetpack Compose

6.1 RxJava 响应式状态

1
2
3
4
5
6
7
8
9
val querySubject = PublishSubject.create<String>()
val results = querySubject
.debounce(300, TimeUnit.MILLISECONDS)
.filter { it.length >= 2 }
.switchMap { api.search(it).toObservable() }
.observeOn(AndroidSchedulers.mainThread())

results.subscribe { updateUI(it) }.addTo(compositeDisposable)
querySubject.onNext(editText.text.toString())

StateFlow / SharedFlow(Kotlin Flow)是官方推荐替代 LiveData 的方案:

1
2
3
4
5
6
7
8
9
10
class ViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

fun loadData() {
viewModelScope.launch {
_uiState.value = UiState.Success(repository.fetch())
}
}
}

6.2 Agera 简介(已归档)

Agera 是 Google 早期的轻量级响应式库,采用推事件、拉数据模型:

  • Observable:广播事件
  • Updatable:监听事件,从 Repository 拉取数据
1
2
3
4
5
6
7
8
9
// 概念示例(Agera 已归档,仅作了解)
val repository = Repositories.repositoryWithInitialValue(initialData)
.observe()
.onUpdatesPerLoop()
.getFrom { fetchFromNetwork() }
.compile()

repository.addUpdatable(updatable)
// 事件触发时,Updatable 从 Repository 拉取最新数据

注意:Agera 已于 2023 年 3 月归档,新项目建议使用 Kotlin Flow / StateFlow。

6.3 Jetpack Compose 状态管理

6.3.1 remember + mutableStateOf

1
2
3
4
5
6
7
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
  • remember:在重组间保持值,避免每次重组重新创建
  • mutableStateOf:创建可观察状态,读该状态的 Composable 会在值变化时重组

6.3.2 状态提升(State Hoisting)

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
Child(count = count, onCountChange = { count = it })
}

@Composable
fun Child(count: Int, onCountChange: (Int) -> Unit) {
Button(onClick = { onCountChange(count + 1) }) {
Text("$count")
}
}

6.3.3 ViewModel + StateFlow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow(MyState())
val state: StateFlow<MyState> = _state.asStateFlow()

fun update() {
_state.update { it.copy(...) }
}
}

@Composable
fun Screen(viewModel: MyViewModel = viewModel()) {
val state by viewModel.state.collectAsStateWithLifecycle()
// 使用 state
}

6.3.4 配置变更保留:rememberSaveable

1
2
var count by rememberSaveable { mutableStateOf(0) }
// 屏幕旋转等配置变更后,count 会保留

七、鸿蒙:ArkTS

7.1 装饰器驱动的状态

ArkTS 通过装饰器声明「可观察」状态,状态变化自动触发 UI 更新。

7.1.1 @State:组件内部状态

1
2
3
4
5
6
7
8
9
10
11
12
@Entry
@Component
struct Counter {
@State count: number = 0
build() {
Column() {
Text(`Count: ${this.count}`)
Button('+1')
.onClick(() => { this.count++ })
}
}
}

7.1.2 @Prop:父 → 子单向

1
2
3
4
5
6
7
8
9
10
@Component
struct Child {
@Prop value: number // 父组件传入,子组件只读
build() {
Text(`${this.value}`)
}
}

// 父组件
Child({ value: this.count })

7.1.3 @Link:父子双向

1
2
3
4
5
6
7
8
9
10
@Component
struct Child {
@Link value: number // 子组件修改会同步回父组件
build() {
Button('+1').onClick(() => { this.value++ })
}
}

// 父组件
Child({ value: $count }) // $ 语法传递引用

7.1.4 @Provide / @Consume:跨层级

1
2
3
4
5
// 祖先
@Provide('theme') theme: string = 'light'

// 任意后代
@Consume('theme') theme: string

7.1.5 @Observed + @ObjectLink:嵌套对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Observed
class User {
name: string
constructor(name: string) { this.name = name }
}

@Component
struct UserView {
@ObjectLink user: User
build() {
Text(this.user.name)
.onClick(() => { this.user.name = 'New' }) // 触发更新
}
}

7.2 状态管理 V2(@ObservedV2 + @Trace)

V2 支持深层属性观察,解决嵌套对象内部属性不可观察的问题。


八、跨平台对比与选型

8.1 概念映射表

概念 React Flutter SwiftUI Jetpack Compose ArkTS
组件内状态 useState State @State remember + mutableStateOf @State
父子单向 props 构造函数参数 普通参数 参数 @Prop
父子双向 回调 + props 回调 @Binding 回调 + 参数 @Link
跨层级 Context Provider/InheritedWidget EnvironmentObject CompositionLocal @Provide/@Consume
引用类型 useRef/Context ChangeNotifier @ObservableObject ViewModel @Observed+@ObjectLink

8.2 选型建议

场景 推荐方案
简单 UI 状态 各平台内置(useState / @State / remember)
跨组件共享 Context / Provider / ViewModel / @Provide
复杂异步流 RxSwift / RxJava / Kotlin Flow
服务端数据 React Query / SWR / ViewModel + Repository
Next.js 全栈 Server Components 拉数据 + Client 管理交互态
新 Android 项目 Jetpack Compose + ViewModel + StateFlow
新 iOS 项目 SwiftUI + @Observable
鸿蒙应用 ArkUI 装饰器体系

九、源码原理浅析

9.1 React:Fiber 与 Hooks 链表

React 在 Fiber 节点上维护 memoizedState 链表,每个 Hook 对应链表中的一个节点:

1
2
3
4
5
Fiber.memoizedState → useState → useEffect → useContext → ...
│
├─ baseState
├─ baseQueue
└─ next (下一个 Hook)

setState 会将更新放入队列,调度器在合适时机执行重渲染,按顺序应用更新,保证 Hooks 调用顺序稳定。

9.2 Jetpack Compose:快照与重组

Compose 使用 Snapshot 系统追踪状态读取:

  1. mutableStateOf 创建 SnapshotMutableState
  2. 读取 state.value 时,当前 Composable 的「重组作用域」会记录对该 state 的依赖
  3. 写入 state.value = x 时,Compose 标记依赖该 state 的作用域需要重组
  4. 重组时重新执行对应 Composable,得到新 UI
1
读取 state → 记录依赖 → 写入 state → 标记无效 → 调度重组 → 重新执行 Composable

9.3 SwiftUI:@State 与依赖追踪

SwiftUI 在编译期和运行期结合,追踪 body 中对 @State 等属性的访问。当 @State 变化时,视图的 body 会重新求值,生成新的 View 描述,再与旧描述 diff 后更新真实视图。

9.4 ArkTS:装饰器与更新调度

@State 等装饰器在编译期生成观察逻辑,运行时状态变化会标记组件为脏,下一帧统一执行 build 更新 UI,类似 Flutter 的 setState + 帧调度。


十、实战案例

10.1 登录表单校验(多字段组合)

需求:用户名 ≥3 字符、密码 ≥6 字符时,登录按钮才可点击。

React:

1
2
3
4
const [user, setUser] = useState('');
const [pwd, setPwd] = useState('');
const canLogin = user.length >= 3 && pwd.length >= 6;
return <Button disabled={!canLogin}>登录</Button>;

SwiftUI:

1
2
3
4
@State private var user = ""
@State private var pwd = ""
var canLogin: Bool { user.count >= 3 && pwd.count >= 6 }
Button("登录") { }.disabled(!canLogin)

Jetpack Compose:

1
2
3
4
var user by remember { mutableStateOf("") }
var pwd by remember { mutableStateOf("") }
val canLogin = user.length >= 3 && pwd.length >= 6
Button(onClick = {}, enabled = canLogin) { Text("登录") }

ArkTS:

1
2
3
4
5
6
@State user: string = ''
@State pwd: string = ''
private get canLogin(): boolean {
return this.user.length >= 3 && this.pwd.length >= 6
}
Button('登录').enabled(this.canLogin)

10.2 搜索防抖 + 取消旧请求

React:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const [keyword, setKeyword] = useState('');
const [results, setResults] = useState([]);

useEffect(() => {
if (keyword.length < 2) return;
const timer = setTimeout(() => {
let cancelled = false;
search(keyword).then(data => {
if (!cancelled) setResults(data);
});
return () => { cancelled = true; };
}, 300);
return () => clearTimeout(timer);
}, [keyword]);

RxSwift:

1
2
3
4
5
6
7
searchTextField.rx.text.orEmpty
.filter { $0.count >= 2 }
.debounce(.milliseconds(300), scheduler: MainScheduler.instance)
.flatMapLatest { api.search($0) }
.observe(on: MainScheduler.instance)
.bind(to: resultsRelay)
.disposed(by: disposeBag)

Jetpack Compose + ViewModel:

1
2
3
4
5
6
val query = MutableStateFlow("")
val results = query
.debounce(300)
.filter { it.length >= 2 }
.flatMapLatest { repository.search(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

10.3 购物车总价实时计算

Flutter (Provider):

1
2
3
4
5
class CartModel extends ChangeNotifier {
final List<CartItem> _items = [];
double get total => _items.fold(0, (sum, item) => sum + item.price * item.qty);
void add(CartItem item) { _items.add(item); notifyListeners(); }
}

React:

1
2
3
4
5
const [items, setItems] = useState([]);
const total = useMemo(
() => items.reduce((s, i) => s + i.price * i.qty, 0),
[items]
);

10.4 Next.js 服务端数据 + 客户端状态

1
2
3
4
5
6
7
8
9
10
// app/product/[id]/page.tsx
export default async function Page({ params }: { params: { id: string } }) {
const product = await fetchProduct(params.id); // 服务端拉取
return (
<div>
<ProductInfo product={product} />
<AddToCartButton productId={product.id} /> {/* 内部用 useState 管理数量 */}
</div>
);
}

十一、总结与最佳实践

11.1 核心要点

维度 共性
本质 状态驱动 UI,变化触发更新
模式 单向数据流、观察者、单一数据源
局部 vs 全局 按范围选择合适的共享机制
生命周期 状态与组件/页面生命周期绑定,避免泄漏

11.2 最佳实践

  1. 最小化状态:能推导的不要存储,用 useMemo / computed / getter
  2. 状态提升:当多组件需要共享时,提升到共同祖先
  3. 不可变更新:避免直接修改,使用 setState/copy/扩展运算符
  4. 副作用清理:useEffect 清理、Disposable.dispose、viewModelScope
  5. 服务端状态分离:与 UI 状态区分,用专门库(React Query 等)管理
  6. 类型安全:TypeScript、Swift、Kotlin 充分利用类型约束状态结构

11.3 各平台快速对照

平台 局部状态 共享状态 异步/流式
React useState Context / Redux useEffect / React Query
Next.js useState (Client) Context (Client) Server Components + fetch
Flutter State / ValueNotifier Provider / Riverpod Stream / Future
SwiftUI @State @ObservableObject / Environment async/await
RxSwift Observable / Subject 同上 + Subject Observable 链
Compose remember + mutableStateOf ViewModel + StateFlow Flow / LaunchedEffect
ArkTS @State @Provide / @Consume Promise / async

参考资源

Trust Wallet Core 功能路径总览:从助记词到多链签名

摘要

Trust Wallet Core 可以理解成一个多链钱包底层库:它主要处理本地的助记词、HD 派生、私钥/公钥、地址、交易/消息编码与签名。它不是 web3.js、ethers.js 那种面向节点 RPC 与合约交互的网络层,也不负责行情、余额索引、广播重试、风控或账户体系。

更准确的一句话是:

Trust Wallet Core 负责把「本地密钥材料」变成「多链地址」和「可广播/可验证的签名结果」;链上参数查询、交易广播、回执轮询和业务风控由 App、后端或 RPC 层完成。

Read more

跨平台与原生交互:Flutter × Electron

由浅入深,从基本概念到源码解析,全面掌握 Flutter 与 Android/iOS/鸿蒙、Electron 与 Mac/Windows 的原生交互能力


一、为什么需要原生交互?

1.1 跨平台框架的局限

跨平台框架(Flutter、Electron、React Native 等)为开发效率带来巨大提升,但受限于自身运行时,无法直接访问所有原生能力:

场景 Flutter 移动端 Electron 桌面端
硬件访问 相机、蓝牙、NFC、传感器 串口、USB、显卡驱动
系统 API 推送、定位、生物识别 剪贴板、系统托盘、原生菜单
第三方 SDK 微信/支付宝支付、地图 企业认证、硬件加密狗
性能敏感 视频编解码、图像处理 大文件加解密、实时音视频
平台特性 iOS Live Activities、Android WorkManager macOS Touch Bar、Windows 通知中心

核心矛盾:跨平台代码运行在「沙箱」中,必须通过桥接层与原生世界通信。

1.2 两种典型架构对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────────┐
│ Flutter 移动端(Android / iOS / 鸿蒙) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Dart (UI/逻辑) ──► Platform Channel ──► Native (Kotlin/Swift/ArkTS) │
│ MethodChannel EventChannel Android/iOS/HarmonyOS API │
│ BasicMessageChannel │
│ │
└─────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────┐
│ Electron 桌面端(Mac / Windows / Linux) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Renderer (HTML/JS) ──► IPC ──► Main Process ──► Native Addon │
│ contextBridge Node-API / FFI │
│ .node / .dll / .dylib │
│ │
└─────────────────────────────────────────────────────────────────────────┘

二、Flutter 与原生交互

2.1 基本概念

2.1.1 Platform Channel 架构

Flutter 的 Platform Channel 是 Dart 与原生代码之间的消息传递机制,底层基于 BinaryMessenger 进行异步二进制通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────────────┐         channel name          ┌──────────────────┐
│ │ ◄────────────────────────► │ │
│ Dart 端 │ ByteData (序列化消息) │ Native 端 │
│ (Client) │ Future/Stream 响应 │ (Host) │
│ │ │ │
│ MethodChannel │ │ MethodChannel │
│ EventChannel │ │ EventChannel │
│ BasicMessageChannel │ BasicMessageChannel │
└──────────────────┘ └──────────────────┘
│ │
│ StandardMessageCodec (JSON-like 序列化) │
│ 支持: null, bool, int, double, String │
│ Uint8List, List, Map │
▼ ▼
Flutter Engine (C++) ──────────────────── Platform Embedder

2.1.2 三种 Channel 类型辨析

Channel 类型 通信模式 典型场景 特点
MethodChannel 请求-响应(RPC) 获取电池电量、调用支付 SDK、打开相机 最常用,一对一调用,返回 Future
EventChannel 流式数据(Stream) 监听传感器、位置更新、蓝牙扫描 单向流,Native → Dart,支持 cancel
BasicMessageChannel 简单消息传递 低层级自定义协议 无方法语义,纯消息收发,较少直接使用

2.1.3 数据类型映射(Dart ↔ Native)

Dart Kotlin/Java Swift/ObjC ArkTS (鸿蒙)
null null nil null
bool Boolean Bool / NSNumber boolean
int (≤32位) Int Int32 number
int (>32位) Long Int64 number
double Double Double number
String String String string
Uint8List ByteArray FlutterStandardTypedData Uint8Array
List List Array Array
Map HashMap Dictionary Object

2.2 MethodChannel 实战:获取电池电量

2.2.1 Dart 端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import 'package:flutter/services.dart';

class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('samples.flutter.dev/battery');
String _batteryLevel = 'Unknown';

Future<void> _getBatteryLevel() async {
try {
final int level = await platform.invokeMethod<int>('getBatteryLevel');
setState(() => _batteryLevel = 'Battery level: $level%');
} on PlatformException catch (e) {
setState(() => _batteryLevel = "Failed: '${e.message}'");
}
}

@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: _getBatteryLevel, child: Text('Get Battery')),
Text(_batteryLevel),
],
);
}
}

要点:

  • Channel 名必须与原生端完全一致
  • invokeMethod 返回 Future,支持泛型
  • 捕获 PlatformException 处理原生端 result.error()

2.2.2 Android (Kotlin)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val level = getBatteryLevel()
if (level != -1) {
result.success(level)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
}
else -> result.notImplemented()
}
}
}

private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}

2.2.3 iOS (Swift)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// AppDelegate.swift
import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(
name: "samples.flutter.dev/battery",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { [weak self] (call, result) in
guard call.method == "getBatteryLevel" else {
result(FlutterMethodNotImplemented)
return
}
self?.receiveBatteryLevel(result: result)
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

private func receiveBatteryLevel(result: FlutterResult) {
let device = UIDevice.current
device.isBatteryMonitoringEnabled = true
if device.batteryState == .unknown {
result(FlutterError(code: "UNAVAILABLE", message: "Battery level not available.", details: nil))
} else {
result(Int(device.batteryLevel * 100))
}
}
}

2.2.4 鸿蒙 (ArkTS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// EntryAbility.ets
import type { FlutterPlugin } from '@ohos/flutter';

export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
const flutterEngine = getFlutterEngine();
const messenger = flutterEngine.getBinaryMessenger();

const channel = new MethodChannel(messenger, 'samples.flutter.dev/battery');
channel.setMethodCallHandler(async (call) => {
if (call.method === 'getBatteryLevel') {
const level = await this.getBatteryLevel();
return level;
}
throw new Error('NotImplemented');
});
}

private async getBatteryLevel(): Promise<number> {
const batteryInfo = await battery.getBatteryInfo();
return batteryInfo.batterySoc; // 0-100
}
}

2.3 EventChannel 实战:监听位置更新

适用于持续从 Native 向 Dart 推送数据的场景。

2.3.1 Dart 端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import 'dart:async';
import 'package:flutter/services.dart';

class LocationService {
static const _channel = EventChannel('samples.flutter.dev/location');

Stream<Location> get locationStream => _channel
.receiveBroadcastStream()
.map((dynamic event) => Location.fromMap(Map<String, dynamic>.from(event as Map)));
}

// 使用
StreamSubscription? _subscription;

void _startListening() {
_subscription = LocationService().locationStream.listen(
(loc) => print('Lat: ${loc.lat}, Lng: ${loc.lng}'),
onError: (e) => print('Error: $e'),
);
}

void _stopListening() {
_subscription?.cancel();
}

2.3.2 Android (Kotlin) - EventChannel.StreamHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class LocationStreamHandler : EventChannel.StreamHandler {
private var eventSink: EventChannel.EventSink? = null
private var locationCallback: LocationCallback? = null

override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
locationCallback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult) {
for (loc in result.locations) {
eventSink?.success(mapOf(
"lat" to loc.latitude,
"lng" to loc.longitude
))
}
}
}
fusedLocationClient.requestLocationUpdates(request, locationCallback!!, Looper.getMainLooper())
}

override fun onCancel(arguments: Any?) {
locationCallback?.let { fusedLocationClient.removeLocationUpdates(it) }
eventSink = null
}
}

// 注册
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/location")
.setStreamHandler(LocationStreamHandler())

2.4 底层原理:BinaryMessenger

所有 Channel 的底层都依赖 BinaryMessenger:

1
2
3
4
5
6
7
8
9
Dart 侧 (binary_messenger.dart)
├── send(channel, message) → Future<ByteData?>
├── setMessageHandler(channel, handler)
└── 消息以 ByteData 形式序列化传输

Native 侧 (Android: DartExecutor, iOS: FlutterBinaryMessenger)
├── 实现 BinaryMessenger 接口
├── 通过 Flutter Engine 与 Dart 隔离
└── 线程:Android 主线程,iOS 主线程

关键点:

  • 消息是异步的,保证 UI 不阻塞
  • 序列化由 StandardMessageCodec 完成(或自定义 MessageCodec)
  • Channel 名称是路由键,用于区分不同功能

2.5 Pigeon:类型安全的 Platform Channel

手动维护 MethodChannel 容易出错(字符串拼写、类型不匹配)。Pigeon 是 Flutter 官方代码生成工具,从接口定义自动生成各端代码。

2.5.1 定义接口 (pigeon/api.dart)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:pigeon/pigeon.dart';

@HostApi()
abstract class BatteryApi {
int getBatteryLevel();
}

@HostApi()
abstract class LocationApi {
void startLocationUpdates();
void stopLocationUpdates();
}

@FlutterApi()
abstract class LocationCallback {
void onLocation(double lat, double lng);
}

2.5.2 生成代码

1
2
3
# pubspec.yaml
dev_dependencies:
pigeon: ^26.0.0
1
flutter pub run pigeon --input pigeon/api.dart

会生成:

  • dart/*.dart:Dart 端接口实现
  • kotlin/*.kt:Android Kotlin 实现骨架
  • swift/*.swift:iOS Swift 实现骨架

2.5.3 优势

对比 手动 MethodChannel Pigeon
类型安全 运行时检查 编译期保证
维护成本 三端同步改 改接口定义即可
代码量 重复样板 自动生成

2.6 实际项目应用案例

案例 1:支付 SDK 接入

1
2
3
4
5
6
7
业务需求:Flutter 调起微信/支付宝支付,并接收支付结果回调

实现要点:
1. MethodChannel 调用 Native 调起支付
2. 支付结果通过 EventChannel 或 MethodChannel.invokeMethod 反向回调
3. Android: Activity Result API,iOS: URL Scheme / Universal Links
4. 注意:支付必须在主线程/主 Activity 完成

案例 2:相机自定义预览 + 拍照

1
2
3
4
5
6
7
业务需求:自定义相机 UI,支持滤镜、闪光灯、变焦

实现要点:
1. 使用 Platform View (AndroidView / UiKitView) 嵌入原生相机 View
2. MethodChannel 控制:开始预览、拍照、切换滤镜
3. EventChannel 或 Texture 传递预览帧(如需实时处理)
4. 注意线程:相机回调可能在子线程,需 Post 到主线程再通过 Channel 回传

案例 3:蓝牙设备扫描与连接

1
2
3
4
5
6
7
业务需求:扫描 BLE 设备,连接并读写特征值

实现要点:
1. EventChannel 持续推送扫描到的设备列表
2. MethodChannel 执行:连接、断开、读/写特征值
3. 连接状态、数据回调通过 EventChannel 流式回传
4. 鸿蒙侧使用 @ohos.bluetooth 相关 API,需申请权限

三、Electron 与原生交互

3.1 基本概念

3.1.1 Electron 架构回顾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────────────┐
│ Renderer Process (多个) │
│ HTML + CSS + JavaScript (前端界面) │
│ 受沙箱限制,无法直接访问 Node.js / 原生模块 │
└───────────────────────────────────┬─────────────────────────────────┘
│ IPC (ipcRenderer / contextBridge)
┌───────────────────────────────────▼─────────────────────────────────┐
│ Main Process (单个) │
│ Node.js 运行时,可 require('fs')、require('原生模块') │
│ 可加载 .node (Native Addon)、调用 FFI │
└───────────────────────────────────┬─────────────────────────────────┘
│ Node-API / N-API
┌───────────────────────────────────▼─────────────────────────────────┐
│ Native Addon / 系统 API │
│ C/C++ 编写,编译为 .node (Unix) / .dll (Windows) │
│ 或通过 FFI 调用已有 .dll / .dylib / .so │
└─────────────────────────────────────────────────────────────────────┘

核心原则:所有原生调用必须在 Main Process 完成,Renderer 通过 IPC 与 Main 通信。

3.1.2 两种原生交互方式

方式 适用场景 技术栈
Native Addon 自己用 C++ 写模块,编译成 .node Node-API / N-API、node-gyp、node-addon-api
FFI 调用已有的 C 库(.dll/.dylib/.so) node-ffi-napi、koffi

3.2 Native Addon 开发(Node-API)

3.2.1 环境准备

macOS:

1
xcode-select --install  # 安装 Xcode Command Line Tools

Windows:

  • 安装 Node.js 时勾选 “Tools for Native Modules”
  • 或通过 npm install -g windows-build-tools 安装 VS Build Tools

依赖:

1
2
npm install node-addon-api bindings
npm install -g node-gyp

3.2.2 项目结构

1
2
3
4
5
6
native-addon/
├── binding.gyp # 构建配置
├── src/
│ └── addon.cc # C++ 源码
├── package.json
└── index.js # 对外导出

3.2.3 binding.gyp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"targets": [{
"target_name": "my_addon",
"sources": [ "src/addon.cc" ],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")"
],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"conditions": [
["OS=='win'", { "msvs_settings": { "VCCLCompilerTool": { "ExceptionHandling": 1 } } }]
]
}]
}

3.2.4 C++ 源码 (src/addon.cc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <napi.h>

Napi::Value Add(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
return env.Null();
}
double a = info[0].As<Napi::Number>().DoubleValue();
double b = info[1].As<Napi::Number>().DoubleValue();
return Napi::Number::New(env, a + b);
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("add", Napi::Function::New(env, Add));
return exports;
}

NODE_API_MODULE(my_addon, Init)

3.2.5 调用方 (index.js)

1
2
const addon = require('bindings')('my_addon');
console.log(addon.add(1.5, 2.3)); // 3.8

3.2.6 在 Electron 中使用

关键:Electron 的 Node.js 版本与官方 Node 不同,需要重新编译 Native Addon:

1
2
npm install @electron/rebuild --save-dev
npx electron-rebuild

或在 package.json 中配置:

1
2
3
4
5
{
"scripts": {
"rebuild": "electron-rebuild"
}
}

Main Process 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const addon = require('bindings')('my_addon');

let win;

function createWindow() {
win = new BrowserWindow({
webPreferences: { nodeIntegration: false, contextIsolation: true }
});
win.loadFile('index.html');
}

ipcMain.handle('add-numbers', (event, a, b) => {
return addon.add(a, b);
});

app.whenReady().then(createWindow);

Preload (preload.js):

1
2
3
4
5
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('native', {
add: (a, b) => ipcRenderer.invoke('add-numbers', a, b)
});

Renderer:

1
2
const result = await window.native.add(1.5, 2.3);
console.log(result); // 3.8

3.3 FFI:调用已有 C 库

当需要调用已有的 .dll (Windows)、.dylib (macOS)、.so (Linux) 时,使用 FFI 无需重写 C++ 代码。

3.3.1 使用 koffi(推荐,支持 N-API)

1
npm install koffi
1
2
3
4
5
6
7
8
9
10
11
12
13
const koffi = require('koffi');

// 加载系统库
const libc = koffi.load('libc.so.6'); // Linux
// const libc = koffi.load('msvcrt.dll'); // Windows
// const libc = koffi.load('libc.dylib'); // macOS

const ceil = libc.func('double ceil(double)', 'ceil');
console.log(ceil(3.2)); // 4

// 加载自定义 DLL
const myLib = koffi.load('./my_lib.dll');
const myFunc = myLib.func('int my_func(const char* str, int len)', 'my_func');

3.3.2 使用 node-ffi-napi(注意 Electron 兼容性)

1
2
3
4
5
6
7
8
const ffi = require('ffi-napi');
const ref = require('ref-napi');

const msvcrt = ffi.Library('msvcrt', {
'ceil': ['double', ['double']]
});

console.log(msvcrt.ceil(3.2)); // 4

注意:Electron 20.3.8+ 的沙箱可能限制指针操作,导致 ffi-napi 崩溃,可考虑:

  • 使用 koffi(基于 N-API,兼容性更好)
  • 或通过 Main Process 调用,避免在 Renderer 使用 FFI

3.4 实际项目应用案例

案例 1:调用 Windows 系统 API(获取 CPU 使用率)

1
2
3
4
5
6
7
需求:在 Electron 应用中显示实时 CPU 使用率

实现:
1. 使用 node-ffi-napi 或 koffi 加载 kernel32.dll / pdh.dll
2. 调用 GetSystemTimes、PdhCollectQueryData 等 API
3. Main Process 定时采样,通过 IPC 推送到 Renderer 展示
4. 跨平台:macOS 使用 sysctl,Linux 使用 /proc/stat

案例 2:硬件加密狗认证

1
2
3
4
5
6
7
需求:桌面端软件需插入 USB 加密狗才能使用

实现:
1. 厂商提供 .dll / .so SDK
2. 通过 FFI 加载 SDK,调用 CheckDongle()、GetLicense() 等
3. Main Process 启动时校验,失败则禁止启动
4. 注意:.dll 路径需随应用打包正确配置(如 resources/ 目录)

案例 3:大文件加解密(性能敏感)

1
2
3
4
5
6
7
需求:对 GB 级文件进行 AES 加解密,纯 JS 太慢

实现:
1. 用 C++ 编写加解密模块,基于 OpenSSL 或 crypto 库
2. 编译为 Native Addon,暴露 encryptFile(path)、decryptFile(path)
3. Main Process 调用,通过 Stream 或进度回调推送到 Renderer
4. 可考虑 Worker 线程 + 原生模块,避免阻塞主进程

四、对比与选型

4.1 Flutter vs Electron 原生交互对比

维度 Flutter (移动端) Electron (桌面端)
通信机制 Platform Channel(异步消息) IPC + Native Addon / FFI
数据格式 StandardMessageCodec(JSON-like) 任意(JS 对象 ↔ C 结构体需手动处理)
类型安全 可配合 Pigeon 生成 需自行保证
多端一致性 同一套 Channel 名,各端实现不同 同一套 Addon/FFI 调用,各平台编译不同
性能 消息序列化有开销,适合中低频调用 Native Addon 直接调用,适合高性能场景
调试 可通过日志追踪 Channel 消息 需 gdb/lldb 调试 C++

4.2 何时用 MethodChannel / EventChannel / FFI / Native Addon

需求 推荐方案
单次调用原生 API(如获取电池) Flutter MethodChannel / Electron IPC + Addon
持续接收原生数据流(如传感器) Flutter EventChannel
调用已有 C 库,不想重写 Electron FFI
高性能计算、自定义算法 Native Addon(C++)
需要类型安全、少写样板 Flutter Pigeon

五、最佳实践与注意事项

5.1 Flutter

  1. Channel 命名:使用反向域名,如 com.yourapp.service/battery,避免冲突
  2. 主线程:Android/iOS 的 Channel 回调一般在主线程,耗时操作应异步处理
  3. 错误处理:原生端务必调用 result.error() 或 result.notImplemented(),避免 Dart 侧 Future 一直挂起
  4. 内存泄漏:EventChannel 的 onCancel 必须移除监听器、释放资源
  5. 插件化:可复用逻辑封装为 Flutter Plugin,发布到 pub.dev

5.2 Electron

  1. 安全:禁用 Renderer 的 nodeIntegration,仅通过 contextBridge 暴露必要 API
  2. 重建:每次升级 Electron 版本后执行 electron-rebuild
  3. 路径:Native Addon 的 .node 文件路径在打包后可能变化,使用 __dirname、app.getPath('userData') 等正确处理
  4. 崩溃:C++ 模块崩溃会导致整个进程退出,需做好 try-catch 和日志

5.3 鸿蒙特别说明

  • Flutter 对 OpenHarmony/HarmonyOS 的支持在快速演进,需关注官方文档和 flutter_harmony 等生态
  • ArkTS 与 Dart 的 MethodChannel 接口类似,但需使用鸿蒙提供的 FlutterPlugin、MethodChannel 等 API
  • 权限、包名、签名等需符合鸿蒙应用规范

六、总结

技术栈 核心机制 典型用途
Flutter + Android/iOS/鸿蒙 Platform Channel(MethodChannel / EventChannel) 移动端调用相机、支付、蓝牙等
Electron + Mac/Windows IPC + Native Addon / FFI 桌面端调用系统 API、硬件、高性能计算

掌握原生交互能力,是跨平台开发从「能写」到「写好」的关键一步。建议先跑通官方示例,再结合实际业务逐步封装和优化。


附录:参考资源

Flutter 应用里 Dart 侧如何与链打交道:库分工与调用顺序

摘要

在「原生壳 + Flutter 模块」的混合架构中,链上读写、业务网关与钱包连接往往同时存在。本文将 Dart 侧常见能力归纳为 五类:JSON-RPC 客户端、HTTP 传输、链数据编解码与请求封装、WalletConnect 会话,以及签名与跨语言辅助;再用 一张总览流程图 与 多张时序图 说明它们如何串成一条可上线的 Swap / 授权类链路。文末补充 大单、单池深度不足 场景下 路由服务与客户端 的分工,便于读者按自家模块命名做映射。

Read more

Android 模块化、组件化与插件化实践

从基础概念到实战应用,系统梳理 Android 架构演进之路


一、基础概念

1.1 为什么需要架构优化?

随着业务迭代,单体 App 会遇到诸多问题:

问题 表现 影响
代码耦合严重 模块间直接 import,循环依赖 难以维护、编译慢
编译效率低 改一行代码全量编译 开发效率下降
团队协作冲突 多人修改同一工程 Git 冲突频繁
无法独立开发 强依赖主工程 无法并行开发、独立测试
复用困难 业务逻辑与 UI 混在一起 跨项目复用成本高

1.2 三种架构模式辨析

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────┐
│ 架构演进路径 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 单体架构 ──► 模块化 ──► 组件化 ──► 插件化 │
│ │ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ 按功能拆分 Gradle模块 路由解耦 运行时动态加载 │
│ 包结构划分 build独立 ARouter等 热更新/按需下载 │
│ │
└─────────────────────────────────────────────────────────────────┘

模块化(Modularization)

  • 定义:按业务功能将代码拆分为独立的 Gradle 模块,每个模块有清晰的 build.gradle 和边界
  • 特点:物理隔离、可独立编译,通常仍在同一 Git 仓库内
  • 粒度:中等,如「用户模块」「订单模块」「支付模块」

组件化(Componentization)

  • 定义:在模块化基础上,通过路由/服务发现实现模块间完全解耦,可独立开发、独立运行
  • 特点:依赖倒置、接口隔离、可单模块调试(Application 切换)
  • 粒度:较细,如「登录组件」「分享组件」「埋点组件」

插件化(Pluginization)

  • 定义:插件 APK/Dex 可动态加载、热更新,主 App 与插件解耦到运行时
  • 特点:运行时动态、按需下载、可热修复、减小包体积
  • 粒度:独立 APK/模块

二、模块化原理与实践

2.1 模块化的核心原则

  1. 单一职责:每个模块只负责一块业务
  2. 接口隔离:模块间通过接口/路由通信,不暴露实现细节
  3. 依赖倒置:依赖抽象(interface)而非具体实现

2.2 Gradle 多模块目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MyApp/
├── app/ # 主工程壳
│ └── build.gradle
├── module_user/ # 用户模块
│ ├── build.gradle
│ └── src/main/
├── module_order/ # 订单模块
│ └── build.gradle
├── module_payment/ # 支付模块
│ └── build.gradle
├── common_base/ # 公共基础库
│ ├── network/
│ ├── utils/
│ └── base/
├── common_router/ # 路由层
│ └── build.gradle
├── build.gradle
└── settings.gradle

2.3 settings.gradle 与模块声明

1
2
3
4
5
6
7
8
// settings.gradle
rootProject.name = "MyApp"
include ':app'
include ':module_user'
include ':module_order'
include ':module_payment'
include ':common_base'
include ':common_router'

2.4 模块间依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/build.gradle - 主工程依赖各业务模块
dependencies {
implementation project(':module_user')
implementation project(':module_order')
implementation project(':module_payment')
implementation project(':common_router')
implementation project(':common_base')
}

// module_user/build.gradle - 业务模块只依赖基础库和路由
dependencies {
implementation project(':common_base')
implementation project(':common_router')
// 不要依赖 module_order、module_payment!
}

// common_base/build.gradle - 基础库不依赖任何业务模块
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}

2.5 模块间通信:接口 + 实现注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// common_router 中定义接口
interface IUserService {
fun getCurrentUser(): User?
fun logout()
}

// module_user 中实现接口
class UserServiceImpl : IUserService {
override fun getCurrentUser(): User? = UserManager.currentUser
override fun logout() { /* ... */ }
}

// 通过 ServiceLocator 或依赖注入框架注入
object ServiceLocator {
var userService: IUserService? = null
}

// module_order 中调用
class OrderActivity : AppCompatActivity() {
private val userService: IUserService? by lazy { ServiceLocator.userService }

fun showUserInfo() {
userService?.getCurrentUser()?.let { user ->
// 显示用户信息
}
}
}

三、组件化原理与实现

3.1 组件化架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
                ┌──────────────────┐
│ app (壳) │
│ 主工程/组装 │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ module_user │ │ module_order│ │module_payment│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────▼──────┐
│ ARouter │
│ 路由中间层 │
└──────┬──────┘
│
┌──────▼──────┐
│ common_base │
│ 基础组件库 │
└─────────────┘

3.2 路由方案对比

方案 原理 优点 缺点
ARouter 注解 + 编译期生成路由表 阿里出品、生态成熟、支持拦截器 需引入注解处理器
WMRouter 美团方案,分组路由 按需加载、包体积优化 学习成本略高
CC (Component Call) 组件调用框架 同步/异步调用、跨进程 概念较多
手写路由表 Map 映射 简单、无依赖 维护成本高

3.3 单模块独立运行(Application 切换)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// module_user/build.gradle
android {
defaultConfig {
// 作为 Application 运行时使用
if (project.hasProperty('runAsApp') && runAsApp) {
applicationId "com.example.user"
}
}
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
if (project.hasProperty('runAsApp') && runAsApp) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- module_user/src/main/debug/AndroidManifest.xml - 独立运行时的 Manifest -->
<manifest>
<application
android:name=".UserDebugApplication"
android:label="User Module Debug">
<activity android:name=".UserActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
1
2
# 独立运行用户模块
./gradlew :module_user:installDebug -PrunAsApp=true

3.4 ARouter 使用与原理

3.4.1 添加依赖

1
2
3
4
5
// 各模块 build.gradle
dependencies {
implementation 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
}

3.4.2 路由配置与跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ========== 组件端:Order 模块 ==========
// 在 OrderListActivity 上添加路由注解
@Route(path = "/order/list")
class OrderListActivity : AppCompatActivity() {
@Autowired
lateinit var userId: String

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
// userId 已自动注入
}
}

// ========== 调用端:任意模块 ==========
ARouter.getInstance()
.build("/order/list")
.withString("userId", "12345")
.navigation()

3.4.3 服务发现(跨模块获取实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义接口(可在 common_base 或单独 api 模块)
interface IOrderService {
fun getOrderCount(userId: String): Int
}

// Order 模块实现并注册
@Route(path = "/service/order")
class OrderServiceImpl : IOrderService {
override fun getOrderCount(userId: String): Int = /* ... */
}

// 调用端获取
val orderService = ARouter.getInstance()
.navigation(IOrderService::class.java)
orderService?.getOrderCount("12345")

3.4.5 ARouter 核心源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 简化版 ARouter 核心逻辑
// 1. 初始化时扫描 Dex,加载路由表(通过 Gradle 插件编译期生成)
class Warehouse {
// 路由表:path -> RouteMeta
static Map<String, RouteMeta> routes = ConcurrentHashMap()
// 分组缓存
static Map<String, Class<? extends IRouteGroup>> groupsIndex = ConcurrentHashMap()
}

// 2. navigation 流程
fun navigation(path: String): Postcard {
val meta = Warehouse.routes[path] // 从路由表获取
return Postcard(path, meta)
}

fun Postcard.navigation(): Any? {
return _ARouter.navigation(context, this, requestCode, navigationCallback)
}

// 3. 实际跳转
fun _ARouter.navigation(...) {
when (meta.type) {
RouteType.ACTIVITY -> {
val intent = Intent(context, meta.destination)
// 注入参数
context.startActivity(intent)
}
RouteType.ISERVICE -> {
return meta.destination.newInstance() // 服务实现类
}
}
}

// 4. 编译期注解处理器生成路由表
// 生成类似:ARouter$$Group$$order、ARouter$$Providers$$order
// 在 init() 时通过反射加载到 Warehouse

3.5 依赖关系设计原则

1
2
3
4
5
6
7
8
9
业务模块 (user/order/payment)
│
├── 只依赖 common_base、common_router (arouter-api)
│
└── 业务模块之间 不直接依赖

app 壳工程
│
└── 依赖所有业务模块,负责组装和 Application 初始化

四、插件化原理与实现

4.1 插件化的应用场景

  • 热更新:修复线上 Bug 无需发版
  • 按需加载:减小包体积,冷启动只加载核心
  • 动态能力:运营活动插件、A/B 测试模块
  • 多端复用:同一套插件可被主 App、Split APK 加载

4.2 Android 插件化技术选型

方案 说明 适用场景
Dynamic Feature Modules Google 官方,AGP 支持 按需下载、免安装功能模块
VirtualAPK 滴滴开源 完整的插件化框架
RePlugin 360 出品 插件即独立 APK,稳定成熟
Shadow 腾讯开源 支持 Android 10+,零 Hook
Atlas 阿里 大型 App 动态化

4.3 Dynamic Feature Modules(官方方案)

4.3.1 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// settings.gradle
include ':app'
include ':feature:order' // 动态功能模块

// app/build.gradle
dependencies {
implementation project(':common_base')
dynamicFeatures = [':feature:order']
}

// feature/order/build.gradle
apply plugin: 'com.android.dynamic-feature'
android {
defaultConfig {
minSdk 24
}
}
dependencies {
implementation project(':app')
}

4.3.2 按需下载与安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用 Play Core Library 下载动态模块
val splitInstallManager = SplitInstallManagerFactory.create(context)
val request = SplitInstallRequest.newBuilder()
.addModule("order")
.build()

splitInstallManager.startInstall(request)
.addOnSuccessListener {
// 安装成功,可跳转
startActivity(Intent(this, OrderActivity::class.java))
}
.addOnFailureListener {
// 处理失败
}

4.4 RePlugin 核心原理简述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──────────────────────────────────────────────────────┐
│ Host App │
│ ┌────────────────────────────────────────────────┐ │
│ │ RePluginHostConfig / PluginManager │ │
│ │ - 加载插件 APK │ │
│ │ - 替换 ClassLoader │ │
│ │ - Hook Activity/Service 等组件 │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Plugin1 │ │ Plugin2 │ │ Plugin3 │
│ .apk │ │ .apk │ │ .apk │
└──────────┘ └──────────┘ └──────────┘

核心流程:

  1. Dex 加载:将插件 APK 的 classes.dex 加入宿主的 DexPathList
  2. 资源加载:创建 Resources,合并插件的 AssetManager
  3. 组件占位:通过 Hook Instrumentation/IActivityManager,用占位 Activity 代理插件 Activity

4.5 手写简易插件加载(Dex 加载)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 简化示例:加载插件 APK 中的类
class PluginLoader(private val context: Context) {

private val pluginClassLoaders = mutableMapOf<String, DexClassLoader>()

fun loadPlugin(apkPath: String): Boolean {
val optimizedDir = File(context.filesDir, "plugin_opt").apply { mkdirs() }
val libDir = File(context.filesDir, "plugin_lib").apply { mkdirs() }

val classLoader = DexClassLoader(
apkPath,
optimizedDir.absolutePath,
null,
context.classLoader
)
pluginClassLoaders[apkPath] = classLoader
return true
}

fun loadClass(apkPath: String, className: String): Class<*>? {
val loader = pluginClassLoaders[apkPath] ?: return null
return try {
loader.loadClass(className)
} catch (e: ClassNotFoundException) {
null
}
}
}

// 加载插件中的 Activity(实际还需 Hook 组件启动流程)
val loader = PluginLoader(context)
loader.loadPlugin("/sdcard/plugin.apk")
val clazz = loader.loadClass("/sdcard/plugin.apk", "com.plugin.MainActivity")
val activity = clazz.newInstance()

4.6 插件化资源加载

1
2
3
4
5
6
7
8
9
10
11
12
// 合并插件资源到宿主
fun createPluginResources(hostResources: Resources, apkPath: String): Resources {
val assets = AssetManager::class.java.newInstance()
val addAssetPath = AssetManager::class.java.getMethod("addAssetPath", String::class.java)
addAssetPath.invoke(assets, apkPath)

return Resources(
assets,
hostResources.displayMetrics,
hostResources.configuration
)
}

五、ARouter 框架源码深度解析

5.1 整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌────────────────────────────────────────────────────────┐
│ ARouter │
│ - 对外 API:build、navigation、inject │
└────────────────────────────────────────────────────────┘
│
┌─────────────────────────┴─────────────────────────────┐
│ _ARouter (内部实现) │
│ - LogisticsCenter:路由表、分组加载 │
│ - Warehouse:路由/服务/拦截器 存储 │
└────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 注解处理器 │ │ 拦截器链 │ │ 降级策略 │
│ 编译期生成 │ │ 可扩展 │ │ DegradeService│
└──────────────┘ └──────────────┘ └──────────────┘

5.2 LogisticsCenter 路由表加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 初始化时通过插件扫描 Dex 中的特定类
public static void init(Context context) {
try {
// 获取所有 ARouter 生成的类:ARouter$$Root$$xxx
Set<String> routerMap = ClassUtils.getFileNameByPackageName(
context, ROUTE_ROOT_PACKAGE);
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PACKAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
((IRouteRoot) Class.forName(className).getConstructor().newInstance())
.loadInto(Warehouse.groupsIndex);
}
}
// 分组按需加载,避免启动时加载全部
} catch (Exception e) {
throw new HandlerException("ARouter init logistics center exception");
}
}

5.3 拦截器机制

1
2
3
4
5
6
7
8
9
10
11
12
@Interceptor(priority = 8)
class LoginInterceptor : IInterceptor {
override fun process(postcard: Postcard, callback: InterceptorCallback) {
if (postcard.extra == NEED_LOGIN && !UserManager.isLoggedIn()) {
// 未登录,拦截并跳转登录
ARouter.getInstance().build("/user/login").navigation()
callback.onInterrupt(null)
} else {
callback.onContinue(postcard)
}
}
}

六、实际项目应用案例

6.1 某电商 App 组件化拆分

1
2
3
4
5
6
7
8
9
10
11
app (壳工程)
├── module_home # 首页
├── module_product # 商品详情
├── module_cart # 购物车
├── module_order # 订单
├── module_user # 用户中心
├── module_payment # 支付
├── module_share # 分享(可复用)
├── module_analytics # 埋点(可复用)
├── common_router # ARouter 封装
└── common_base # 网络/缓存/UI 基础库

收益:

  • 编译时间:全量 6min → 单模块 1min
  • 4 个业务线可并行开发,Git 冲突减少 60%
  • ShareModule、AnalyticsModule 复用到多个 App

6.2 路由表设计实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 统一路由 Path 常量
object RouterPath {
const val ORDER_LIST = "/order/list"
const val ORDER_DETAIL = "/order/detail"
const val USER_LOGIN = "/user/login"
const val USER_PROFILE = "/user/profile"
}

// 封装便捷方法
object Router {
fun toOrderList(userId: String) {
ARouter.getInstance()
.build(RouterPath.ORDER_LIST)
.withString("userId", userId)
.navigation()
}

fun toOrderDetail(orderId: String) {
ARouter.getInstance()
.build(RouterPath.ORDER_DETAIL)
.withString("orderId", orderId)
.navigation()
}
}

6.3 解耦实践:避免循环依赖

错误示例:

1
2
module_order -> module_user (获取用户信息)
module_user -> module_order (跳转订单列表)

形成循环依赖,Gradle 编译失败。

正确做法:

1
2
module_order -> common_router
module_user -> common_router

需要「用户信息」时,Order 通过 ARouter.getInstance().navigation(IUserService::class.java) 获取;需要「跳转订单」时,User 通过 ARouter.getInstance().build("/order/list").navigation() 跳转。

6.4 模块化 + 插件化组合:Dynamic Feature 实践

某资讯 App 将「小说」「漫画」等非核心功能做成 Dynamic Feature,用户首次进入时提示下载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 检查模块是否已安装
fun isModuleInstalled(moduleName: String): Boolean {
return SplitInstallManagerFactory.create(context)
.installedModules.contains(moduleName)
}

// 进入小说模块前检查
fun openNovelModule() {
if (isModuleInstalled("novel")) {
startActivity(Intent(this, NovelActivity::class.java))
} else {
showDownloadDialog {
installDynamicModule("novel")
}
}
}

七、最佳实践与注意事项

7.1 模块拆分原则

  • 高内聚低耦合:模块内聚度高,模块间依赖少
  • 按业务边界拆分:参考 DDD 的 Bounded Context
  • 基础组件下沉:网络、缓存、日志等抽成 common_base
  • 渐进式演进:先模块化再组件化,避免一步到位导致成本过高

7.2 常见坑与规避

问题 原因 规避
编译顺序错误 模块间隐式依赖 严格检查 build.gradle,用 ./gradlew :module_x:dependencies 检查
路由表膨胀 所有页面都注册路由 仅对外暴露的页面注册,内部页面用 startActivity
启动变慢 初始化时加载全部路由 使用分组按需加载(ARouter 已支持)
R 文件冲突 多模块资源 ID 冲突 在 build.gradle 中设置 resourcePrefix "module_user_"
插件化兼容性 不同厂商 ROM 限制 优先使用 Dynamic Feature,第三方框架需充分测试

7.3 R 文件与资源隔离

1
2
3
4
// 各业务模块 build.gradle 中
android {
resourcePrefix "user_" // 资源必须以 user_ 开头
}

7.4 架构演进路线图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Phase 1: 模块化
├── 拆分为 Gradle 多模块
├── 建立 common_base、common_router
└── 模块间通过接口通信

Phase 2: 组件化
├── 引入 ARouter 路由
├── 服务发现替代直接依赖
└── 支持单模块独立运行调试

Phase 3: 插件化(可选)
├── 非核心功能改为 Dynamic Feature
├── 或引入 RePlugin/Shadow 做完整插件化
└── 按需下载、热更新

八、总结

架构 适用场景 核心手段
模块化 中小型 App、团队 < 10 人 Gradle 多模块、接口、依赖配置
组件化 中大型 App、多业务线并行 ARouter、服务发现、单模块调试
插件化 需要动态化、热更新、包体积优化 Dynamic Feature、RePlugin、Shadow

架构没有银弹,需结合团队规模、业务复杂度、迭代节奏选择合适方案。建议从模块化起步,随着复杂度提升再逐步演进到组件化,插件化则按实际需求谨慎引入。


参考资源

Android 核心组件体系与设计思想

由浅入深,从四大组件、应用骨架到设计哲学与最佳实践,系统梳理 Android 核心组件体系与架构思想


一、基本概念

1.1 什么是 Android 核心组件?

Android 核心组件是系统提供的、用于构建应用的基本构建块。应用由多个组件组成,组件之间通过 Intent 和 Binder 等机制协作,由系统负责创建、调度和生命周期管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────────────────┐
│ Android 应用 = 组件的组合与协作 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 四大组件 │
│ Activity / Service / BroadcastReceiver / ContentProvider │
│ │ │
│ ▼ │
│ Intent(意图)──► 组件间通信、启动、数据传递 │
│ Context(上下文)──► 访问资源、启动组件、获取系统服务 │
│ Application ──► 进程级单例,应用全局状态与初始化 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1.2 为什么是「组件化」?

Android 没有「一个 main 入口跑到底」的传统应用模型,而是采用 组件化 + 系统调度:

设计点 说明
可复用 任意应用可通过 Intent 启动其他应用的 Activity(如调起相机、地图),无需链接其代码
可替换 同一种组件类型可有多个实现(如多个浏览器),用户或系统可选择
生命周期由系统管 组件由系统创建与销毁,便于在内存紧张时回收、在配置变更时重建
安全与隔离 组件运行在应用进程内,通过权限与 Intent 过滤控制谁能调谁

二、四大核心组件

2.1 Activity:界面与用户交互

Activity 代表一个「界面」或「屏幕」。用户看到的每一个全屏/窗口化界面,通常对应一个 Activity(或 Fragment 承载的视图)。

要点 说明
职责 提供 UI、处理用户输入、与用户交互
生命周期 onCreate → onStart → onResume → (运行中) → onPause → onStop → onDestroy
启动方式 其他 Activity 或应用通过 startActivity(Intent) 启动
任务栈 多个 Activity 组成「返回栈」,Back 键按栈顶依次退出
1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────────────┐
│ Activity 典型生命周期(简化) │
│ │
│ 创建 ──► onCreate() ──► onStart() ──► onResume() ──► 可见且可交互 │
│ │ │
│ ▼ │
│ 切到后台 / 被遮挡 ──► onPause() ──► onStop() │
│ │ │
│ 再次回到前台 ──► onRestart() ──► onStart() ──► onResume() │
│ │
│ 销毁 ──► onDestroy() │
└─────────────────────────────────────────────────────────────────────────┘
  • 与 Fragment 的关系:一个 Activity 可包含多个 Fragment,Fragment 是「界面片段」,用于在同一个 Activity 内做模块化 UI 与复用。

2.2 Service:后台任务与长期运行

Service 用于在「无界面」的情况下执行长时间运行的任务,或为其他组件提供后台能力(如播放音乐、下载、同步)。

类型 说明 典型场景
Started Service 由 startService() 启动,可长期运行直到 stopSelf() 或 stopService() 音乐播放、下载、上传
Bound Service 由 bindService() 绑定,为其他组件提供 C/S 式接口,无客户端绑定后可由系统回收 本地 SDK、数据服务、AIDL 服务
  • 生命周期:onCreate → onStartCommand(Started)或 onBind(Bound)→ 运行 → onUnbind / onDestroy。
  • 前台服务:若需长时间在后台运行且不被系统轻易杀死,可调用 startForeground() 并显示持久通知,符合系统对「前台服务」的规范。

2.3 BroadcastReceiver:事件广播与响应

BroadcastReceiver 用于接收系统或应用发出的「广播」(如开机完成、网络变化、电量低、自定义事件),并做轻量级响应。

要点 说明
注册方式 静态注册(AndroidManifest.xml)或动态注册(代码中 registerReceiver)
执行 主线程、短时执行;耗时逻辑应交给 Service 或 WorkManager
有序广播 可指定优先级与「截断」传播(abortBroadcast)

常见系统广播:ACTION_BOOT_COMPLETED、ACTION_BATTERY_LOW、CONNECTIVITY_ACTION 等。

2.4 ContentProvider:数据抽象与跨进程共享

ContentProvider 对数据提供统一的「增删改查」接口(类似小型数据库 API),并支持跨应用、跨进程访问,是 Android 中「数据共享」的标准方式。

要点 说明
职责 封装数据源(SQLite、文件、内存、网络),对外提供 URI 与 Cursor/ContentValues API
跨进程 底层通过 Binder 暴露,其他应用通过 ContentResolver 访问,无需直接依赖实现方
权限 可在 AndroidManifest 中为 Provider 声明读写权限,由系统做权限校验

系统示例:通讯录、媒体库、设置等,均通过 ContentProvider 暴露给其他应用。


三、支撑性核心:Intent、Context、Application

3.1 Intent:意图与组件间通信

Intent 表示「要做的一件事」或「要传递的一包信息」,用于启动 Activity/Service、发送广播,并携带数据与标志。

类型 说明 典型用法
显式 Intent 指定 ComponentName(包名 + 类名),明确启动哪个组件 应用内页面跳转、指定 Service
隐式 Intent 只指定 action、category、data(可选),由系统根据各组件声明的 匹配 调起相机、分享、打开链接、跨应用启动
1
2
显式:我知道要启动谁 → setComponent / setClassName
隐式:我只描述「要做什么」→ setAction / addCategory / setData,系统找合适的组件
  • Intent 可携带:Bundle 数据、Extra、Flags(如 FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_SINGLE_TOP)等,是组件间解耦通信的载体。

3.2 Context:上下文与资源访问

Context 是「当前组件/应用所在环境」的抽象,提供:

  • 访问应用资源(布局、字符串、drawable、主题等)
  • 启动组件(startActivity、startService、sendBroadcast)
  • 获取系统服务(getSystemService:如 LayoutInflater、ActivityManager、LocationManager)
  • 访问应用专属目录与 SharedPreferences 等
常见实现 说明
Activity Activity 本身是 Context,且带有「界面/任务」相关能力(如 startActivityForResult)
Application 进程级单例 Context,生命周期等于进程,适合做全局初始化与单例持有
Service Service 也是 Context,但无界面相关 API

注意:长时间持有 Activity 的 Context 容易导致内存泄漏(如静态变量引用 Activity),在异步回调中应避免;可改用 Application Context 或弱引用。

3.3 Application:应用级单例

Application 在进程启动时由系统创建,且每个进程只有一个实例。常用作:

  • 应用级初始化(SDK、全局配置、数据库等)
  • 全局单例或依赖注入容器的持有
  • 实现 Application 级别的生命周期回调(如 onConfigurationChanged)

在 AndroidManifest.xml 中通过 <application android:name=".MyApplication"> 指定自定义子类。


四、核心设计思想

4.1 组件化与「应用即组件集合」

Android 不强调「一个程序从 main 开始跑」,而是强调:

  • 应用由多个组件构成,每个组件有明确类型(Activity/Service/…)和声明(AndroidManifest)。
  • 入口是「可被系统或其它应用调用的组件」:例如 LAUNCHER Activity、可被隐式 Intent 匹配的 Activity/Service。
  • 组件之间通过 Intent 解耦:调用方只表达「意图」,不直接依赖实现类,便于复用与替换。
1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────────────┐
│ 组件化思想:应用 = 组件 + 声明 + 意图驱动 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ AndroidManifest.xml 声明组件与 Intent-Filter │
│ │ │
│ ▼ │
│ 系统 / 其他应用 ──► 发出 Intent ──► 系统解析 ──► 创建并启动对应组件 │
│ │
│ 同一「动作」可有多个实现(如多个浏览器),用户或系统选择其一 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

4.2 生命周期由系统托管

组件的创建、暂停、恢复、销毁由 系统 根据用户行为、系统资源、配置变更(如旋转、多窗口、语言切换)统一调度:

  • 开发者实现生命周期回调(如 Activity 的 onPause/onResume),在「合适的时机」保存状态、释放资源、恢复 UI。
  • 系统可在内存紧张时回收后台组件;配置变更时销毁并重建 Activity,通过 onSaveInstanceState / ViewModel 等恢复状态。

这种设计把「何时创建/销毁」交给系统,应用只响应「已经发生」的生命周期事件,便于多任务与资源管理。

4.3 安全与权限模型

  • 进程隔离:每个应用默认运行在独立进程,内存与执行空间隔离。
  • 组件级权限:可在 AndroidManifest 中为 Activity/Service/ContentProvider 等声明 android:permission,调用方需具备相应权限才能启动或访问。
  • 运行时权限:危险权限(如相机、位置、存储)需在运行时向用户申请,用户同意后应用才可使用。
  • Intent 与导出:组件可设为 android:exported="true/false",控制是否允许其他应用通过隐式 Intent 调起。

4.4 多任务与任务栈

  • 任务(Task):通常对应用户概念里的「一个应用的一组界面」,由一组 Activity 的栈组成。
  • 启动模式:Activity 的 launchMode(standard、singleTop、singleTask、singleInstance)以及 Intent 的 Flags 共同决定「新 Activity 如何入栈、是否复用已有实例」,从而影响返回栈行为与多任务表现。

五、从设计思想到最佳实践

5.1 用 Intent 做解耦

  • 应用内跳转:可用显式 Intent,也可用隐式 Intent + 自定义 action,便于后续替换实现或做 Deep Link。
  • 跨应用能力:尽量用系统或公共的 action/data 约定(如 VIEW + http/https),或在自己的 Provider/Activity 上声明清晰的 intent-filter,便于被系统或其他应用发现和调起。

5.2 生命周期与状态保存

  • 短时状态:在 onSaveInstanceState 中保存,在 onCreate/onRestoreInstanceState 中恢复。
  • 界面相关数据:使用 ViewModel + LiveData/StateFlow,在配置变更时保留,避免在 Activity 里堆业务状态。
  • 释放资源:在 onPause/onStop 中暂停耗时操作、释放监听与引用,在 onDestroy 中做最终清理,避免泄漏。

5.3 Service 与后台行为规范

  • 短时任务:优先用 WorkManager 或 协程 + 应用前后台状态,避免长时间占住 Service。
  • 需要长时间运行且用户可感知(如音乐播放、导航):使用 前台 Service,并按规定显示通知。
  • 避免在 BroadcastReceiver 中做重活:应启动 Service 或提交到 WorkManager。

5.4 架构分层与组件角色

  • Activity/Fragment:只做 UI 与用户输入,将业务与数据交给 ViewModel 或 Presenter。
  • ViewModel:持有界面状态与业务逻辑入口,不持有 Context/View,便于测试与复用。
  • Repository / UseCase:封装数据来源(网络、本地、ContentProvider),对上层提供统一接口。
  • ContentProvider:仅在需要「跨应用/跨进程共享数据」时使用;应用内本地数据可用 Room + DAO 等,不必强行上 Provider。

六、小结

主题 要点
四大组件 Activity(界面)、Service(后台)、BroadcastReceiver(广播)、ContentProvider(数据抽象与跨应用共享)
支撑核心 Intent(意图与组件通信)、Context(资源与系统服务)、Application(进程级单例)
设计思想 组件化、系统托管生命周期、Intent 驱动与解耦、安全与权限、任务栈与多任务
实践方向 善用 Intent 解耦、重视生命周期与状态保存、规范使用 Service 与后台、UI 与业务分层

理解「应用即组件的组合」「生命周期由系统托管」「Intent 表达意图、系统负责匹配与调度」,就能更好地把握 Android 核心组件与设计思想,并在此基础上用好 ViewModel、WorkManager、Jetpack 等现代组件与工具。

C++ 与 Rust:对比与实践

一篇由浅入深、涵盖概念、原理、源码与实战的对比与学习指南。


目录

  1. 基本概念
  2. 核心原理对比
  3. 语法与源码示例
  4. 内存与并发模型
  5. 实际项目应用案例
  6. 选型与迁移建议

一、基本概念

1.1 C++ 是什么

C++ 是一种多范式、编译型系统编程语言,由 Bjarne Stroustrup 在 1979 年起在 C 语言基础上扩展而来。特点包括:

  • 零成本抽象:高级抽象(类、模板、智能指针)在合理使用下不增加运行时开销。
  • 手动内存管理:可显式 new/delete,也可借助 RAII 与智能指针自动化。
  • 多范式:面向过程、面向对象、泛型、函数式、元编程均可。
  • 标准与生态:ISO 标准驱动,STL 与第三方库丰富,广泛应用于操作系统、游戏、嵌入式、高性能服务等。

1.2 Rust 是什么

Rust 是一种系统级、内存安全、无数据竞争的编程语言,由 Mozilla 发起,2015 年 1.0 稳定。特点包括:

  • 所有权系统:通过所有权、借用、生命周期在编译期消除大部分内存错误与数据竞争。
  • 无垃圾回收:不依赖 GC,通过类型系统与借用检查实现安全与性能兼得。
  • 无畏并发:类型系统保证线程安全,避免数据竞争在编译期被发现。
  • 现代工具链:cargo 包管理、rustfmt/clippy 格式化与静态检查,体验统一。

1.3 定位对比(一句话)

维度 C++ Rust
设计目标 性能 + 灵活 + 与 C 兼容 安全 + 性能 + 并发 + 现代工具链
内存安全 需开发者自律与规范 编译器强制保证(所有权/借用)
学习曲线 陡峭(历史包袱、未定义行为) 陡峭(所有权/生命周期概念)
典型场景 遗留系统、游戏、内核、HPC 新系统服务、WebAssembly、CLI、基础设施

二、核心原理对比

2.1 内存管理模型

C++:RAII + 可选智能指针

  • 资源获取即初始化(RAII):构造时获取资源,析构时释放。
  • 可裸指针 + 手动 new/delete,也可用 std::unique_ptr、std::shared_ptr 等减少错误。
  • 编译器不阻止悬垂指针、双重释放、use-after-free,需靠规范与静态分析。

Rust:所有权 + 借用

  • 所有权:每个值有唯一所有者,离开作用域自动 drop,无 GC。
  • 借用:通过引用(&T/&mut T)临时借用,编译期检查引用合法性与可变性。
  • 生命周期:标注引用与谁同寿,避免悬垂引用在编译期报错。

概念对照:

C++ 概念 Rust 近似概念
std::unique_ptr 所有权(默认 move)
std::shared_ptr Rc<T> / Arc<T>
引用 T& 借用 &T / &mut T
析构函数 Drop trait

2.2 类型系统与泛型

C++

  • 模板(Template):编译期多态,可特化、偏特化、SFINAE、C++20 Concept。
  • 类型擦除:std::function、虚函数、void* 等。

Rust

  • 泛型 + Trait:类似「接口」,可默认实现、泛型约束、trait object(动态分发)。
  • 无继承,通过组合与 trait 实现多态。

2.3 错误处理

C++

  • 异常(throw/catch):灵活但可能影响性能与 ABI,部分项目禁用。
  • 错误码、std::optional、std::expected(C++23)等。

Rust

  • Result<T, E>:强制显式处理错误,? 运算符传播错误。
  • panic! 用于不可恢复错误,类似「断言失败」。

三、语法与源码示例

3.1 你好世界

C++

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}

Rust

1
2
3
fn main() {
println!("Hello, World!");
}

3.2 变量与不可变/可变

C++

1
2
3
const int x = 42;       // 不可变
int y = 10; // 可变
y = 20;

Rust

1
2
3
let x = 42;             // 默认不可变
let mut y = 10; // 可变需显式 mut
y = 20;

3.3 结构体与实现

C++

1
2
3
4
5
struct Point {
int x, y;
Point(int x, int y) : x(x), y(y) {}
int norm_sq() const { return x * x + y * y; }
};

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Point {
x: i32,
y: i32,
}

impl Point {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
fn norm_sq(&self) -> i32 {
self.x * self.x + self.y * self.y
}
}

3.4 枚举与模式匹配

C++(std::variant + std::visit,C++17)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <variant>
#include <string>

using Number = std::variant<int, double>;
using Message = std::variant<int, double, std::string>;

void process(Message m) {
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) { /* ... */ }
else if constexpr (std::is_same_v<T, double>) { /* ... */ }
else if constexpr (std::is_same_v<T, std::string>) { /* ... */ }
}, m);
}

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

fn process(msg: Message) {
match msg {
Message::Quit => {}
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(s) => println!("Write: {}", s),
Message::ChangeColor(r, g, b) => println!("Color: ({},{},{})", r, g, b),
}
}

3.5 错误处理

C++(错误码 + optional)

1
2
3
4
5
6
7
8
9
10
#include <optional>
#include <string>

std::optional<int> parse_int(const std::string& s) {
try {
return std::stoi(s);
} catch (...) {
return std::nullopt;
}
}

Rust

1
2
3
4
5
6
7
8
9
10
fn parse_int(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}

// 使用 ? 传播错误
fn read_and_parse() -> Result<i32, Box<dyn std::error::Error>> {
let s = std::fs::read_to_string("num.txt")?;
let n: i32 = s.trim().parse()?;
Ok(n)
}

3.6 所有权与移动

Rust(核心概念,C++ 无直接对应)

1
2
3
4
5
6
7
8
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动到 s2,s1 不再有效
// println!("{}", s1); // 编译错误:value used after move

let s3 = s2.clone(); // 显式克隆,s2 仍有效
println!("{} {}", s2, s3);
}

C++(拷贝/移动语义)

1
2
3
4
5
6
7
#include <string>

int main() {
std::string s1 = "hello";
std::string s2 = std::move(s1); // 移动后 s1 处于有效但未指定状态
std::string s3 = s2; // 拷贝(或拷贝省略)
}

3.7 借用与引用

Rust

1
2
3
4
5
6
7
8
9
fn len(s: &String) -> usize {
s.len()
} // s 的借用结束,不拥有 s,不 drop

fn main() {
let s = String::from("hello");
println!("length: {}", len(&s));
println!("{}", s); // s 仍然有效
}

C++

1
2
3
4
5
6
7
8
9
10
11
#include <string>

size_t len(const std::string& s) {
return s.size();
}

int main() {
std::string s = "hello";
std::cout << "length: " << len(s) << std::endl;
std::cout << s << std::endl;
}

3.8 泛型与约束

C++(模板 + Concept,C++20)

1
2
3
4
5
6
7
8
9
#include <concepts>

template<typename T>
concept Addable = requires(T a, T b) { a + b; };

template<Addable T>
T add(T a, T b) {
return a + b;
}

Rust

1
2
3
4
5
6
7
8
9
10
11
12
13
use std::ops::Add;

fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}

// 或使用 where
fn add2<T>(a: T, b: T) -> T
where
T: Add<Output = T>,
{
a + b
}

3.9 并发:线程与同步

C++(std::thread + mutex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <thread>
#include <mutex>
#include <iostream>

std::mutex mtx;
int counter = 0;

void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "counter = " << counter << std::endl;
}

Rust(Arc + Mutex)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc::channel;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..2 {
let c = Arc::clone(&counter);
let h = thread::spawn(move || {
for _ in 0..100_000 {
let mut num = c.lock().unwrap();
*num += 1;
}
});
handles.push(h);
}

for h in handles {
h.join().unwrap();
}
println!("counter = {}", *counter.lock().unwrap());
}

四、内存与并发模型

4.1 C++ 内存模型简述

  • 堆:new/delete 或智能指针管理。
  • 栈:局部变量,自动管理。
  • 未定义行为(UB):如 use-after-free、双重释放、数据竞争等,标准不保证行为,需避免。

4.2 Rust 内存模型与安全保证

  • 栈上值默认,堆上用 Box<T>、Vec<T> 等,由所有权与 drop 管理。
  • 引用规则(借用检查):
    • 任意时刻,要么多个不可变引用,要么一个可变引用。
    • 引用不能比被引用者活得更久(生命周期检查)。
  • 满足规则则无数据竞争、无悬垂引用,在编译期保证。

4.3 生命周期示例(Rust 独有)

1
2
3
4
5
6
7
8
9
10
11
12
13
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

fn main() {
let s1 = String::from("short");
let result;
{
let s2 = String::from("longer");
result = longest(s1.as_str(), s2.as_str());
}
// println!("{}", result); // 错误:result 可能引用已 drop 的 s2
}

五、实际项目应用案例

5.1 C++ 典型应用

  • LLVM/Clang:编译器基础设施,大量模板与手写内存管理。
  • Chromium:浏览器引擎,C++ 主导,复杂多进程与沙箱。
  • Unreal Engine:游戏引擎,深度使用 C++ 与反射/蓝图。
  • TensorFlow/PyTorch 底层:高性能算子与运行时多为 C++。
  • MySQL/PostgreSQL:数据库核心用 C/C++ 实现。

5.2 Rust 典型应用

  • Rust 编译器(rustc):自举,展示大型 Rust 项目与复杂类型系统。
  • Firefox(Servo 组件):浏览器引擎实验,部分已合入 Firefox。
  • Deno:TypeScript/JavaScript 运行时,核心用 Rust 重写。
  • Discord:从 Go 迁移部分服务到 Rust,降低延迟与资源占用。
  • Cloudflare:边缘逻辑、代理、零拷贝解析等用 Rust。
  • AWS Firecracker:轻量级 VMM,用于 Lambda 等,安全与性能关键。
  • 1Password:部分核心组件用 Rust 编写。

5.3 案例:从 C++ 到 Rust 的迁移(概念示例)

某团队将一高并发 TCP 代理从 C++ 迁移到 Rust 的动机与收益(概念性总结):

  • 动机:偶发 use-after-free 与竞态,难以在测试中稳定复现。
  • 做法:用 Rust 重写核心路径,保持协议与配置兼容;通过 FFI 或重写逐步替换。
  • 收益:
    • 编译通过即大幅排除内存与数据竞争类 bug。
    • 无 GC 停顿,延迟与 C++ 版本相当或更稳定。
    • cargo 与 clippy 提升协作与代码质量。

5.4 混合使用:Rust 调用 C++ / C++ 调用 Rust

Rust 调用 C 库(C++ 可 extern “C” 暴露)

1
2
3
4
5
6
7
8
9
// 在 Rust 中声明 C 函数
#[link(name = "mylib")]
extern "C" {
fn c_hello();
}

fn main() {
unsafe { c_hello(); }
}

C++ 调用 Rust(Rust 导出 C ABI)

1
2
3
4
5
// lib.rs
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}
1
2
3
4
5
6
// main.cpp
extern "C" int rust_add(int a, int b);

int main() {
return rust_add(1, 2);
}

实际项目常用 bindgen(C/C++ → Rust 绑定)与 cbindgen(Rust → C 头文件)做接口生成。


六、选型与迁移建议

6.1 何时优先 C++

  • 已有大型 C++ 代码库,团队熟悉 C++。
  • 强依赖现有 C++ 生态(如特定游戏引擎、HPC 库)。
  • 需要与 C ABI 或 C++ ABI 深度耦合、对 ABI 稳定性有要求。
  • 对语言标准与编译器行为有历史兼容需求。

6.2 何时优先 Rust

  • 新项目,尤其重视内存安全与并发正确性。
  • 系统软件、网络服务、命令行工具、WebAssembly 等。
  • 希望减少内存与并发类 bug,愿意接受所有权与生命周期的学习成本。
  • 希望统一构建、测试、格式化(cargo)与静态检查(clippy)。

6.3 迁移策略(C++ → Rust)

  1. 新模块用 Rust:在现有 C++ 系统中用 Rust 写新组件,通过 C ABI 或 gRPC/消息队列与 C++ 交互。
  2. 逐层替换:从边界服务或库开始,用 Rust 重写并保持接口一致。
  3. 双轨维护:关键路径先保留 C++,Rust 实现做并行验证与灰度。
  4. 工具:用 bindgen/cbindgen、cxx 等减少手写 FFI,并补充测试与模糊测试。

小结

主题 C++ 要点 Rust 要点
内存 RAII、智能指针、可手写任意指针 所有权、借用、生命周期、无 GC
安全 依赖规范与工具 类型系统与借用检查强制保证
并发 需谨慎使用锁与原子 类型系统辅助避免数据竞争
抽象 模板、虚函数、Concept 泛型、Trait、trait object
错误处理 异常、错误码、optional Result + ?,显式错误路径
生态与工具 标准委员会、多编译器、多构建系统 Cargo、clippy、rustfmt 统一体验

两者都是系统级语言,C++ 更偏灵活与历史兼容,Rust 更偏安全与可维护。掌握两者有助于在不同场景下做出合适的技术选型,并在需要时进行混合编程或渐进式迁移。


文档中的代码示例均在 C++17/20 与 Rust 2021 下可编译运行,实际项目请以各自代码库与规范为准。