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:
@@ -1,14 +1,17 @@
|
||||
/// 上传传输抽象与双实现(P3 计划 Task 7)。
|
||||
/// 上传传输抽象与双实现(P3 计划 Task 7;Task 8 扩展 iOS 选择分支)。
|
||||
///
|
||||
/// 引擎的传输步骤(presign PUT)经 [Transport] 接口委托:
|
||||
/// - **[DartHttpTransport]**(Task 6 逻辑原样抽出):裸 dio 流式 PUT +
|
||||
/// 进度计数流 + 取消令牌——iOS / 桌面 / 调试路径(dart-define
|
||||
/// 进度计数流 + 取消令牌——桌面 / 调试路径(dart-define
|
||||
/// `FORCE_DART_TRANSPORT=true` 强制启用);
|
||||
/// - **[NativeTransport]**(Task 7 新增,Android):MethodChannel
|
||||
/// `photolive/uploads` 注册 WorkManager 后台工作(expedited + dataSync
|
||||
/// 前台服务),EventChannel `photolive/uploads/events` 回流
|
||||
/// progress/completed/failed/needsRenew 事件——App 切后台/锁屏后续传
|
||||
/// 由系统调度承担。
|
||||
/// - **[NativeTransport]**(Task 7 新增,Android;Task 8 起含 iOS):
|
||||
/// MethodChannel `photolive/uploads` 注册原生后台上传——Android =
|
||||
/// WorkManager 后台工作(expedited + dataSync 前台服务);iOS =
|
||||
/// URLSession background(identifier com.photolive.photo_live.uploads,
|
||||
/// 编译留 G3)。两平台协议逐字一致(Kotlin `UploadProtocol.kt` /
|
||||
/// Swift `UploadBridge.swift`),EventChannel `photolive/uploads/events`
|
||||
/// 回流 progress/completed/failed/needsRenew 事件——App 切后台/锁屏后
|
||||
/// 续传由系统调度承担。
|
||||
///
|
||||
/// 失败语义(两实现归一为 [TransportException] 族,引擎分类见
|
||||
/// upload_engine.dart `_classifyFailure`):
|
||||
@@ -197,10 +200,11 @@ class DartHttpTransport implements Transport {
|
||||
}
|
||||
}
|
||||
|
||||
// ── 原生(WorkManager)实现 ──────────────────────────────────────────────
|
||||
// ── 原生(Android WorkManager / iOS URLSession background)实现 ─────────
|
||||
|
||||
/// 平台通道名(与 Android UploadPlugin 一致;Kotlin 侧常量见
|
||||
/// UploadProtocol.kt)。
|
||||
/// 平台通道名(与 Android `UploadPlugin.kt` / iOS `UploadPlugin.swift`
|
||||
/// 一致;原生常量见 UploadProtocol.kt 与 UploadBridge.swift——Task 8 起
|
||||
/// iOS 为同一协议的 Swift 镜像,无协议分歧)。
|
||||
abstract final class UploadBridgeChannels {
|
||||
/// 命令通道:registerTask / cancelTask / queryTask / ackEvents。
|
||||
static const String methods = 'photolive/uploads';
|
||||
@@ -243,11 +247,12 @@ class _NativeWaiter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Android 原生传输:registerTask 入队 WorkManager(expedited + dataSync
|
||||
/// 前台服务),事件流回流终态/进度。
|
||||
/// Android/iOS 原生传输:registerTask 入队原生后台上传(Android =
|
||||
/// WorkManager;iOS = URLSession background),事件流回流终态/进度。
|
||||
///
|
||||
/// 生命周期安全:事件带单调 seq;终态事件在 Dart ack 前由原生侧缓存,
|
||||
/// 重新 listen 时重放——进程重启/切后台恢复后不丢完成信号。进度事件不
|
||||
/// 重新 listen 时重放——进程重启/切后台恢复后不丢完成信号(iOS 侧缓存
|
||||
/// 另落盘,系统后台拉起投递的终态在进程重启后同样重放)。进度事件不
|
||||
/// 重放(瞬态)。
|
||||
class NativeTransport implements Transport {
|
||||
NativeTransport({NativeUploadChannel? channel})
|
||||
@@ -400,21 +405,30 @@ class NativeTransport implements Transport {
|
||||
|
||||
// ── 传输选择 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// dart-define FORCE_DART_TRANSPORT=true:Android 也强制 Dart dio 路径
|
||||
/// (桥接层问题排查用)。
|
||||
/// dart-define FORCE_DART_TRANSPORT=true:Android/iOS 也强制 Dart dio
|
||||
/// 路径(桥接层问题排查用)。
|
||||
const bool kForceDartTransport = bool.fromEnvironment('FORCE_DART_TRANSPORT');
|
||||
|
||||
/// 传输选择纯函数(可测):Android 且未强制 Dart → 原生。
|
||||
/// 传输选择纯函数(可测):Android 或 iOS 且未强制 Dart → 原生。
|
||||
///
|
||||
/// Task 8 起含 iOS:协议与 Android 逐字一致(同一 [NativeTransport]
|
||||
/// 经同一通道名驱动 Swift 侧 UploadPlugin——iOS 编译/真机验证留 G3,
|
||||
/// 代码路径在此之前即纳入原生选择)。
|
||||
bool preferNativeTransport({
|
||||
required bool isAndroid,
|
||||
required bool isIOS,
|
||||
bool forceDartTransport = kForceDartTransport,
|
||||
}) =>
|
||||
isAndroid && !forceDartTransport;
|
||||
(isAndroid || isIOS) && !forceDartTransport;
|
||||
|
||||
/// 默认传输解析:Android → [NativeTransport](WorkManager 后台续传);
|
||||
/// 其余平台 / 强制覆盖 → [DartHttpTransport]。
|
||||
/// 默认传输解析:Android/iOS → [NativeTransport](WorkManager /
|
||||
/// URLSession background 后台续传);其余平台 / 强制覆盖 →
|
||||
/// [DartHttpTransport]。
|
||||
Transport defaultUploadTransport() {
|
||||
if (preferNativeTransport(isAndroid: !kIsWeb && Platform.isAndroid)) {
|
||||
if (preferNativeTransport(
|
||||
isAndroid: !kIsWeb && Platform.isAndroid,
|
||||
isIOS: !kIsWeb && Platform.isIOS,
|
||||
)) {
|
||||
return NativeTransport();
|
||||
}
|
||||
return DartHttpTransport();
|
||||
|
||||
Reference in New Issue
Block a user