# 分析方案

## 变更内容总结
1. **iOS后台任务系统重构** - 修复后台任务无法自动运行的问题
2. **日志系统优化** - 改进日志记录机制,添加队列和批量写入
3. **文档新增** - 添加后台任务修复总结和测试指南文档
4. **应用启动优化** - 添加后台任务状态检查和恢复逻辑
5. **版本号更新** - Info.plist版本从1.0.23升级到1.0.24

## 提交信息类型判断
- **主要类型**: `fix` - 这是一个重要的bug修复,解决了iOS后台任务无法自动运行的核心问题
- **作用域**: `ios-background` - 专注于iOS后台任务功能
- **影响**: 这个修复对iOS用户的后台功能至关重要

## 提交信息

fix(ios-background): 修复iOS后台任务无法自动运行的问题

主要修复内容:
- 修复BackgroundTaskBridge任务调度逻辑,改用BGAppRefreshTaskRequest
- 添加任务完成后自动重新调度机制,确保任务持续执行
- 优化应用生命周期管理,移除重复的后台任务调度
- 在应用启动时添加后台任务状态检查和恢复功能
- 将默认任务间隔从30分钟优化为15分钟

次要改进:
- 重构日志系统,添加内存队列和批量写入机制,提升性能
- 添加写入锁和重试机制,防止日志数据丢失
- 新增详细的修复总结文档和测试指南

技术细节:
- 使用BGAppRefreshTaskRequest替代BGProcessingTaskRequest
- 实现任务过期自动重新调度
- 添加任务执行状态监控和恢复逻辑
- 优化错误处理和日志输出

影响范围: iOS后台任务调度、通知推送、应用状态管理
This commit is contained in:
richarjiang
2025-11-04 19:14:53 +08:00
parent f80a1bae78
commit d74046498d
8 changed files with 1014 additions and 102 deletions

View File

