feat(mobile): ios background upload bridge (compile-gated at G3)

Swift 侧(协议与 Android UploadPlugin.kt 逐字一致,编译/真机留 G3):
- UploadBridge.swift:通道/方法/事件/失败码常量 + registerTask 参数解析
  (镜像 UploadProtocol.kt)
- UploadEventBus.swift:单调 seq + 终态 ack 缓存重放(镜像
  UploadEventBus.kt),iOS 增强——未 ack 终态落盘(系统后台拉起投递的
  回调在进程重启后同样重放,技术方案 §6.2 持久化原生回调)
- UploadTaskStore.swift:taskId→URLSession taskIdentifier 映射 plist 持久化
  (Application Support;对应 Android WorkManager 自持映射)
- UploadPlugin.swift:URLSession background
  (com.photolive.photo_live.uploads)+ delegate 失败分类(2xx→completed+etag
  / 403→needsRenew / 4xx→HTTP_4XX / 网络→重试上限后 NETWORK,镜像
  UploadPutExecutor/UploadWorker)、原生重试(5 次上限、10s 指数退避)、
  进度 500ms 节流、queryTask/cancelTask/ackEvents、静默取消
- AppDelegate.swift:implicit engine 通道注册 + 启动重建会话 +
  handleEventsForBackgroundURLSession completionHandler 暂存
- Info.plist:UIBackgroundModes 有意不声明(background URLSession 无需;
  计划措辞按技术方案 §6.2 修正,见 README-ios.md)
- project.pbxproj 登记新 Swift 文件;README-ios.md(Xcode 步骤 + G3 验证
  清单:实际文件上传/持久化回调窗口/服务端补偿/重启重联校准/强制退出/
  低电量延迟/100 张批量)

Dart:NativeTransport 选择矩阵并入 iOS(Platform.isAndroid || isIOS,
FORCE_DART_TRANSPORT 覆盖不变)——协议两侧一致,无新通道代码。

flutter analyze 0 issue;flutter test 219 全绿。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-19 03:42:49 +08:00
parent 4e35bdc0b2
commit 42e1c2e34b
11 changed files with 1146 additions and 33 deletions
@@ -6,8 +6,8 @@
/// (progress/completed/needsRenew/failed×码表)、终态 ack、取消联动、
/// 注册失败归一、多任务路由;
/// - DartHttpTransport:dio 异常 → 传输异常归一(403→renew、取消、网络);
/// - 传输选择:Android 默认原生、FORCE_DART_TRANSPORT 覆盖、VM 缺省
/// DartHttp。
/// - 传输选择:Android/iOS 默认原生(Task 8 起 iOS 并入同一矩阵)、
/// FORCE_DART_TRANSPORT 覆盖、VM 缺省 DartHttp。
library;
import 'dart:async';
@@ -405,22 +405,39 @@ void main() {
});
});
group('传输选择(Android 原生 / FORCE_DART_TRANSPORT)', () {
test('选择矩阵:Android→native;强制 Dart / 非 Android→dart', () {
expect(preferNativeTransport(isAndroid: true), isTrue);
expect(preferNativeTransport(isAndroid: false), isFalse);
group('传输选择(Android/iOS 原生 / FORCE_DART_TRANSPORT)', () {
test('选择矩阵:Android/iOS→native;强制 Dart / 桌面→dart', () {
// Android / iOS:原生桥接(Task 7 / Task 8——协议一致,同一通道)。
expect(
preferNativeTransport(isAndroid: true, forceDartTransport: true),
isFalse,
reason: 'FORCE_DART_TRANSPORT 覆盖',
preferNativeTransport(isAndroid: true, isIOS: false),
isTrue,
reason: 'Android → WorkManager 原生',
);
expect(
preferNativeTransport(isAndroid: true, forceDartTransport: false),
preferNativeTransport(isAndroid: false, isIOS: true),
isTrue,
reason: 'iOS → URLSession background 原生(编译留 G3)',
);
// 桌面 / 测试 VM:Dart dio。
expect(preferNativeTransport(isAndroid: false, isIOS: false), isFalse);
// FORCE_DART_TRANSPORT 覆盖原生选择。
expect(
preferNativeTransport(isAndroid: true, isIOS: false, forceDartTransport: true),
isFalse,
reason: 'FORCE_DART_TRANSPORT 覆盖(Android)',
);
expect(
preferNativeTransport(isAndroid: false, isIOS: true, forceDartTransport: true),
isFalse,
reason: 'FORCE_DART_TRANSPORT 覆盖(iOS)',
);
expect(
preferNativeTransport(isAndroid: true, isIOS: false, forceDartTransport: false),
isTrue,
);
});
test('缺省解析:VM 测试环境(非 Android)→ DartHttpTransport', () {
test('缺省解析:VM 测试环境(非 Android/iOS)→ DartHttpTransport', () {
final resolved = defaultUploadTransport();
expect(resolved, isA<DartHttpTransport>());
resolved.dispose();