解决后台任务在JS监听器未就绪时丢失的问题。新增任务缓存队列,当检测到无JS监听器时将任务暂存,并启动20秒超时计时器等待JS初始化完成。JS层通过markJSReady接口通知原生层准备就绪,触发缓存任务的立即执行。超时后自动切换到默认处理逻辑,确保任务不丢失。
685 lines
23 KiB
TypeScript
685 lines
23 KiB
TypeScript
import { NativeEventEmitter, NativeModules, Platform, type EmitterSubscription } from 'react-native';
|
||
|
||
import { listChallenges } from '@/services/challengesApi';
|
||
import { resyncFastingNotifications } from '@/services/fastingNotifications';
|
||
import { store } from '@/store';
|
||
import { selectActiveFastingPlan, selectActiveFastingSchedule } from '@/store/fastingSlice';
|
||
import { getWaterIntakeFromHealthKit } from '@/utils/health';
|
||
import AsyncStorage from '@/utils/kvStore';
|
||
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 * 15; // 15 minutes
|
||
const BACKGROUND_EVENT = 'BackgroundTaskBridge.execute';
|
||
const EXPIRATION_EVENT = 'BackgroundTaskBridge.expire';
|
||
|
||
const NativeBackgroundModule = NativeModules.BackgroundTaskBridge;
|
||
|
||
|
||
const isIosBackgroundModuleAvailable = Platform.OS === 'ios' && NativeBackgroundModule;
|
||
|
||
// 检查通知权限
|
||
async function checkNotificationPermissions(): Promise<boolean> {
|
||
try {
|
||
const Notifications = await import('expo-notifications');
|
||
const { status } = await Notifications.getPermissionsAsync();
|
||
return status === 'granted';
|
||
} catch (error) {
|
||
console.error('检查通知权限失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// 执行喝水提醒后台任务
|
||
async function executeWaterReminderTask(): Promise<void> {
|
||
try {
|
||
console.log('执行喝水提醒后台任务...');
|
||
|
||
let state;
|
||
try {
|
||
state = store.getState();
|
||
} catch (error) {
|
||
console.log('无法获取 Redux state,使用本地存储:', error);
|
||
const dailyGoal = await getWaterGoalFromStorage();
|
||
if (!dailyGoal || dailyGoal <= 0) {
|
||
console.log('没有设置喝水目标,跳过喝水提醒');
|
||
return;
|
||
}
|
||
await sendSimpleWaterReminder();
|
||
return;
|
||
}
|
||
|
||
const waterStats = state.water?.todayStats;
|
||
const userProfile = state.user?.profile;
|
||
|
||
let dailyGoal = waterStats?.dailyGoal ?? 0;
|
||
if (!dailyGoal || dailyGoal <= 0) {
|
||
dailyGoal = await getWaterGoalFromStorage();
|
||
}
|
||
|
||
if (!dailyGoal || dailyGoal <= 0) {
|
||
console.log('没有设置喝水目标,跳过喝水提醒');
|
||
return;
|
||
}
|
||
|
||
const currentHour = new Date().getHours();
|
||
const userName = userProfile?.name || '朋友';
|
||
|
||
const todayRange = {
|
||
startDate: dayjs().startOf('day').toDate().toISOString(),
|
||
endDate: dayjs().endOf('day').toDate().toISOString()
|
||
};
|
||
|
||
let totalAmount = waterStats?.totalAmount ?? 0;
|
||
let completionRate = waterStats?.completionRate ?? (dailyGoal > 0 ? (totalAmount / dailyGoal) * 100 : 0);
|
||
|
||
try {
|
||
const healthKitRecords = await getWaterIntakeFromHealthKit(todayRange);
|
||
if (Array.isArray(healthKitRecords) && healthKitRecords.length > 0) {
|
||
totalAmount = healthKitRecords.reduce((sum: number, record: unknown) => {
|
||
if (record && typeof record === 'object' && 'value' in record) {
|
||
const { value } = record as { value?: number | string };
|
||
const numericValue = Number(value ?? 0);
|
||
return Number.isFinite(numericValue) ? sum + numericValue : sum;
|
||
}
|
||
return sum;
|
||
}, 0);
|
||
completionRate = Math.min((totalAmount / dailyGoal) * 100, 100);
|
||
} else {
|
||
console.log('HealthKit 未返回今日饮水记录,使用应用内缓存数据');
|
||
}
|
||
} catch (healthKitError) {
|
||
console.error('从HealthKit获取饮水记录失败,使用应用内缓存数据:', healthKitError);
|
||
}
|
||
|
||
const todayWaterStats = {
|
||
totalAmount,
|
||
dailyGoal,
|
||
completionRate: Number.isFinite(completionRate) ? completionRate : 0
|
||
};
|
||
|
||
const notificationSent = await WaterNotificationHelpers.checkWaterGoalAndNotify(
|
||
userName,
|
||
todayWaterStats,
|
||
currentHour
|
||
);
|
||
|
||
if (notificationSent) {
|
||
console.log('后台喝水提醒通知已发送');
|
||
await AsyncStorage.setItem('@last_background_water_check', Date.now().toString());
|
||
} else {
|
||
console.log('无需发送后台喝水提醒通知');
|
||
}
|
||
} catch (error) {
|
||
console.error('执行喝水提醒后台任务失败:', error);
|
||
}
|
||
}
|
||
|
||
async function executeChallengeReminderTask(): Promise<void> {
|
||
try {
|
||
console.log('执行挑战鼓励提醒后台任务...');
|
||
|
||
let userName = '朋友';
|
||
try {
|
||
const state = store.getState();
|
||
const normalizedUserName = state.user?.profile?.name?.trim();
|
||
userName = normalizedUserName && normalizedUserName.length > 0 ? normalizedUserName : '朋友';
|
||
} catch (error) {
|
||
console.log('无法获取用户名,使用默认值:', error);
|
||
}
|
||
|
||
const challenges = await listChallenges();
|
||
const joinedChallenges = challenges.filter((challenge) => challenge.isJoined && challenge.progress);
|
||
|
||
if (!joinedChallenges.length) {
|
||
console.log('没有加入的挑战或挑战没有进度,跳过挑战提醒');
|
||
return;
|
||
}
|
||
|
||
const todayKey = new Date().toISOString().slice(0, 10);
|
||
|
||
const eligibleChallenges = [];
|
||
for (const challenge of joinedChallenges) {
|
||
const progress = challenge.progress;
|
||
if (!progress || progress.checkedInToday) {
|
||
continue;
|
||
}
|
||
|
||
const storageKey = `@challenge_encouragement_sent:${challenge.id}`;
|
||
const lastSent = await AsyncStorage.getItem(storageKey);
|
||
if (lastSent === todayKey) {
|
||
continue;
|
||
}
|
||
|
||
eligibleChallenges.push(challenge);
|
||
}
|
||
|
||
if (eligibleChallenges.length > 0) {
|
||
const randomIndex = Math.floor(Math.random() * eligibleChallenges.length);
|
||
const selectedChallenge = eligibleChallenges[randomIndex];
|
||
|
||
try {
|
||
await ChallengeNotificationHelpers.sendEncouragementNotification({
|
||
userName,
|
||
challengeTitle: selectedChallenge.title,
|
||
challengeId: selectedChallenge.id,
|
||
});
|
||
|
||
const storageKey = `@challenge_encouragement_sent:${selectedChallenge.id}`;
|
||
await AsyncStorage.setItem(storageKey, todayKey);
|
||
|
||
console.log(`已随机选择并发送挑战鼓励通知: ${selectedChallenge.title}`);
|
||
} catch (notificationError) {
|
||
console.error('发送挑战鼓励通知失败:', notificationError);
|
||
}
|
||
} else {
|
||
console.log('没有符合条件的挑战需要发送鼓励通知');
|
||
}
|
||
|
||
console.log('挑战鼓励提醒后台任务完成');
|
||
} catch (error) {
|
||
console.error('执行挑战鼓励提醒后台任务失败:', error);
|
||
}
|
||
}
|
||
|
||
// 执行断食通知后台任务
|
||
async function executeFastingNotificationTask(): Promise<void> {
|
||
try {
|
||
console.log('执行断食通知后台任务...');
|
||
|
||
let state;
|
||
try {
|
||
state = store.getState();
|
||
} catch (error) {
|
||
console.log('无法获取 Redux state,跳过断食通知任务:', error);
|
||
return;
|
||
}
|
||
|
||
const activeSchedule = selectActiveFastingSchedule(state);
|
||
const activePlan = selectActiveFastingPlan(state);
|
||
|
||
if (!activeSchedule || !activePlan) {
|
||
console.log('没有激活的断食计划,跳过断食通知任务');
|
||
return;
|
||
}
|
||
|
||
const end = dayjs(activeSchedule.endISO);
|
||
if (end.isBefore(dayjs())) {
|
||
console.log('断食计划已结束,跳过断食通知任务');
|
||
return;
|
||
}
|
||
|
||
console.log('正在同步断食通知...', {
|
||
planId: activePlan.id,
|
||
start: activeSchedule.startISO,
|
||
end: activeSchedule.endISO,
|
||
});
|
||
|
||
await resyncFastingNotifications({
|
||
schedule: activeSchedule,
|
||
plan: activePlan,
|
||
enabled: true,
|
||
});
|
||
|
||
console.log('断食通知后台同步完成');
|
||
} catch (error) {
|
||
console.error('执行断食通知后台任务失败:', error);
|
||
}
|
||
}
|
||
|
||
// 发送测试通知以验证后台任务执行
|
||
async function sendTestNotification(): Promise<void> {
|
||
try {
|
||
console.log('发送后台任务测试通知...');
|
||
|
||
const Notifications = await import('expo-notifications');
|
||
|
||
await Notifications.scheduleNotificationAsync({
|
||
content: {
|
||
title: '后台任务测试',
|
||
body: `后台任务正在执行中... 时间: ${new Date().toLocaleTimeString()}`,
|
||
data: {
|
||
type: 'background_task_test',
|
||
timestamp: Date.now()
|
||
}
|
||
},
|
||
trigger: null,
|
||
});
|
||
|
||
console.log('后台任务测试通知发送成功');
|
||
await AsyncStorage.setItem('@last_background_test_notification', Date.now().toString());
|
||
} catch (error) {
|
||
console.error('发送测试通知失败:', error);
|
||
}
|
||
}
|
||
|
||
async function sendSimpleWaterReminder(): Promise<void> {
|
||
try {
|
||
const userName = '朋友';
|
||
const Notifications = await import('expo-notifications');
|
||
|
||
const notificationId = await Notifications.scheduleNotificationAsync({
|
||
content: {
|
||
title: '💧 该喝水啦!',
|
||
body: `${userName},记得补充水分,保持身体健康~`,
|
||
data: {
|
||
type: 'water_reminder',
|
||
url: '/statistics'
|
||
},
|
||
sound: 'default',
|
||
},
|
||
trigger: null,
|
||
});
|
||
|
||
console.log('简单喝水提醒已发送,ID:', notificationId);
|
||
} catch (error) {
|
||
console.error('发送简单喝水提醒失败:', error);
|
||
}
|
||
}
|
||
|
||
async function executeBackgroundTasks(): Promise<void> {
|
||
console.log('开始执行后台任务...');
|
||
|
||
try {
|
||
const hasPermission = await checkNotificationPermissions();
|
||
if (!hasPermission) {
|
||
console.log('没有通知权限,跳过后台任务');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const state = store.getState();
|
||
if (!state) {
|
||
console.log('Redux store 未初始化,跳过后台任务');
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
console.log('无法访问 Redux store,跳过后台任务:', error);
|
||
return;
|
||
}
|
||
|
||
const testNotificationsEnabled = await AsyncStorage.getItem('@background_test_notifications_enabled') === 'true';
|
||
if (testNotificationsEnabled) {
|
||
await sendTestNotification();
|
||
}
|
||
|
||
await executeWaterReminderTask();
|
||
await executeChallengeReminderTask();
|
||
await executeFastingNotificationTask();
|
||
|
||
console.log('后台任务执行完成');
|
||
} catch (error) {
|
||
console.error('执行后台任务失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
export class BackgroundTaskManagerV2 {
|
||
private static instance: BackgroundTaskManagerV2;
|
||
|
||
private isInitialized = false;
|
||
private executingPromise: Promise<void> | null = null;
|
||
private eventSubscription?: EmitterSubscription;
|
||
private expirationSubscription?: EmitterSubscription;
|
||
|
||
static getInstance(): BackgroundTaskManagerV2 {
|
||
if (!BackgroundTaskManagerV2.instance) {
|
||
BackgroundTaskManagerV2.instance = new BackgroundTaskManagerV2();
|
||
}
|
||
return BackgroundTaskManagerV2.instance;
|
||
}
|
||
|
||
async initialize(): Promise<void> {
|
||
if (this.isInitialized) {
|
||
logger.info('[BackgroundTaskManagerV2] 后台任务管理器已初始化,跳过重复初始化');
|
||
return;
|
||
}
|
||
|
||
logger.info('[BackgroundTaskManagerV2] ====== 开始初始化后台任务管理器 ======');
|
||
|
||
if (!isIosBackgroundModuleAvailable) {
|
||
logger.warn('[BackgroundTaskManagerV2] iOS 原生后台模块不可用,跳过初始化');
|
||
logger.warn('[BackgroundTaskManagerV2] Platform:', Platform.OS);
|
||
this.isInitialized = false;
|
||
return;
|
||
}
|
||
|
||
logger.info('[BackgroundTaskManagerV2] 原生模块可用,开始注册事件监听器');
|
||
|
||
const emitter = new NativeEventEmitter(NativeBackgroundModule);
|
||
|
||
this.eventSubscription = emitter.addListener(BACKGROUND_EVENT, (payload) => {
|
||
logger.info('[BackgroundTaskManagerV2] ✅ 收到后台任务执行事件', payload);
|
||
this.handleBackgroundExecution();
|
||
});
|
||
|
||
this.expirationSubscription = emitter.addListener(EXPIRATION_EVENT, (payload) => {
|
||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 后台任务即将过期', payload);
|
||
// 处理任务过期情况,确保重新调度
|
||
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] 事件监听器注册完成');
|
||
|
||
try {
|
||
// 检查后台刷新状态
|
||
logger.info('[BackgroundTaskManagerV2] 检查后台刷新权限状态...');
|
||
const status = await this.getStatus();
|
||
logger.info('[BackgroundTaskManagerV2] 后台刷新状态:', status);
|
||
|
||
if (status === 'denied') {
|
||
logger.error('[BackgroundTaskManagerV2] ❌ 后台刷新被拒绝!');
|
||
logger.error('[BackgroundTaskManagerV2] 请在 设置 > Out Live > 后台App刷新 中启用');
|
||
this.isInitialized = false;
|
||
return;
|
||
}
|
||
|
||
if (status === 'restricted') {
|
||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 后台刷新被限制(可能是家长控制)');
|
||
this.isInitialized = false;
|
||
return;
|
||
}
|
||
|
||
logger.info('[BackgroundTaskManagerV2] 配置后台任务...');
|
||
await NativeBackgroundModule.configure({
|
||
identifier: BACKGROUND_TASK_IDENTIFIER,
|
||
taskType: 'refresh',
|
||
requiresNetworkConnectivity: false,
|
||
requiresExternalPower: false,
|
||
defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS,
|
||
});
|
||
|
||
this.isInitialized = true;
|
||
logger.info('[BackgroundTaskManagerV2] ✅ 后台任务配置成功');
|
||
|
||
// 立即调度一次后台任务
|
||
logger.info('[BackgroundTaskManagerV2] 调度首次后台任务...');
|
||
await this.scheduleNextTask();
|
||
|
||
// 检查待处理的任务请求
|
||
const pendingRequests = await this.getPendingRequests();
|
||
logger.info('[BackgroundTaskManagerV2] 当前待处理的任务请求数量:', pendingRequests.length);
|
||
|
||
if (pendingRequests.length > 0) {
|
||
logger.info('[BackgroundTaskManagerV2] 待处理任务详情:', JSON.stringify(pendingRequests, null, 2));
|
||
}
|
||
|
||
logger.info('[BackgroundTaskManagerV2] ====== 初始化完成 ======');
|
||
|
||
} catch (error: any) {
|
||
// BGTaskSchedulerErrorDomain 错误码 1 表示后台任务功能不可用
|
||
// 这在模拟器上是正常的,因为模拟器不完全支持后台任务
|
||
const errorMessage = error?.message || String(error);
|
||
const isBGTaskUnavailable = errorMessage.includes('BGTaskSchedulerErrorDomain') &&
|
||
(errorMessage.includes('错误1') || errorMessage.includes('code 1'));
|
||
|
||
if (isBGTaskUnavailable) {
|
||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 后台任务功能在当前环境不可用');
|
||
logger.warn('[BackgroundTaskManagerV2] 这是模拟器的正常限制,在真机上会正常工作');
|
||
logger.warn('[BackgroundTaskManagerV2] 建议:在真机上测试后台任务功能');
|
||
this.removeListeners();
|
||
this.isInitialized = false;
|
||
// 不抛出错误,因为这是预期行为
|
||
return;
|
||
}
|
||
|
||
// 其他错误情况,尝试恢复
|
||
logger.error('[BackgroundTaskManagerV2] ❌ 初始化失败', error);
|
||
logger.error('[BackgroundTaskManagerV2] 错误详情:', errorMessage);
|
||
|
||
try {
|
||
logger.info('[BackgroundTaskManagerV2] 尝试恢复...');
|
||
await this.attemptRecovery();
|
||
logger.info('[BackgroundTaskManagerV2] ✅ 恢复成功');
|
||
} 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) {
|
||
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) {
|
||
logger.error('[BackgroundTaskManagerV2] 标记后台任务成功完成失败', error);
|
||
// 即使标记失败,也尝试手动重新调度
|
||
await this.scheduleNextTask();
|
||
}
|
||
}
|
||
})
|
||
.catch(async (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) {
|
||
logger.error('[BackgroundTaskManagerV2] 标记后台任务失败状态时出错', completionError);
|
||
// 即使标记失败,也尝试手动重新调度
|
||
await this.scheduleNextTask();
|
||
}
|
||
}
|
||
})
|
||
.finally(() => {
|
||
this.executingPromise = null;
|
||
});
|
||
|
||
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;
|
||
}
|
||
|
||
try {
|
||
await NativeBackgroundModule.cancelAll();
|
||
} catch (error) {
|
||
logger.error('[BackgroundTaskManagerV2] 停止后台任务失败', error);
|
||
} finally {
|
||
this.removeListeners();
|
||
this.isInitialized = false;
|
||
}
|
||
}
|
||
|
||
private removeListeners(): void {
|
||
this.eventSubscription?.remove();
|
||
this.expirationSubscription?.remove();
|
||
this.eventSubscription = undefined;
|
||
this.expirationSubscription = undefined;
|
||
}
|
||
|
||
async getStatus(): Promise<string> {
|
||
if (!isIosBackgroundModuleAvailable) {
|
||
return Platform.OS;
|
||
}
|
||
|
||
try {
|
||
const status = await NativeBackgroundModule.backgroundRefreshStatus();
|
||
return status;
|
||
} catch (error) {
|
||
logger.error('[BackgroundTaskManagerV2] 获取后台任务状态失败', error);
|
||
return 'unknown';
|
||
}
|
||
}
|
||
|
||
async checkStatus(): Promise<string> {
|
||
const status = await this.getStatus();
|
||
switch (status) {
|
||
case 'available':
|
||
return '可用';
|
||
case 'restricted':
|
||
return '受限制';
|
||
case 'denied':
|
||
return '被拒绝';
|
||
default:
|
||
return '未知';
|
||
}
|
||
}
|
||
|
||
async triggerTaskForTesting(): Promise<void> {
|
||
if (!isIosBackgroundModuleAvailable) {
|
||
logger.info('[BackgroundTaskManagerV2] 原生模块不可用,直接执行后台任务逻辑');
|
||
await executeBackgroundTasks();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
logger.info('[BackgroundTaskManagerV2] 尝试模拟触发后台任务...');
|
||
await NativeBackgroundModule.simulateLaunch();
|
||
logger.info('[BackgroundTaskManagerV2] ✅ 模拟触发成功');
|
||
} catch (error: any) {
|
||
const errorMessage = error?.message || String(error);
|
||
const errorCode = error?.code || '';
|
||
|
||
// 检查是否是模拟器不支持的错误
|
||
if (errorCode === 'SIMULATOR_NOT_SUPPORTED') {
|
||
logger.warn('[BackgroundTaskManagerV2] ⚠️ 模拟器不支持后台任务');
|
||
logger.warn('[BackgroundTaskManagerV2] 这是正常的限制,请在真机上测试');
|
||
logger.info('[BackgroundTaskManagerV2] 作为替代,直接执行后台任务逻辑...');
|
||
// 在模拟器上直接执行后台任务逻辑作为测试
|
||
await executeBackgroundTasks();
|
||
return;
|
||
}
|
||
|
||
// 检查是否是监听器未注册的错误
|
||
if (errorCode === 'NO_LISTENERS') {
|
||
logger.warn('[BackgroundTaskManagerV2] ⚠️ JS 监听器未注册');
|
||
logger.warn('[BackgroundTaskManagerV2] 可能是应用还未完全初始化');
|
||
logger.info('[BackgroundTaskManagerV2] 尝试直接执行后台任务逻辑...');
|
||
await executeBackgroundTasks();
|
||
return;
|
||
}
|
||
|
||
logger.error('[BackgroundTaskManagerV2] ❌ 模拟后台任务触发失败', error);
|
||
logger.error('[BackgroundTaskManagerV2] 错误代码:', errorCode);
|
||
logger.error('[BackgroundTaskManagerV2] 错误信息:', errorMessage);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async testBackgroundTask(): Promise<void> {
|
||
await this.triggerTaskForTesting();
|
||
}
|
||
|
||
async getLastBackgroundCheckTime(): Promise<number | null> {
|
||
try {
|
||
const lastCheck = await AsyncStorage.getItem('@last_background_water_check');
|
||
return lastCheck ? parseInt(lastCheck, 10) : null;
|
||
} catch (error) {
|
||
console.error('获取最后后台检查时间失败:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async getPendingRequests(): Promise<any[]> {
|
||
if (!isIosBackgroundModuleAvailable) {
|
||
return [];
|
||
}
|
||
|
||
try {
|
||
const requests = await NativeBackgroundModule.getPendingRequests();
|
||
return Array.isArray(requests) ? requests : [];
|
||
} catch (error) {
|
||
logger.error('[BackgroundTaskManagerV2] 获取待处理的后台任务请求失败', error);
|
||
return [];
|
||
}
|
||
}
|
||
}
|
||
|
||
export type BackgroundTaskEvent = {
|
||
taskId: string;
|
||
timestamp: number;
|
||
success: boolean;
|
||
error?: string;
|
||
};
|
||
|
||
export const BackgroundTaskManager = BackgroundTaskManagerV2;
|