@@ -6,14 +6,14 @@ import { store } from '@/store';
import { selectActiveFastingPlan, selectActiveFastingSchedule } from '@/store/fastingSlice';
import { getWaterIntakeFromHealthKit } from '@/utils/health';
import AsyncStorage from '@/utils/kvStore';
import { log } from '@/utils/logger';
import { logger } from '@/utils/logger';
import { ChallengeNotificationHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
import { getWaterGoalFromStorage } from '@/utils/userPreferences';
import dayjs from 'dayjs';
export const BACKGROUND_TASK_IDENTIFIER = 'com.anonymous.digitalpilates.task';
const DEFAULT_RESCHEDULE_INTERVAL_SECONDS = 60 * 30; // 30 minutes
const DEFAULT_RESCHEDULE_INTERVAL_SECONDS = 60 * 15; // 15 minutes
const BACKGROUND_EVENT = 'BackgroundTaskBridge.execute';
const EXPIRATION_EVENT = 'BackgroundTaskBridge.expire';
@@ -335,11 +335,12 @@ export class BackgroundTaskManagerV2 {
async initialize(): Promise<void> {
if (this.isInitialized) {
logger.info('[BackgroundTaskManagerV2] 后台任务管理器已初始化,跳过重复初始化');
return;
}
if (!isIosBackgroundModuleAvailable) {
log.warn('[BackgroundTaskManagerV2] iOS 原生后台模块不可用,跳过初始化');
logger.warn('[BackgroundTaskManagerV2] iOS 原生后台模块不可用,跳过初始化');
this.isInitialized = false;
return;
}
@@ -347,68 +348,125 @@ export class BackgroundTaskManagerV2 {
const emitter = new NativeEventEmitter(NativeBackgroundModule);
this.eventSubscription = emitter.addListener(BACKGROUND_EVENT, (payload) => {
log.info('[BackgroundTaskManagerV2] 收到后台任务事件', payload);
logger.info('[BackgroundTaskManagerV2] 收到后台任务事件', payload);
this.handleBackgroundExecution();
});
this.expirationSubscription = emitter.addListener(EXPIRATION_EVENT, (payload) => {
log.warn('[BackgroundTaskManagerV2] 后台任务在完成前即将过期', payload);
logger.warn('[BackgroundTaskManagerV2] 后台任务在完成前即将过期', payload);
// 处理任务过期情况,确保重新调度
this.handleTaskExpiration();
});
try {
// 检查后台刷新状态
const status = await this.getStatus();
logger.info('[BackgroundTaskManagerV2] 后台刷新状态:', status);
if (status === 'denied' || status === 'restricted') {
logger.warn('[BackgroundTaskManagerV2] 后台刷新被限制或拒绝,后台任务可能无法正常工作');
// 不抛出错误,但标记为未完全初始化
this.isInitialized = false;
return;
}
await NativeBackgroundModule.configure({
identifier: BACKGROUND_TASK_IDENTIFIER,
taskType: 'processing',
taskType: 'refresh',
requiresNetworkConnectivity: false,
requiresExternalPower: false,
defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS,
});
this.isInitialized = true;
log.info('[BackgroundTaskManagerV2] 已初始化并注册 iOS 后台任务');
logger.info('[BackgroundTaskManagerV2] 已初始化并注册 iOS 后台任务');
// 立即调度一次后台任务
await this.scheduleNextTask();
// 检查待处理的任务请求
const pendingRequests = await this.getPendingRequests();
logger.info('[BackgroundTaskManagerV2] 当前待处理的任务请求数量:', pendingRequests.length);
} catch (error: any) {
// BGTaskSchedulerErrorDomain 错误码 1 表示后台任务功能不可用
// 这在模拟器上是正常的,因为模拟器不完全支持后台任务
const errorMessage = error?.message || String(error);
const isBGTaskUnavailable = errorMessage.includes('BGTaskSchedulerErrorDomain') &&
errorMessage.includes('错误1');
(errorMessage.includes('错误1') || errorMessage.includes('code 1'));
if (isBGTaskUnavailable) {
log.warn('[BackgroundTaskManagerV2] 后台任务功能在当前环境不可用(模拟器限制),将在真机上正常工作');
logger.warn('[BackgroundTaskManagerV2] 后台任务功能在当前环境不可用(模拟器限制),将在真机上正常工作');
this.removeListeners();
this.isInitialized = false;
// 不抛出错误,因为这是预期行为
return;
}
log.error('[BackgroundTaskManagerV2] 初始化失败', error);
this.removeListeners();
throw error;
// 其他错误情况,尝试恢复
logger.error('[BackgroundTaskManagerV2] 初始化失败,尝试恢复', error);
try {
// 尝试重新初始化一次
await this.attemptRecovery();
} catch (recoveryError) {
logger.error('[BackgroundTaskManagerV2] 恢复失败,放弃初始化', recoveryError);
this.removeListeners();
throw error;
}
}
}
async scheduleNextTask(): Promise<void> {
if (!isIosBackgroundModuleAvailable) {
return;
}
try {
await NativeBackgroundModule.schedule({
delay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS
});
logger.info('[BackgroundTaskManagerV2] 已调度下一次后台任务');
} catch (error) {
logger.error('[BackgroundTaskManagerV2] 调度后台任务失败', error);
}
}
private async handleBackgroundExecution(): Promise<void> {
if (this.executingPromise) {
log.info('[BackgroundTaskManagerV2] 已有后台任务在执行,忽略重复触发');
logger.info('[BackgroundTaskManagerV2] 已有后台任务在执行,忽略重复触发');
return;
}
const startTime = Date.now();
logger.info('[BackgroundTaskManagerV2] 开始执行后台任务');
this.executingPromise = executeBackgroundTasks()
.then(async () => {
const executionTime = Date.now() - startTime;
logger.info(`[BackgroundTaskManagerV2] 后台任务执行成功,耗时: ${executionTime}ms`);
if (isIosBackgroundModuleAvailable) {
try {
await NativeBackgroundModule.complete(true, DEFAULT_RESCHEDULE_INTERVAL_SECONDS);
logger.info('[BackgroundTaskManagerV2] 已标记后台任务成功完成并重新调度');
} catch (error) {
log.error('[BackgroundTaskManagerV2] 标记后台任务成功完成失败', error);
logger.error('[BackgroundTaskManagerV2] 标记后台任务成功完成失败', error);
// 即使标记失败,也尝试手动重新调度
await this.scheduleNextTask();
}
}
})
.catch(async (error) => {
log.error('[BackgroundTaskManagerV2] 后台任务执行失败', error);
const executionTime = Date.now() - startTime;
logger.error(`[BackgroundTaskManagerV2] 后台任务执行失败,耗时: ${executionTime}ms`, error);
if (isIosBackgroundModuleAvailable) {
try {
await NativeBackgroundModule.complete(false, DEFAULT_RESCHEDULE_INTERVAL_SECONDS);
logger.info('[BackgroundTaskManagerV2] 已标记后台任务失败并重新调度');
} catch (completionError) {
log.error('[BackgroundTaskManagerV2] 标记后台任务失败状态时出错', completionError);
logger.error('[BackgroundTaskManagerV2] 标记后台任务失败状态时出错', completionError);
// 即使标记失败,也尝试手动重新调度
await this.scheduleNextTask();
}
}
})
@@ -419,6 +477,46 @@ export class BackgroundTaskManagerV2 {
await this.executingPromise;
}
private async handleTaskExpiration(): Promise<void> {
logger.warn('[BackgroundTaskManagerV2] 处理后台任务过期');
// 任务过期时,确保重新调度下一次任务
try {
await this.scheduleNextTask();
logger.info('[BackgroundTaskManagerV2] 已为过期的任务重新调度');
} catch (error) {
logger.error('[BackgroundTaskManagerV2] 为过期任务重新调度失败', error);
}
}
private async attemptRecovery(): Promise<void> {
logger.info('[BackgroundTaskManagerV2] 尝试恢复后台任务功能');
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 2000));
// 取消所有现有任务
if (isIosBackgroundModuleAvailable) {
try {
await NativeBackgroundModule.cancelAll();
logger.info('[BackgroundTaskManagerV2] 已取消所有现有后台任务');
} catch (error) {
logger.warn('[BackgroundTaskManagerV2] 取消现有任务失败', error);
}
}
// 重新配置
await NativeBackgroundModule.configure({
identifier: BACKGROUND_TASK_IDENTIFIER,
taskType: 'refresh',
requiresNetworkConnectivity: false,
requiresExternalPower: false,
defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS,
});
logger.info('[BackgroundTaskManagerV2] 后台任务功能恢复成功');
}
async stop(): Promise<void> {
if (!isIosBackgroundModuleAvailable) {
return;
@@ -427,7 +525,7 @@ export class BackgroundTaskManagerV2 {
try {
await NativeBackgroundModule.cancelAll();
} catch (error) {
log.error('[BackgroundTaskManagerV2] 停止后台任务失败', error);
logger.error('[BackgroundTaskManagerV2] 停止后台任务失败', error);
} finally {
this.removeListeners();
this.isInitialized = false;
@@ -450,7 +548,7 @@ export class BackgroundTaskManagerV2 {
const status = await NativeBackgroundModule.backgroundRefreshStatus();
return status;
} catch (error) {
log.error('[BackgroundTaskManagerV2] 获取后台任务状态失败', error);
logger.error('[BackgroundTaskManagerV2] 获取后台任务状态失败', error);
return 'unknown';
}
}
@@ -478,7 +576,7 @@ export class BackgroundTaskManagerV2 {
try {
await NativeBackgroundModule.simulateLaunch();
} catch (error) {
log.error('[BackgroundTaskManagerV2] 模拟后台任务触发失败', error);
logger.error('[BackgroundTaskManagerV2] 模拟后台任务触发失败', error);
throw error;
}
}
@@ -506,7 +604,7 @@ export class BackgroundTaskManagerV2 {
const requests = await NativeBackgroundModule.getPendingRequests();
return Array.isArray(requests) ? requests : [];
} catch (error) {
log.error('[BackgroundTaskManagerV2] 获取待处理的后台任务请求失败', error);
logger.error('[BackgroundTaskManagerV2] 获取待处理的后台任务请求失败', error);
return [];
}
}