feat(background-task): 实现原生与JS层的任务同步机制
解决后台任务在JS监听器未就绪时丢失的问题。新增任务缓存队列,当检测到无JS监听器时将任务暂存,并启动20秒超时计时器等待JS初始化完成。JS层通过markJSReady接口通知原生层准备就绪,触发缓存任务的立即执行。超时后自动切换到默认处理逻辑,确保任务不丢失。
This commit is contained in:
@@ -28,4 +28,7 @@ RCT_EXTERN_METHOD(backgroundRefreshStatus:(RCTPromiseResolveBlock)resolver
|
|||||||
RCT_EXTERN_METHOD(simulateLaunch:(RCTPromiseResolveBlock)resolver
|
RCT_EXTERN_METHOD(simulateLaunch:(RCTPromiseResolveBlock)resolver
|
||||||
rejecter:(RCTPromiseRejectBlock)rejecter)
|
rejecter:(RCTPromiseRejectBlock)rejecter)
|
||||||
|
|
||||||
|
RCT_EXTERN_METHOD(markJSReady:(RCTPromiseResolveBlock)resolver
|
||||||
|
rejecter:(RCTPromiseRejectBlock)rejecter)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
|||||||
private var requiresExternalPower = false
|
private var requiresExternalPower = false
|
||||||
private var defaultDelay: TimeInterval = 60 * 30 // 30 minutes
|
private var defaultDelay: TimeInterval = 60 * 30 // 30 minutes
|
||||||
private var currentTask: BGTask?
|
private var currentTask: BGTask?
|
||||||
|
private let pendingTaskWaitTimeout: TimeInterval = 20
|
||||||
|
private var waitingForJSListeners = false
|
||||||
|
private var pendingTaskPayload: [String: Any]?
|
||||||
|
private var pendingTaskTimeoutWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
@@ -49,6 +53,9 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
|||||||
|
|
||||||
override func startObserving() {
|
override func startObserving() {
|
||||||
hasListeners = true
|
hasListeners = true
|
||||||
|
queue.async { [weak self] in
|
||||||
|
self?.emitPendingTaskIfPossible()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopObserving() {
|
override func stopObserving() {
|
||||||
@@ -345,6 +352,22 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func markJSReady(
|
||||||
|
_ resolver: @escaping RCTPromiseResolveBlock,
|
||||||
|
rejecter: @escaping RCTPromiseRejectBlock
|
||||||
|
) {
|
||||||
|
queue.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
|
let hadPendingTask = self.waitingForJSListeners && self.pendingTaskPayload != nil
|
||||||
|
self.emitPendingTaskIfPossible()
|
||||||
|
resolver([
|
||||||
|
"pendingTaskFlushed": hadPendingTask
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Private helpers
|
// MARK: - Private helpers
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
@available(iOS 13.0, *)
|
||||||
@@ -408,6 +431,11 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
|||||||
self.currentTask = task
|
self.currentTask = task
|
||||||
NSLog("[BackgroundTaskBridge] 开始处理后台任务: \(task.identifier)")
|
NSLog("[BackgroundTaskBridge] 开始处理后台任务: \(task.identifier)")
|
||||||
|
|
||||||
|
if self.identifier == nil {
|
||||||
|
self.identifier = task.identifier
|
||||||
|
NSLog("[BackgroundTaskBridge] 使用任务标识符初始化 identifier: \(task.identifier)")
|
||||||
|
}
|
||||||
|
|
||||||
// 设置任务过期处理器
|
// 设置任务过期处理器
|
||||||
task.expirationHandler = { [weak self] in
|
task.expirationHandler = { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -435,28 +463,29 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let payload: [String: Any] = [
|
||||||
|
"identifier": self.identifier ?? task.identifier,
|
||||||
|
"timestamp": Date().timeIntervalSince1970 * 1000
|
||||||
|
]
|
||||||
|
|
||||||
guard self.hasListeners else {
|
guard self.hasListeners else {
|
||||||
NSLog("[BackgroundTaskBridge] 没有JS监听器,执行默认处理并重新调度")
|
NSLog("[BackgroundTaskBridge] 暂无 JS 监听器,等待 JS 初始化 (最多 \(Int(self.pendingTaskWaitTimeout)) 秒)")
|
||||||
// 即使没有JS监听器,也要执行基本的任务处理
|
self.cacheTaskForLater(payload: payload)
|
||||||
self.executeDefaultTaskAndReschedule()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog("[BackgroundTaskBridge] 发送后台任务执行事件到JS")
|
NSLog("[BackgroundTaskBridge] 发送后台任务执行事件到JS")
|
||||||
DispatchQueue.main.async {
|
self.emitTaskToJS(payload: payload)
|
||||||
self.sendEvent(
|
|
||||||
withName: "BackgroundTaskBridge.execute",
|
|
||||||
body: [
|
|
||||||
"identifier": self.identifier ?? "",
|
|
||||||
"timestamp": Date().timeIntervalSince1970 * 1000
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
@available(iOS 13.0, *)
|
||||||
private func executeDefaultTaskAndReschedule() {
|
private func executeDefaultTaskAndReschedule() {
|
||||||
|
waitingForJSListeners = false
|
||||||
|
pendingTaskPayload = nil
|
||||||
|
pendingTaskTimeoutWorkItem?.cancel()
|
||||||
|
pendingTaskTimeoutWorkItem = nil
|
||||||
|
|
||||||
// 执行默认的后台任务逻辑(当没有JS监听器时)
|
// 执行默认的后台任务逻辑(当没有JS监听器时)
|
||||||
NSLog("[BackgroundTaskBridge] 执行默认后台任务逻辑")
|
NSLog("[BackgroundTaskBridge] 执行默认后台任务逻辑")
|
||||||
|
|
||||||
@@ -506,4 +535,48 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func cacheTaskForLater(payload: [String: Any]) {
|
||||||
|
waitingForJSListeners = true
|
||||||
|
pendingTaskPayload = payload
|
||||||
|
|
||||||
|
pendingTaskTimeoutWorkItem?.cancel()
|
||||||
|
|
||||||
|
let timeoutItem = DispatchWorkItem { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
guard self.waitingForJSListeners else { return }
|
||||||
|
|
||||||
|
NSLog("[BackgroundTaskBridge] 等待 JS 监听器超时,执行默认处理")
|
||||||
|
self.waitingForJSListeners = false
|
||||||
|
self.pendingTaskPayload = nil
|
||||||
|
self.pendingTaskTimeoutWorkItem = nil
|
||||||
|
self.executeDefaultTaskAndReschedule()
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingTaskTimeoutWorkItem = timeoutItem
|
||||||
|
queue.asyncAfter(deadline: .now() + pendingTaskWaitTimeout, execute: timeoutItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func emitPendingTaskIfPossible() {
|
||||||
|
guard hasListeners else { return }
|
||||||
|
guard waitingForJSListeners, let payload = pendingTaskPayload else { return }
|
||||||
|
|
||||||
|
pendingTaskTimeoutWorkItem?.cancel()
|
||||||
|
pendingTaskTimeoutWorkItem = nil
|
||||||
|
waitingForJSListeners = false
|
||||||
|
pendingTaskPayload = nil
|
||||||
|
|
||||||
|
NSLog("[BackgroundTaskBridge] JS 监听器已就绪,发送缓存的后台任务事件")
|
||||||
|
emitTaskToJS(payload: payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func emitTaskToJS(payload: [String: Any]) {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
self.sendEvent(
|
||||||
|
withName: "BackgroundTaskBridge.execute",
|
||||||
|
body: payload
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -363,6 +363,15 @@ export class BackgroundTaskManagerV2 {
|
|||||||
this.handleTaskExpiration();
|
this.handleTaskExpiration();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (typeof NativeBackgroundModule.markJSReady === 'function') {
|
||||||
|
try {
|
||||||
|
await NativeBackgroundModule.markJSReady();
|
||||||
|
logger.info('[BackgroundTaskManagerV2] 已通知原生层 JS 监听器就绪');
|
||||||
|
} catch (readyError) {
|
||||||
|
logger.warn('[BackgroundTaskManagerV2] 通知原生层 JS 准备状态失败', readyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('[BackgroundTaskManagerV2] 事件监听器注册完成');
|
logger.info('[BackgroundTaskManagerV2] 事件监听器注册完成');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user