diff --git a/app.json b/app.json index 274b66b..98c31d1 100644 --- a/app.json +++ b/app.json @@ -23,7 +23,8 @@ "NSUserNotificationsUsageDescription": "应用需要发送通知以提醒您喝水和站立活动。", "BGTaskSchedulerPermittedIdentifiers": [ "com.expo.modules.backgroundtask.processing", - "com.anonymous.digitalpilates.task" + "com.anonymous.digitalpilates.task", + "com.anonymous.digitalpilates.processing" ], "UIBackgroundModes": [ "processing", diff --git a/app/notification-settings.tsx b/app/notification-settings.tsx index 76e3fec..ba0e00a 100644 --- a/app/notification-settings.tsx +++ b/app/notification-settings.tsx @@ -1,5 +1,4 @@ import { ThemedText } from '@/components/ThemedText'; -import { useAuthGuard } from '@/hooks/useAuthGuard'; import { useI18n } from '@/hooks/useI18n'; import { useNotifications } from '@/hooks/useNotifications'; import { @@ -18,7 +17,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; export default function NotificationSettingsScreen() { const insets = useSafeAreaInsets(); - const { pushIfAuthedElseLogin } = useAuthGuard(); const { t } = useI18n(); const { requestPermission, sendNotification } = useNotifications(); const isLgAvailable = isLiquidGlassAvailable(); diff --git a/ios/OutLive/AppDelegate.swift b/ios/OutLive/AppDelegate.swift index 6711acc..d34a5dc 100644 --- a/ios/OutLive/AppDelegate.swift +++ b/ios/OutLive/AppDelegate.swift @@ -63,12 +63,25 @@ public class AppDelegate: ExpoAppDelegate { forTaskWithIdentifier: identifier, using: nil ) { [weak self] task in + NSLog("[AppDelegate] ✅ 后台任务被系统触发: \(identifier)") // 尝试通知 BackgroundTaskBridge 处理任务 // 如果 bridge 不可用,标记任务完成 self?.handleBackgroundTask(task, identifier: identifier) } NSLog("[AppDelegate] 后台任务已在应用启动时注册: \(identifier)") + + // 额外注册 processing 类型的任务(如果需要) + let processingIdentifier = "com.anonymous.digitalpilates.processing" + BGTaskScheduler.shared.register( + forTaskWithIdentifier: processingIdentifier, + using: nil + ) { [weak self] task in + NSLog("[AppDelegate] ✅ Processing 后台任务被系统触发: \(processingIdentifier)") + self?.handleBackgroundTask(task, identifier: processingIdentifier) + } + + NSLog("[AppDelegate] Processing 后台任务已注册: \(processingIdentifier)") } @available(iOS 13.0, *) @@ -76,11 +89,22 @@ public class AppDelegate: ExpoAppDelegate { NSLog("[AppDelegate] ====== 后台任务被触发 ======") NSLog("[AppDelegate] 任务标识符: \(identifier)") NSLog("[AppDelegate] 任务类型: \(type(of: task))") + NSLog("[AppDelegate] 触发时间: \(Date().description)") + + // 记录任务开始 + let defaults = UserDefaults.standard + defaults.set(Date().timeIntervalSince1970, forKey: "last_background_task_trigger") + NSLog("[AppDelegate] 已记录任务触发时间到UserDefaults") // 设置任务过期处理器(iOS 给的执行时间有限,通常30秒) - task.expirationHandler = { + task.expirationHandler = { [weak self] in NSLog("[AppDelegate] ⚠️ 后台任务即将过期,强制完成") task.setTaskCompleted(success: false) + + // 确保重新调度下一次任务 + if let strongSelf = self { + strongSelf.scheduleNextBackgroundTask(identifier: identifier) + } } // 尝试获取 BackgroundTaskBridge 实例来处理任务 @@ -117,7 +141,9 @@ public class AppDelegate: ExpoAppDelegate { @available(iOS 13.0, *) private func executeBasicBackgroundMaintenance(task: BGTask, identifier: String) { - NSLog("[AppDelegate] 执行基本后台维护任务") + NSLog("[AppDelegate] ===== 执行基本后台维护任务 =====") + NSLog("[AppDelegate] 标识符: \(identifier)") + NSLog("[AppDelegate] 开始时间: \(Date().description)") // 在后台线程执行基本维护 DispatchQueue.global(qos: .background).async { @@ -139,9 +165,28 @@ public class AppDelegate: ExpoAppDelegate { @available(iOS 13.0, *) private func scheduleNextBackgroundTask(identifier: String) { - let request = BGAppRefreshTaskRequest(identifier: identifier) - // 设置最早开始时间(15分钟后) - request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + NSLog("[AppDelegate] ===== 调度下一次后台任务 =====") + NSLog("[AppDelegate] 标识符: \(identifier)") + + let request: BGTaskRequest + if identifier.contains("processing") { + request = BGProcessingTaskRequest(identifier: identifier) + if let processingRequest = request as? BGProcessingTaskRequest { + processingRequest.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + processingRequest.requiresNetworkConnectivity = false + processingRequest.requiresExternalPower = false + } + } else { + request = BGAppRefreshTaskRequest(identifier: identifier) + if let appRefreshRequest = request as? BGAppRefreshTaskRequest { + appRefreshRequest.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + } + } + + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + let scheduledTime = formatter.string(from: Date(timeIntervalSinceNow: 15 * 60)) + NSLog("[AppDelegate] 计划执行时间: \(scheduledTime)") do { try BGTaskScheduler.shared.submit(request) diff --git a/ios/OutLive/BackgroundTaskBridge.swift b/ios/OutLive/BackgroundTaskBridge.swift index 906083c..b235291 100644 --- a/ios/OutLive/BackgroundTaskBridge.swift +++ b/ios/OutLive/BackgroundTaskBridge.swift @@ -390,14 +390,27 @@ class BackgroundTaskBridge: RCTEventEmitter { BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier) NSLog("[BackgroundTaskBridge] 已取消之前的任务请求: \(identifier)") - // 使用 BGAppRefreshTaskRequest 而不是 BGProcessingTaskRequest - // BGAppRefreshTaskRequest 更适合定期刷新数据的场景 - let request = BGAppRefreshTaskRequest(identifier: identifier) + // 根据任务类型选择合适的请求类 + // processing 类型使用 BGProcessingTaskRequest,fetch 类型使用 BGAppRefreshTaskRequest + let request: BGTaskRequest + + switch kind { + case .processing: + request = BGProcessingTaskRequest(identifier: identifier) + case .refresh: + request = BGAppRefreshTaskRequest(identifier: identifier) + } // 设置最早开始时间 // 注意:实际执行时间由系统决定,可能会延迟 // 系统通常会在设备空闲、网络连接良好、电量充足时执行 - request.earliestBeginDate = Date(timeIntervalSinceNow: delay) + if let appRefreshRequest = request as? BGAppRefreshTaskRequest { + appRefreshRequest.earliestBeginDate = Date(timeIntervalSinceNow: delay) + } else if let processingRequest = request as? BGProcessingTaskRequest { + processingRequest.earliestBeginDate = Date(timeIntervalSinceNow: delay) + processingRequest.requiresNetworkConnectivity = requiresNetworkConnectivity + processingRequest.requiresExternalPower = requiresExternalPower + } do { try BGTaskScheduler.shared.submit(request) @@ -429,7 +442,10 @@ class BackgroundTaskBridge: RCTEventEmitter { guard let self else { return } self.currentTask = task - NSLog("[BackgroundTaskBridge] 开始处理后台任务: \(task.identifier)") + NSLog("[BackgroundTaskBridge] ===== 开始处理后台任务 =====") + NSLog("[BackgroundTaskBridge] 任务标识符: \(task.identifier)") + NSLog("[BackgroundTaskBridge] 任务类型: \(type(of: task))") + NSLog("[BackgroundTaskBridge] 当前时间: \(Date().description)") if self.identifier == nil { self.identifier = task.identifier @@ -474,8 +490,10 @@ class BackgroundTaskBridge: RCTEventEmitter { return } - NSLog("[BackgroundTaskBridge] 发送后台任务执行事件到JS") + NSLog("[BackgroundTaskBridge] 准备发送后台任务执行事件到JS") + NSLog("[BackgroundTaskBridge] hasListeners: \(self.hasListeners)") self.emitTaskToJS(payload: payload) + NSLog("[BackgroundTaskBridge] ✅ 事件已发送到JS层") } } @@ -514,8 +532,22 @@ class BackgroundTaskBridge: RCTEventEmitter { // 先取消之前的任务请求,避免重复 BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier) - let request = BGAppRefreshTaskRequest(identifier: identifier) - request.earliestBeginDate = Date(timeIntervalSinceNow: self.defaultDelay) + // 使用与配置时相同的任务类型 + let request: BGTaskRequest + switch self.kind { + case .processing: + request = BGProcessingTaskRequest(identifier: identifier) + if let processingRequest = request as? BGProcessingTaskRequest { + processingRequest.earliestBeginDate = Date(timeIntervalSinceNow: self.defaultDelay) + processingRequest.requiresNetworkConnectivity = self.requiresNetworkConnectivity + processingRequest.requiresExternalPower = self.requiresExternalPower + } + case .refresh: + request = BGAppRefreshTaskRequest(identifier: identifier) + if let appRefreshRequest = request as? BGAppRefreshTaskRequest { + appRefreshRequest.earliestBeginDate = Date(timeIntervalSinceNow: self.defaultDelay) + } + } do { try BGTaskScheduler.shared.submit(request) @@ -542,11 +574,14 @@ class BackgroundTaskBridge: RCTEventEmitter { pendingTaskTimeoutWorkItem?.cancel() + NSLog("[BackgroundTaskBridge] 缓存后台任务等待JS监听器...") + NSLog("[BackgroundTaskBridge] 等待超时时间: \(Int(pendingTaskWaitTimeout))秒") + let timeoutItem = DispatchWorkItem { [weak self] in guard let self else { return } guard self.waitingForJSListeners else { return } - NSLog("[BackgroundTaskBridge] 等待 JS 监听器超时,执行默认处理") + NSLog("[BackgroundTaskBridge] ⚠️ 等待 JS 监听器超时,执行默认处理") self.waitingForJSListeners = false self.pendingTaskPayload = nil self.pendingTaskTimeoutWorkItem = nil @@ -555,6 +590,7 @@ class BackgroundTaskBridge: RCTEventEmitter { pendingTaskTimeoutWorkItem = timeoutItem queue.asyncAfter(deadline: .now() + pendingTaskWaitTimeout, execute: timeoutItem) + NSLog("[BackgroundTaskBridge] 已设置超时计时器") } private func emitPendingTaskIfPossible() { @@ -573,10 +609,12 @@ class BackgroundTaskBridge: RCTEventEmitter { private func emitTaskToJS(payload: [String: Any]) { DispatchQueue.main.async { [weak self] in guard let self else { return } + NSLog("[BackgroundTaskBridge] 正在发送事件到JS层...") self.sendEvent( withName: "BackgroundTaskBridge.execute", body: payload ) + NSLog("[BackgroundTaskBridge] ✅ 事件发送成功") } } } diff --git a/ios/OutLive/Info.plist b/ios/OutLive/Info.plist index e313e72..4b1db45 100644 --- a/ios/OutLive/Info.plist +++ b/ios/OutLive/Info.plist @@ -6,6 +6,7 @@ com.expo.modules.backgroundtask.processing com.anonymous.digitalpilates.task + com.anonymous.digitalpilates.processing CADisableMinimumFrameDurationOnPhone @@ -26,7 +27,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0.24 + 1.0.25 CFBundleSignature ???? CFBundleURLTypes diff --git a/services/backgroundTaskManagerV2.ts b/services/backgroundTaskManagerV2.ts index 062b78a..485b91b 100644 --- a/services/backgroundTaskManagerV2.ts +++ b/services/backgroundTaskManagerV2.ts @@ -396,7 +396,7 @@ export class BackgroundTaskManagerV2 { logger.info('[BackgroundTaskManagerV2] 配置后台任务...'); await NativeBackgroundModule.configure({ identifier: BACKGROUND_TASK_IDENTIFIER, - taskType: 'refresh', + taskType: 'processing', // 使用 processing 类型,与 Info.plist 中的配置匹配 requiresNetworkConnectivity: false, requiresExternalPower: false, defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS, @@ -469,24 +469,43 @@ export class BackgroundTaskManagerV2 { private async handleBackgroundExecution(): Promise { if (this.executingPromise) { - logger.info('[BackgroundTaskManagerV2] 已有后台任务在执行,忽略重复触发'); + logger.warn('[BackgroundTaskManagerV2] ⚠️ 已有后台任务在执行,忽略重复触发'); return; } const startTime = Date.now(); - logger.info('[BackgroundTaskManagerV2] 开始执行后台任务'); + logger.info('[BackgroundTaskManagerV2] ===== 开始执行后台任务 ====='); + logger.info(`[BackgroundTaskManagerV2] 执行时间: ${new Date().toLocaleString()}`); + logger.info('[BackgroundTaskManagerV2] 检查通知权限...'); + + // 记录任务开始时间 + await AsyncStorage.setItem('@last_background_task_start', Date.now().toString()); this.executingPromise = executeBackgroundTasks() .then(async () => { const executionTime = Date.now() - startTime; - logger.info(`[BackgroundTaskManagerV2] 后台任务执行成功,耗时: ${executionTime}ms`); + logger.info(`[BackgroundTaskManagerV2] ✅ 后台任务执行成功,耗时: ${executionTime}ms`); if (isIosBackgroundModuleAvailable) { try { + // 确保任务完成前有足够的时间完成所有操作 + logger.info('[BackgroundTaskManagerV2] 等待1秒确保所有操作完成...'); + await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒确保所有操作完成 + await NativeBackgroundModule.complete(true, DEFAULT_RESCHEDULE_INTERVAL_SECONDS); - logger.info('[BackgroundTaskManagerV2] 已标记后台任务成功完成并重新调度'); + logger.info('[BackgroundTaskManagerV2] ✅ 已标记后台任务成功完成并重新调度'); + + // 记录成功执行的时间戳 + const successTime = Date.now(); + await AsyncStorage.setItem('@last_background_execution_success', successTime.toString()); + logger.info(`[BackgroundTaskManagerV2] ✅ 成功执行时间戳: ${new Date(successTime).toLocaleString()}`); + + // 检查后台任务状态 + const status = await this.getStatus(); + const statusText = await this.checkStatus(); + logger.info(`[BackgroundTaskManagerV2] 后台任务状态检查: ${status} (${statusText})`); } catch (error) { - logger.error('[BackgroundTaskManagerV2] 标记后台任务成功完成失败', error); + logger.error('[BackgroundTaskManagerV2] ❌ 标记后台任务成功完成失败', error); // 即使标记失败,也尝试手动重新调度 await this.scheduleNextTask(); } @@ -494,14 +513,14 @@ export class BackgroundTaskManagerV2 { }) .catch(async (error) => { const executionTime = Date.now() - startTime; - logger.error(`[BackgroundTaskManagerV2] 后台任务执行失败,耗时: ${executionTime}ms`, error); + logger.error(`[BackgroundTaskManagerV2] ❌ 后台任务执行失败,耗时: ${executionTime}ms`, error); if (isIosBackgroundModuleAvailable) { try { await NativeBackgroundModule.complete(false, DEFAULT_RESCHEDULE_INTERVAL_SECONDS); logger.info('[BackgroundTaskManagerV2] 已标记后台任务失败并重新调度'); } catch (completionError) { - logger.error('[BackgroundTaskManagerV2] 标记后台任务失败状态时出错', completionError); + logger.error('[BackgroundTaskManagerV2] ❌ 标记后台任务失败状态时出错', completionError); // 即使标记失败,也尝试手动重新调度 await this.scheduleNextTask(); } @@ -509,6 +528,7 @@ export class BackgroundTaskManagerV2 { }) .finally(() => { this.executingPromise = null; + logger.info('[BackgroundTaskManagerV2] ===== 后台任务处理完成 ====='); }); await this.executingPromise;