22.1 这节课解决什么问题
第 21 课的两条管线能手工跑通,但"手工"不可发布------人总会漏命令、漏版本。本课把它变成可复现、可审计、一键执行的工程:
① scripts/build-all.sh:一条命令出 Android AAR + iOS xcframework(+ 桌面库)
② CI(GitHub Actions macOS runner):push 即自动出全平台包 + 跑测试
③ release 构建调优与发布前 checklist
④ 知识回顾图 + 结课作业
💡 本课反复出现一个词:可复现(reproducible)。发布物能由"仓库 + 固定版本 + 一条命令"重建,才谈得上灰度、回滚与多人协作。所有版本号(NDK、Xcode、Rust toolchain、uniffi)都要钉死并写进文档/脚本。
22.2 一条命令出全平台:build-all.sh
把第 21 课的手工命令固化成脚本(放在仓库根 scripts/):
bash
#!/usr/bin/env bash
set -euo pipefail # 任何一步失败立刻停
# 0) 版本与工具链钉死(可复现的关键)
RUST_VERSION="1.85.0" # 或 rust-toolchain.toml 钉版本
UNIFFI_VERSION="0.29.0"
ANDROID_TARGETS=("aarch64-linux-android" "x86_64-linux-android")
IOS_TARGETS=("aarch64-apple-ios" "aarch64-apple-ios-sim")
echo "==> [1/6] 测试 core"
cargo test -p my-ai-core
echo "==> [2/6] Android .so"
cargo ndk -t arm64-v8a -t x86_64 -o android-out/jniLibs build -p my_ai_ffi --release
echo "==> [3/6] iOS 静态库 + xcframework"
rustup target add "${IOS_TARGETS[@]}"
for t in "${IOS_TARGETS[@]}"; do
cargo build -p my_ai_ffi --release --target "$t"
done
rm -rf ios-out/MyAiCore.xcframework
xcrun xcframework create \
-library target/aarch64-apple-ios/release/libmy_ai_ffi.a \
-library target/aarch64-apple-ios-sim/release/libmy_ai_ffi.a \
-output ios-out/MyAiCore.xcframework
echo "==> [4/6] 生成绑定"
uniffi-bindgen generate src/lib.rs \
--library target/aarch64-linux-android/release/libmy_ai_ffi.so \
--language kotlin --out-dir android-out/kotlin
uniffi-bindgen generate src/lib.rs \
--library target/aarch64-apple-ios/release/libmy_ai_ffi.a \
--language swift --out-dir ios-out/swift
echo "==> [5/6] 冒烟(Python 通道,21 课保留的回归脚本)"
python3 ../../scripts/smoke.py
echo "==> [6/6] 产物清单"
find android-out ios-out -type f | sort
echo "全部完成 ✅"
配套约定:
脚本幂等:重复执行结果一致(先删旧产物再生成)
产物不进 git:.gitignore 加 android-out/ ios-out/ target/
文档同步:docs/build-notes.md 记录工具版本与踩坑(21 课练习 7)
💡 iOS 步骤在非 macOS 上直接报错(
xcrun不存在)------这正是 CI 要解决的:用 macOS runner 跑全流程 ,开发者本地跑cargo test即可,出包交给 CI。
22.3 CI:push 即出全平台包
GitHub Actions 用 macOS runner(能同时编 iOS/Android/桌面):
yaml
# .github/workflows/release.yml
name: build-all
on:
push:
tags: ["v*"] # 打 tag 才触发正式出包
jobs:
build:
runs-on: macos-latest # ★ 必须 macOS:iOS 交叉编译需要 Xcode
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-linux-android,x86_64-linux-android,
aarch64-apple-ios,aarch64-apple-ios-sim
- name: Install NDK + cargo-ndk
run: |
brew install --cask android-commandlinetools # 或 actions/setup-android
cargo install cargo-ndk
- name: Test core
run: cargo test --workspace
- name: Build all
run: bash scripts/build-all.sh
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: my-ai-${GITHUB_REF_NAME}
path: |
android-out/
ios-out/
CI 三要义:
① 环境与本地一致:toolchain/NDK/Xcode 版本都钉死(否则本地绿、CI 红)
② 双保险:先 cargo test,再 build-all(把"测试"与"出包"同一次跑完)
③ 产物可下载:artifacts/Release 挂包,方便测试同学与双端 Demo 直接拿
💡 版本演进纪律:tag
v0.1.0出的包即对外契约。共享核心要遵守 semver------任何对 UI 可见的 API 变更都 bump 大版本,并在 release notes 里写迁移说明(17 课"导出=冻结签名"的工程版)。
22.4 release 构建调优与产物体检
22.4.1 profile.release 全套
toml
[profile.release]
opt-level = 3 # 速度优先(网络/解析类代码建议)
lto = true # 全程序链接优化(跨 crate 内联,减体积)
codegen-units = 1 # 单编译单元,配合 LTO 效果最好
panic = "abort" # FFI/发布:abort 比 unwind 更省且不会把栈 unwind 漏出去
strip = "symbols" # 去掉符号表,显著减体积(注意:栈回溯会没名字)
⚠️ 权衡:
panic = "abort"会让catch_unwind(16 课 FFI 兜底)失效------发布版通常可接受(core 已用错误返回代替 panic),但要在测试环境用默认 unwind 跑一遍全测试。strip同理:想保留崩溃符号就换成strip = "debuginfo"或单独出符号文件。
22.4.2 出包后体检清单(发布前逐项过)
bash
# 大小
du -sh android-out ios-out
# 架构(每片都要对)
file ios-out/MyAiCore.xcframework/ios-arm64/libmy_ai_ffi.a
# 版本/符号(可选)
strings libmy_ai_ffi.so | grep -i version
| 检查项 | 通过标准 |
|---|---|
cargo test --workspace |
全绿 |
| Python 冒烟 | 通过(19 课保留的回归通道) |
| debug 与 release 各测一次核心 | 行为一致 |
| 产物架构正确 | file/readelf 显示目标平台 |
| 包体在预期范围 | 记录基线,后续版本比较涨幅 |
| 无明文 http 端点 | ATS/网络安全配置就位 |
| 隐私清单 | 网络/用户数据声明齐全 |
| 日志与崩溃上报 | tracing 落盘或接入平台上报;RUST_BACKTRACE 仅 debug |
22.5 桌面 / Web 的快速扩展(同核心,再附赠平台)
同一 core 顺手出桌面与 Web,让"一次编写"兑现到更多平台:
bash
# 桌面:主机库 + 各语言绑定(第 16 课 ctypes / UniFFI 都有现成通道)
cargo build -p my_ai_ffi --release # libmy_ai_ffi.dylib/.dll/.so
uniffi-bindgen generate src/lib.rs --library target/release/libmy_ai_ffi.dylib \
--language python --out-dir desktop-out/python # 或 swift/kotlin
# 桌面整包方案:Tauri(Rust 主进程 + Web UI)可整包复用 core ------ 介绍即可
# Web 子集:wasm32 可复用"模型 + 校验 + 存储接口",网络/SSE 走浏览器原生
rustup target add wasm32-unknown-unknown
💡 范围声明:本课不展开 wasm 的线程与 IO 适配(那是另一个话题)。重点是展示架构红利------UI 换壳,core 不动:移动(21 课)、桌面(本课)、Web 全部消费同一份 17-19 课资产。
22.6 发布前内存 / 安全 / 质量手册(精简版)
内存与线程
- core 单例持有;raw 对象不跨线程共享;锁只在 SQL 语句内;
- 所有跨 FFI 的分配/释放走 UniFFI 成对生成,禁止手写 free;
- 长连接/轮询在页面退出时取消(未来可 drop 即停)。
安全
- API key / token 只存系统钥匙串 / Android Keystore,不进 core 也不进 git;
- 所有出网走 https;参数化 SQL(18 课);不信任上游 JSON(14 课缺字段即报错);
- 会话删除要连消息一起删(数据库 CASCADE + service 双保险)。
质量
- 提交前链条:
fmt → clippy -D warnings → test(10 课)+ 打 tag 前跑 build-all; - 每次升级 uniffi/NDK/工具链都记录在
docs/,回滚有据。
常见坑清单(终极版)
| 坑 | 一句话解法 |
|---|---|
| NDK 版本不一致 | 钉版本:ANDROID_NDK_HOME + CI 一致 |
| iOS bitcode | 新 Xcode 已默认关;别手动开 |
| 回调线程 | 壳侧统一回主线程(20 课) |
| release 优化缺失 | [profile.release] 四件套 |
| 忘 strip | 包体虚胖;上线对比基线 |
| 忘记版本对齐 | uniffi crate == uniffi_bindgen 版本 |
| debug/release 不一致 | 两档都跑核心测试 |
22.7 🗺️ 全课程知识地图(回顾)
基础篇 01--10:语言地基
01 环境 → 02 语法 → 03 字符串/复合类型 → 04 所有权 → 05 生命周期
→ 06 struct/enum/方法 → 07 错误处理 → 08 trait/泛型 → 09 迭代器/闭包 → 10 工程化
高级篇 11--16:并发与跨平台零件
11 智能指针 → 12 线程并发 → 13 async/tokio → 14 网络+JSON
→ 15 SSE 流式 → 16 FFI 手写原理
实战篇 17--22:AI 助手共享核心(小灵)
17 架构(core 分层)→ 18 SQLite 存储 → 19 UniFFI 导出 → 20 壳封装
→ 21 Android/iOS 出包 → 22 CI 与发布收尾
能力闭环:
建模 → 持久化 → 网络/流式 → 跨端导出 → 出包发布
22.8 📝 结课作业
给你自己的一个 App 场景(天气、记账、笔记、或继续打磨小灵)按本架构完整交付:
1. 规划:一句话业务 + FFI 边界(哪些进 Rust、哪些留 UI)
2. 建模:核心类型 + 两个 trait(存储/外部服务)+ 一个用例层
3. 实现:core 用假实现让 service 测试全绿
4. 持久化:换 sqlite 实现,测试零改动
5. 网络:真/假客户端 + 超时重试 + 流式(如业务需要)
6. 绑定:UniFFI 导出 + Python 冒烟通过
7. 壳:一端 UI(SwiftUI/Kotlin/TS 任一)完整跑通
8. 收尾:build-all.sh + CI 出包 + release checklist 过一遍
9. 提交一份 README:架构图、命令、版本、已知限制
验收门禁(课程毕业标准):
- 能独立讲 5 分钟"所有权如何保障我的共享核心安全";
- 能向队友解释"为什么 UI 层不该碰网络与 DB";
- 一份核心,能在 ≥2 个平台出包并被调用;
- 遇到 panic/锁/链接错误时,能定位到 04/12/16/21 的对应知识并给出解法。
✅ 本节小结(课程收官)
- 可复现:脚本 + CI 把双端出包变成"一条命令 + 固定版本";
- release 调优 :
opt-level/LTO/codegen-units/panic/strip权衡取舍;出包前体检清单; - 多平台红利:移动(21 课)+桌面+Web 消费同一 core,壳随便换;
- 工程纪律:semver 管理导出契约、文档记录工具版本、上线 checklist;
- 你的收获:从"Hello World"到"一份 Rust 核心打进 Android/iOS 双端包"全流程;
- 接下来 :可以继续深入的方向------Rust 网络性能调优(hyper/axum 服务端)、
flutter_rust_bridge(Flutter 生态)、共享核心的增量发布与热更新、或者回头用本课程的知识重构手头另一个项目。
祝贺你完成《Rust 跨平台业务核心开发》22 课! 现在你不仅"会写 Rust",而且能用它交付真实的多端产品。保持练习,把结课作业做成一个能拿得出手的作品,就是最好的下一课。🚀