feat(background): 增强iOS后台任务系统,添加processing任务类型支持

- 添加新的processing任务标识符到iOS配置文件
- 重构BackgroundTaskBridge支持不同任务类型(refresh/processing)
- 增强后台任务日志记录和调试信息
- 修复任务类型配置不匹配问题
- 改进任务调度逻辑和错误处理机制
- 添加任务执行时间戳记录用于调试
- 移除notification-settings中未使用的AuthGuard依赖
This commit is contained in:
richarjiang
2025-11-13 17:12:57 +08:00
parent 2dca3253e6
commit d282abd146
6 changed files with 129 additions and 26 deletions

View File

@@ -23,7 +23,8 @@
"NSUserNotificationsUsageDescription": "应用需要发送通知以提醒您喝水和站立活动。", "NSUserNotificationsUsageDescription": "应用需要发送通知以提醒您喝水和站立活动。",
"BGTaskSchedulerPermittedIdentifiers": [ "BGTaskSchedulerPermittedIdentifiers": [
"com.expo.modules.backgroundtask.processing", "com.expo.modules.backgroundtask.processing",
"com.anonymous.digitalpilates.task" "com.anonymous.digitalpilates.task",
"com.anonymous.digitalpilates.processing"
], ],
"UIBackgroundModes": [ "UIBackgroundModes": [
"processing", "processing",

View File

@@ -1,5 +1,4 @@
import { ThemedText } from '@/components/ThemedText'; import { ThemedText } from '@/components/ThemedText';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useNotifications } from '@/hooks/useNotifications'; import { useNotifications } from '@/hooks/useNotifications';
import { import {
@@ -18,7 +17,6 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
export default function NotificationSettingsScreen() { export default function NotificationSettingsScreen() {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const { pushIfAuthedElseLogin } = useAuthGuard();
const { t } = useI18n(); const { t } = useI18n();
const { requestPermission, sendNotification } = useNotifications(); const { requestPermission, sendNotification } = useNotifications();
const isLgAvailable = isLiquidGlassAvailable(); const isLgAvailable = isLiquidGlassAvailable();

View File

@@ -63,12 +63,25 @@ public class AppDelegate: ExpoAppDelegate {
forTaskWithIdentifier: identifier, forTaskWithIdentifier: identifier,
using: nil using: nil
) { [weak self] task in ) { [weak self] task in
NSLog("[AppDelegate] ✅ 后台任务被系统触发: \(identifier)")
// BackgroundTaskBridge // BackgroundTaskBridge
// bridge // bridge
self?.handleBackgroundTask(task, identifier: identifier) self?.handleBackgroundTask(task, identifier: identifier)
} }
NSLog("[AppDelegate] 后台任务已在应用启动时注册: \(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, *) @available(iOS 13.0, *)
@@ -76,11 +89,22 @@ public class AppDelegate: ExpoAppDelegate {
NSLog("[AppDelegate] ====== 后台任务被触发 ======") NSLog("[AppDelegate] ====== 后台任务被触发 ======")
NSLog("[AppDelegate] 任务标识符: \(identifier)") NSLog("[AppDelegate] 任务标识符: \(identifier)")
NSLog("[AppDelegate] 任务类型: \(type(of: task))") 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 // iOS 30
task.expirationHandler = { task.expirationHandler = { [weak self] in
NSLog("[AppDelegate] ⚠️ 后台任务即将过期,强制完成") NSLog("[AppDelegate] ⚠️ 后台任务即将过期,强制完成")
task.setTaskCompleted(success: false) task.setTaskCompleted(success: false)
//
if let strongSelf = self {
strongSelf.scheduleNextBackgroundTask(identifier: identifier)
}
} }
// BackgroundTaskBridge // BackgroundTaskBridge
@@ -117,7 +141,9 @@ public class AppDelegate: ExpoAppDelegate {
@available(iOS 13.0, *) @available(iOS 13.0, *)
private func executeBasicBackgroundMaintenance(task: BGTask, identifier: String) { private func executeBasicBackgroundMaintenance(task: BGTask, identifier: String) {
NSLog("[AppDelegate] 执行基本后台维护任务") NSLog("[AppDelegate] ===== 执行基本后台维护任务 =====")
NSLog("[AppDelegate] 标识符: \(identifier)")
NSLog("[AppDelegate] 开始时间: \(Date().description)")
// 线 // 线
DispatchQueue.global(qos: .background).async { DispatchQueue.global(qos: .background).async {
@@ -139,9 +165,28 @@ public class AppDelegate: ExpoAppDelegate {
@available(iOS 13.0, *) @available(iOS 13.0, *)
private func scheduleNextBackgroundTask(identifier: String) { private func scheduleNextBackgroundTask(identifier: String) {
let request = BGAppRefreshTaskRequest(identifier: identifier) NSLog("[AppDelegate] ===== 调度下一次后台任务 =====")
// 15 NSLog("[AppDelegate] 标识符: \(identifier)")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
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 { do {
try BGTaskScheduler.shared.submit(request) try BGTaskScheduler.shared.submit(request)

View File

@@ -390,14 +390,27 @@ class BackgroundTaskBridge: RCTEventEmitter {
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier) BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier)
NSLog("[BackgroundTaskBridge] 已取消之前的任务请求: \(identifier)") NSLog("[BackgroundTaskBridge] 已取消之前的任务请求: \(identifier)")
// 使 BGAppRefreshTaskRequest BGProcessingTaskRequest //
// BGAppRefreshTaskRequest // processing 使 BGProcessingTaskRequestfetch 使 BGAppRefreshTaskRequest
let request = BGAppRefreshTaskRequest(identifier: identifier) 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 { do {
try BGTaskScheduler.shared.submit(request) try BGTaskScheduler.shared.submit(request)
@@ -429,7 +442,10 @@ class BackgroundTaskBridge: RCTEventEmitter {
guard let self else { return } guard let self else { return }
self.currentTask = task 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 { if self.identifier == nil {
self.identifier = task.identifier self.identifier = task.identifier
@@ -474,8 +490,10 @@ class BackgroundTaskBridge: RCTEventEmitter {
return return
} }
NSLog("[BackgroundTaskBridge] 发送后台任务执行事件到JS") NSLog("[BackgroundTaskBridge] 准备发送后台任务执行事件到JS")
NSLog("[BackgroundTaskBridge] hasListeners: \(self.hasListeners)")
self.emitTaskToJS(payload: payload) self.emitTaskToJS(payload: payload)
NSLog("[BackgroundTaskBridge] ✅ 事件已发送到JS层")
} }
} }
@@ -514,8 +532,22 @@ class BackgroundTaskBridge: RCTEventEmitter {
// //
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier) 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 { do {
try BGTaskScheduler.shared.submit(request) try BGTaskScheduler.shared.submit(request)
@@ -542,11 +574,14 @@ class BackgroundTaskBridge: RCTEventEmitter {
pendingTaskTimeoutWorkItem?.cancel() pendingTaskTimeoutWorkItem?.cancel()
NSLog("[BackgroundTaskBridge] 缓存后台任务等待JS监听器...")
NSLog("[BackgroundTaskBridge] 等待超时时间: \(Int(pendingTaskWaitTimeout))")
let timeoutItem = DispatchWorkItem { [weak self] in let timeoutItem = DispatchWorkItem { [weak self] in
guard let self else { return } guard let self else { return }
guard self.waitingForJSListeners else { return } guard self.waitingForJSListeners else { return }
NSLog("[BackgroundTaskBridge] 等待 JS 监听器超时,执行默认处理") NSLog("[BackgroundTaskBridge] ⚠️ 等待 JS 监听器超时,执行默认处理")
self.waitingForJSListeners = false self.waitingForJSListeners = false
self.pendingTaskPayload = nil self.pendingTaskPayload = nil
self.pendingTaskTimeoutWorkItem = nil self.pendingTaskTimeoutWorkItem = nil
@@ -555,6 +590,7 @@ class BackgroundTaskBridge: RCTEventEmitter {
pendingTaskTimeoutWorkItem = timeoutItem pendingTaskTimeoutWorkItem = timeoutItem
queue.asyncAfter(deadline: .now() + pendingTaskWaitTimeout, execute: timeoutItem) queue.asyncAfter(deadline: .now() + pendingTaskWaitTimeout, execute: timeoutItem)
NSLog("[BackgroundTaskBridge] 已设置超时计时器")
} }
private func emitPendingTaskIfPossible() { private func emitPendingTaskIfPossible() {
@@ -573,10 +609,12 @@ class BackgroundTaskBridge: RCTEventEmitter {
private func emitTaskToJS(payload: [String: Any]) { private func emitTaskToJS(payload: [String: Any]) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self else { return } guard let self else { return }
NSLog("[BackgroundTaskBridge] 正在发送事件到JS层...")
self.sendEvent( self.sendEvent(
withName: "BackgroundTaskBridge.execute", withName: "BackgroundTaskBridge.execute",
body: payload body: payload
) )
NSLog("[BackgroundTaskBridge] ✅ 事件发送成功")
} }
} }
} }

View File

@@ -6,6 +6,7 @@
<array> <array>
<string>com.expo.modules.backgroundtask.processing</string> <string>com.expo.modules.backgroundtask.processing</string>
<string>com.anonymous.digitalpilates.task</string> <string>com.anonymous.digitalpilates.task</string>
<string>com.anonymous.digitalpilates.processing</string>
</array> </array>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
@@ -26,7 +27,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0.24</string> <string>1.0.25</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleURLTypes</key> <key>CFBundleURLTypes</key>

View File

@@ -396,7 +396,7 @@ export class BackgroundTaskManagerV2 {
logger.info('[BackgroundTaskManagerV2] 配置后台任务...'); logger.info('[BackgroundTaskManagerV2] 配置后台任务...');
await NativeBackgroundModule.configure({ await NativeBackgroundModule.configure({
identifier: BACKGROUND_TASK_IDENTIFIER, identifier: BACKGROUND_TASK_IDENTIFIER,
taskType: 'refresh', taskType: 'processing', // 使用 processing 类型,与 Info.plist 中的配置匹配
requiresNetworkConnectivity: false, requiresNetworkConnectivity: false,
requiresExternalPower: false, requiresExternalPower: false,
defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS, defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS,
@@ -469,24 +469,43 @@ export class BackgroundTaskManagerV2 {
private async handleBackgroundExecution(): Promise<void> { private async handleBackgroundExecution(): Promise<void> {
if (this.executingPromise) { if (this.executingPromise) {
logger.info('[BackgroundTaskManagerV2] 已有后台任务在执行,忽略重复触发'); logger.warn('[BackgroundTaskManagerV2] ⚠️ 已有后台任务在执行,忽略重复触发');
return; return;
} }
const startTime = Date.now(); 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() this.executingPromise = executeBackgroundTasks()
.then(async () => { .then(async () => {
const executionTime = Date.now() - startTime; const executionTime = Date.now() - startTime;
logger.info(`[BackgroundTaskManagerV2] 后台任务执行成功,耗时: ${executionTime}ms`); logger.info(`[BackgroundTaskManagerV2] 后台任务执行成功,耗时: ${executionTime}ms`);
if (isIosBackgroundModuleAvailable) { if (isIosBackgroundModuleAvailable) {
try { try {
// 确保任务完成前有足够的时间完成所有操作
logger.info('[BackgroundTaskManagerV2] 等待1秒确保所有操作完成...');
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒确保所有操作完成
await NativeBackgroundModule.complete(true, DEFAULT_RESCHEDULE_INTERVAL_SECONDS); 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) { } catch (error) {
logger.error('[BackgroundTaskManagerV2] 标记后台任务成功完成失败', error); logger.error('[BackgroundTaskManagerV2] 标记后台任务成功完成失败', error);
// 即使标记失败,也尝试手动重新调度 // 即使标记失败,也尝试手动重新调度
await this.scheduleNextTask(); await this.scheduleNextTask();
} }
@@ -494,14 +513,14 @@ export class BackgroundTaskManagerV2 {
}) })
.catch(async (error) => { .catch(async (error) => {
const executionTime = Date.now() - startTime; const executionTime = Date.now() - startTime;
logger.error(`[BackgroundTaskManagerV2] 后台任务执行失败,耗时: ${executionTime}ms`, error); logger.error(`[BackgroundTaskManagerV2] 后台任务执行失败,耗时: ${executionTime}ms`, error);
if (isIosBackgroundModuleAvailable) { if (isIosBackgroundModuleAvailable) {
try { try {
await NativeBackgroundModule.complete(false, DEFAULT_RESCHEDULE_INTERVAL_SECONDS); await NativeBackgroundModule.complete(false, DEFAULT_RESCHEDULE_INTERVAL_SECONDS);
logger.info('[BackgroundTaskManagerV2] 已标记后台任务失败并重新调度'); logger.info('[BackgroundTaskManagerV2] 已标记后台任务失败并重新调度');
} catch (completionError) { } catch (completionError) {
logger.error('[BackgroundTaskManagerV2] 标记后台任务失败状态时出错', completionError); logger.error('[BackgroundTaskManagerV2] 标记后台任务失败状态时出错', completionError);
// 即使标记失败,也尝试手动重新调度 // 即使标记失败,也尝试手动重新调度
await this.scheduleNextTask(); await this.scheduleNextTask();
} }
@@ -509,6 +528,7 @@ export class BackgroundTaskManagerV2 {
}) })
.finally(() => { .finally(() => {
this.executingPromise = null; this.executingPromise = null;
logger.info('[BackgroundTaskManagerV2] ===== 后台任务处理完成 =====');
}); });
await this.executingPromise; await this.executingPromise;