observer
动物实时识别 App(Flutter 版)。Android / iOS 一套代码,后端接口与支付见
docs/PaymentApi.md。
Android 打包
./build_apk.sh # 用当前 pubspec 版本构建,产物 build/app/outputs/flutter-apk/observer-x.y.z.apk
./build_apk.sh --bump # 自动递增 patch+1、build+1 后构建
./build_apk.sh 1.0.7 # 用指定版本(versionName)构建,build+1
产物命名 observer-x.y.z.apk(管理端上传版本号从文件名识别);构建的
app-release.apk 中间产物由脚本清理,只保留规范命名文件。
iOS 真机部署(iPhone)
构建与安装
# 生产包直接构建即可:默认 API_BASE_URL 为线上域名(lib/config/app_config.dart),无需传参
flutter build ios --release
# 仅本地联调(后端跑在 Mac 上、手机连同一 Wi-Fi)时才覆盖为 Mac 局域网 IP:
# flutter build ios --release --dart-define=API_BASE_URL=http://<Mac局域网IP>:8080
# 安装到真机(UDID 可用 `xcrun devicectl list devices` 查询)
xcrun devicectl device install app --device <UDID> build/ios/iphoneos/Runner.app
# 启动并抓控制台日志(--terminate-existing 先杀掉旧实例)
xcrun devicectl device process launch --console --terminate-existing \
--device <UDID> com.observer.app
注意事项(踩过的坑)
- debug 构建不能在真机上从桌面图标启动:iOS 14+ 会提示
"In iOS 14+, debug mode Flutter apps can only be launched from Flutter tooling"。
debug 调试必须用
flutter run -d <设备ID>或 Xcode IDE 启动(flutter devices查设备ID); 从图标启动只对 release 构建有效。 flutter run真机 debug 附接失败(errno 49,多次复现):Xcode 构建、安装、 启动都成功,attach 阶段报OS Error: Can't assign requested address, errno = 49工具即退出——与本机 VPN(utun 隧道)环境相关,断 VPN 后可恢复。 需要真机验证时一律用上方 release + devicectl 流程(不依赖 attach);只有需要 热重载/看 debugPrint 才用flutter run,遇 errno 49 先断 VPN 重试。
端侧推理加速(GPU / CoreML)
识别慢的根因是 yolov8s@1280 推理量大(CPU 4 线程约每秒不到 1 帧),
TfliteDetector.fromBuffer 加载模型时按平台挂加速 delegate,均为浮点计算不降精度
(区别于 int8 量化掉点):
| 平台 | delegate | 说明 |
|---|---|---|
| Android | GpuDelegateV2 |
TFLite GPU delegate;依赖 libtensorflowlite_gpu_jni.so,已 vendor 到 android/app/src/main/jniLibs/arm64-v8a/(AAR 因 AGP 9 namespace 冲突保持排除,升级 tflite_flutter 时需同步换 .so,版本对齐 base 2.11.0) |
| iOS | CoreMlDelegate |
Core ML(苹果 ANE/GPU,插件 pod 自带 TensorFlowLiteSwift/CoreML,无需额外依赖) |
delegate 初始化失败(老设备/驱动/符号缺失)自动回退纯 CPU 4 线程,最后才返回 null
(仅预览不识别)。生效与否看日志:加载模型时输出
[TfliteDetector] 加速生效 model=xxx (CoreML|GPU),回退输出 回退 CPU 及原因。
GPU delegate 默认允许 FP16 计算(YOLO 类精度损失可忽略);如需全精度改为传
GpuDelegateOptionsV2(isPrecisionLossAllowed: false)。
模型热更新(多数据集模型)
模型与 APK 更新走独立通道:启动时拉取 GET /api/v1/app/update 随附的
models 目录(公开接口,无需登录),与 UpdateChecker 的 APK 检查并行。
- 目录条目:
{datasetId, datasetName, variant, version, labels[], sizeBytes, sha256, downloadUrl, coverUrl}——双档位(2026-09-03):每数据集至多 2 条 = 高识别 s (@1280 精度优先,默认)+ 高性能 n(@704 速度优先)各自的当前版本,条目带variant(s/n);服务器未发布模型时不返回models字段,App 无模型可用, 相机页仅预览不识别。 - 下载入口:相机页设置弹层「模型清单」按需下载/使用(封面缩略图 2 列网格, 每数据集两档各占一格并带档位角标;未下载点击「使用」显示进度,完成自动激活; 已激活再次点击取消;下载中可取消)。档位切换:弹层顶部「识别模式」分段控件 (s 高识别 / n 高性能,默认 s)持久化本地,切换即热加载新档位已激活模型。
- 存储:应用私有目录
models/<datasetId>/<variant>/(双档位 2026-09-03, 原无 variant 目录与存量 s 档一致——s 档复用models/<datasetId>/同级读取, 目录键 = 档位标识符),含model.tflite、labels.json、meta.json(meta 记录{version, sha256})。版本与摘要都未变化时跳过下载;变化则下载到.part临时文件、sha256 校验通过后原子 rename 替换,失败重试一次并保留旧模型, 下次启动再试——检查记账按(datasetId, variant)独立。 - 清理:服务器下线的数据集下次同步时删除本地对应目录(两档都无条目时才删)。
- 并行推理合并:识别时加载当前识别档位下全部已激活模型(
DetectorWorkerisolate 内逐模型加载,单个失败不影响其他),同帧各模型独立推理后按类别分组做 跨模型 NMS(同类别不同模型检出同一目标取高分去重,不同类别互不压制), 结果叠加modelName(数据集名+档位)标注来源。
实现:lib/models/model_manager.dart(下载/校验/持久化,ModelManager
单例 + ChangeNotifier,条目身份含档位)、lib/detection/detector_worker.dart
(多模型并行推理与 mergeAcrossModels)、lib/camera/camera_screen.dart
(启动同步 + 设置弹层「识别模式」切换 + 诊断行展示模型列表)。
- 模型输入是 NHWC:训练导出的模型需做字节级手术(开头 TRANSPOSE→RESHAPE, 输入 [1,320,320,3])再发布给 App,否则 iOS 报 "Node number 0 (TRANSPOSE) failed to prepare"。
- 模拟器黑屏:本机 iOS 模拟器 Impeller 渲染黑屏,验证 UI 用 VM service
(
flutter run输出里的 DevTools 地址),或直接真机验证。