# 分析方案
## 变更内容总结 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:
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user