feat: 集成expo-background-task和expo-task-manager,重构后台任务管理,添加健康提醒功能,优化任务执行逻辑
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { store } from '@/store';
|
||||
import { StandReminderHelpers, WaterNotificationHelpers } from '@/utils/notificationHelpers';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import BackgroundFetch from 'react-native-background-fetch';
|
||||
import * as BackgroundTask from 'expo-background-task';
|
||||
import * as TaskManager from 'expo-task-manager';
|
||||
|
||||
/**
|
||||
* 后台任务标识符
|
||||
@@ -9,11 +10,148 @@ import BackgroundFetch from 'react-native-background-fetch';
|
||||
export const BACKGROUND_TASK_IDS = {
|
||||
WATER_REMINDER: 'water-reminder-task',
|
||||
STAND_REMINDER: 'stand-reminder-task',
|
||||
HEALTH_REMINDERS: 'background-health-reminders',
|
||||
} as const;
|
||||
|
||||
// 定义后台任务
|
||||
TaskManager.defineTask(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, async () => {
|
||||
try {
|
||||
console.log('[BackgroundTask] 后台任务执行');
|
||||
await executeBackgroundTasks();
|
||||
return BackgroundTask.BackgroundTaskResult.Success;
|
||||
} catch (error) {
|
||||
console.error('[BackgroundTask] 任务执行失败:', error);
|
||||
return BackgroundTask.BackgroundTaskResult.Failed;
|
||||
}
|
||||
});
|
||||
|
||||
// 检查通知权限
|
||||
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('执行喝水提醒后台任务...');
|
||||
|
||||
// 获取当前状态
|
||||
const state = store.getState();
|
||||
const waterStats = state.water.todayStats;
|
||||
const userProfile = state.user.profile;
|
||||
|
||||
// 检查是否有喝水目标设置
|
||||
if (!waterStats || !waterStats.dailyGoal || waterStats.dailyGoal <= 0) {
|
||||
console.log('没有设置喝水目标,跳过喝水提醒');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查时间限制(避免深夜打扰)
|
||||
const currentHour = new Date().getHours();
|
||||
if (currentHour < 9 || currentHour >= 21) {
|
||||
console.log(`当前时间${currentHour}点,不在提醒时间范围内,跳过喝水提醒`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户名
|
||||
const userName = userProfile?.name || '朋友';
|
||||
|
||||
// 构造今日统计数据
|
||||
const todayWaterStats = {
|
||||
totalAmount: waterStats.totalAmount || 0,
|
||||
dailyGoal: waterStats.dailyGoal,
|
||||
completionRate: waterStats.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 executeStandReminderTask(): Promise<void> {
|
||||
try {
|
||||
console.log('执行站立提醒后台任务...');
|
||||
|
||||
// 获取当前状态
|
||||
const state = store.getState();
|
||||
const userProfile = state.user.profile;
|
||||
|
||||
// 检查时间限制(工作时间内提醒,避免深夜或清晨打扰)
|
||||
const currentHour = new Date().getHours();
|
||||
if (currentHour < 9 || currentHour >= 21) {
|
||||
console.log(`当前时间${currentHour}点,不在站立提醒时间范围内,跳过站立提醒`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户名
|
||||
const userName = userProfile?.name || '朋友';
|
||||
|
||||
// 调用站立提醒检查函数
|
||||
const notificationSent = await StandReminderHelpers.checkStandStatusAndNotify(userName);
|
||||
|
||||
if (notificationSent) {
|
||||
console.log('后台站立提醒通知已发送');
|
||||
// 记录后台任务执行时间
|
||||
await AsyncStorage.setItem('@last_background_stand_check', Date.now().toString());
|
||||
} else {
|
||||
console.log('无需发送后台站立提醒通知');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('执行站立提醒后台任务失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 后台任务执行函数
|
||||
async function executeBackgroundTasks(): Promise<void> {
|
||||
console.log('开始执行后台任务...');
|
||||
|
||||
try {
|
||||
// 检查应用权限和用户设置
|
||||
const hasPermission = await checkNotificationPermissions();
|
||||
if (!hasPermission) {
|
||||
console.log('没有通知权限,跳过后台任务');
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行喝水提醒检查任务
|
||||
await executeWaterReminderTask();
|
||||
|
||||
// 执行站立提醒检查任务
|
||||
await executeStandReminderTask();
|
||||
|
||||
console.log('后台任务执行完成');
|
||||
} catch (error) {
|
||||
console.error('执行后台任务失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台任务管理器
|
||||
* 负责配置和管理 iOS 应用的后台任务执行
|
||||
* 负责配置和管理应用的后台任务执行
|
||||
*/
|
||||
export class BackgroundTaskManager {
|
||||
private static instance: BackgroundTaskManager;
|
||||
@@ -36,194 +174,32 @@ export class BackgroundTaskManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// 配置后台获取
|
||||
const status = await BackgroundFetch.configure({
|
||||
minimumFetchInterval: 15, // 最小间隔15分钟(单位:秒)
|
||||
}, async (taskId) => {
|
||||
console.log('[BackgroundFetch] 后台任务执行:', taskId);
|
||||
await this.executeBackgroundTasks();
|
||||
// 完成任务
|
||||
BackgroundFetch.finish(taskId);
|
||||
}, (error) => {
|
||||
console.error('[BackgroundFetch] 配置失败:', error);
|
||||
// 注册后台任务
|
||||
const status = await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, {
|
||||
minimumInterval: 15, // 15分钟
|
||||
});
|
||||
|
||||
console.log('[BackgroundFetch] 配置状态:', status);
|
||||
console.log('[BackgroundTask] 配置状态:', status);
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('后台任务管理器初始化完成');
|
||||
|
||||
// 初始化完成后自动启动后台任务
|
||||
await this.start();
|
||||
console.log('后台任务已自动启动');
|
||||
|
||||
} catch (error) {
|
||||
console.error('初始化后台任务管理器失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行后台任务
|
||||
*/
|
||||
private async executeBackgroundTasks(): Promise<void> {
|
||||
console.log('开始执行后台任务...');
|
||||
|
||||
try {
|
||||
// 检查应用权限和用户设置
|
||||
const hasPermission = await this.checkNotificationPermissions();
|
||||
if (!hasPermission) {
|
||||
console.log('没有通知权限,跳过后台任务');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户名
|
||||
const state = store.getState();
|
||||
const userName = state.user.profile?.name || '朋友';
|
||||
|
||||
// 发送测试通知
|
||||
const Notifications = await import('expo-notifications');
|
||||
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: '测试通知',
|
||||
body: `你好 ${userName}!这是一条测试消息,用于验证通知功能是否正常工作。`,
|
||||
data: { type: 'test_notification', timestamp: Date.now() },
|
||||
sound: true,
|
||||
priority: Notifications.AndroidNotificationPriority.HIGH,
|
||||
},
|
||||
trigger: null, // 立即发送
|
||||
});
|
||||
|
||||
// 执行喝水提醒检查任务
|
||||
await this.executeWaterReminderTask();
|
||||
|
||||
// 执行站立提醒检查任务
|
||||
await this.executeStandReminderTask();
|
||||
|
||||
console.log('后台任务执行完成');
|
||||
} catch (error) {
|
||||
console.error('执行后台任务失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行喝水提醒后台任务
|
||||
*/
|
||||
private async executeWaterReminderTask(): Promise<void> {
|
||||
try {
|
||||
console.log('执行喝水提醒后台任务...');
|
||||
|
||||
// 获取当前状态
|
||||
const state = store.getState();
|
||||
const waterStats = state.water.todayStats;
|
||||
const userProfile = state.user.profile;
|
||||
|
||||
// 检查是否有喝水目标设置
|
||||
if (!waterStats || !waterStats.dailyGoal || waterStats.dailyGoal <= 0) {
|
||||
console.log('没有设置喝水目标,跳过喝水提醒');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查时间限制(避免深夜打扰)
|
||||
const currentHour = new Date().getHours();
|
||||
if (currentHour < 9 || currentHour >= 21) {
|
||||
console.log(`当前时间${currentHour}点,不在提醒时间范围内,跳过喝水提醒`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户名
|
||||
const userName = userProfile?.name || '朋友';
|
||||
|
||||
// 构造今日统计数据
|
||||
const todayWaterStats = {
|
||||
totalAmount: waterStats.totalAmount || 0,
|
||||
dailyGoal: waterStats.dailyGoal,
|
||||
completionRate: waterStats.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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行站立提醒后台任务
|
||||
*/
|
||||
private async executeStandReminderTask(): Promise<void> {
|
||||
try {
|
||||
console.log('执行站立提醒后台任务...');
|
||||
|
||||
// 获取当前状态
|
||||
const state = store.getState();
|
||||
const userProfile = state.user.profile;
|
||||
|
||||
// 检查时间限制(工作时间内提醒,避免深夜或清晨打扰)
|
||||
const currentHour = new Date().getHours();
|
||||
if (currentHour < 9 || currentHour >= 21) {
|
||||
console.log(`当前时间${currentHour}点,不在站立提醒时间范围内,跳过站立提醒`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户名
|
||||
const userName = userProfile?.name || '朋友';
|
||||
|
||||
// 调用站立提醒检查函数
|
||||
const notificationSent = await StandReminderHelpers.checkStandStatusAndNotify(userName);
|
||||
|
||||
if (notificationSent) {
|
||||
console.log('后台站立提醒通知已发送');
|
||||
// 记录后台任务执行时间
|
||||
await AsyncStorage.setItem('@last_background_stand_check', Date.now().toString());
|
||||
} else {
|
||||
console.log('无需发送后台站立提醒通知');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('执行站立提醒后台任务失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查通知权限
|
||||
*/
|
||||
private async 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 start(): Promise<void> {
|
||||
try {
|
||||
BackgroundFetch.scheduleTask({
|
||||
taskId: 'com.anonymous.digitalpilates.backgroundfetch',
|
||||
delay: 15 * 60 * 1000
|
||||
await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_IDS.HEALTH_REMINDERS, {
|
||||
minimumInterval: 15,
|
||||
});
|
||||
|
||||
await BackgroundFetch.start();
|
||||
console.log('后台任务已启动');
|
||||
} catch (error) {
|
||||
console.error('启动后台任务失败:', error);
|
||||
@@ -235,7 +211,7 @@ export class BackgroundTaskManager {
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
try {
|
||||
await BackgroundFetch.stop();
|
||||
await BackgroundTask.unregisterTaskAsync(BACKGROUND_TASK_IDS.HEALTH_REMINDERS);
|
||||
console.log('后台任务已停止');
|
||||
} catch (error) {
|
||||
console.error('停止后台任务失败:', error);
|
||||
@@ -245,12 +221,13 @@ export class BackgroundTaskManager {
|
||||
/**
|
||||
* 获取后台任务状态
|
||||
*/
|
||||
async getStatus(): Promise<number> {
|
||||
async getStatus(): Promise<BackgroundTask.BackgroundTaskStatus> {
|
||||
try {
|
||||
return await BackgroundFetch.status();
|
||||
const status = await BackgroundTask.getStatusAsync();
|
||||
return status || BackgroundTask.BackgroundTaskStatus.Restricted;
|
||||
} catch (error) {
|
||||
console.error('获取后台任务状态失败:', error);
|
||||
return BackgroundFetch.STATUS_DENIED;
|
||||
return BackgroundTask.BackgroundTaskStatus.Restricted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,11 +238,9 @@ export class BackgroundTaskManager {
|
||||
const status = await this.getStatus();
|
||||
|
||||
switch (status) {
|
||||
case BackgroundFetch.STATUS_AVAILABLE:
|
||||
case BackgroundTask.BackgroundTaskStatus.Available:
|
||||
return '可用';
|
||||
case BackgroundFetch.STATUS_DENIED:
|
||||
return '被拒绝';
|
||||
case BackgroundFetch.STATUS_RESTRICTED:
|
||||
case BackgroundTask.BackgroundTaskStatus.Restricted:
|
||||
return '受限制';
|
||||
default:
|
||||
return '未知';
|
||||
@@ -280,7 +255,7 @@ export class BackgroundTaskManager {
|
||||
|
||||
try {
|
||||
// 手动触发后台任务执行
|
||||
await this.executeBackgroundTasks();
|
||||
await executeBackgroundTasks();
|
||||
console.log('后台任务测试完成');
|
||||
} catch (error) {
|
||||
console.error('后台任务测试失败:', error);
|
||||
@@ -396,7 +371,7 @@ export class BackgroundTaskManager {
|
||||
|
||||
try {
|
||||
// 手动触发站立提醒任务执行
|
||||
await this.executeStandReminderTask();
|
||||
await executeStandReminderTask();
|
||||
console.log('站立提醒后台任务测试完成');
|
||||
} catch (error) {
|
||||
console.error('站立提醒后台任务测试失败:', error);
|
||||
|
||||
Reference in New Issue
Block a user