diff --git a/app.json b/app.json
index 0dd45d7..71386ef 100644
--- a/app.json
+++ b/app.json
@@ -18,8 +18,7 @@
"NSPhotoLibraryUsageDescription": "应用需要访问相册以选择您的体态照片用于AI测评。",
"NSPhotoLibraryAddUsageDescription": "应用需要写入相册以保存拍摄的体态照片(可选)。",
"UIBackgroundModes": [
- "background-fetch",
- "background-processing"
+ "processing"
]
}
},
@@ -70,12 +69,7 @@
]
}
],
- [
- "expo-background-fetch",
- {
- "minimumInterval": 15
- }
- ]
+ "expo-background-task"
],
"experiments": {
"typedRoutes": true
diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx
index 3114e8a..d39eeff 100644
--- a/app/(tabs)/statistics.tsx
+++ b/app/(tabs)/statistics.tsx
@@ -15,6 +15,7 @@ import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useBackgroundTasks } from '@/hooks/useBackgroundTasks';
import { notificationService } from '@/services/notifications';
+import { backgroundTaskManager } from '@/services/backgroundTaskManager';
import { selectHealthDataByDate, setHealthData } from '@/store/healthSlice';
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
import { fetchDailyNutritionData, selectNutritionSummaryByDate } from '@/store/nutritionSlice';
@@ -37,7 +38,8 @@ import {
ScrollView,
StyleSheet,
Text,
- View
+ View,
+ TouchableOpacity
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -618,6 +620,19 @@ export default function ExploreScreen() {
海豹健康
+
+ {/* 开发环境调试按钮 */}
+ {__DEV__ && (
+ {
+ console.log('🔧 手动触发后台任务测试...');
+ await backgroundTaskManager.debugExecuteBackgroundTask();
+ }}
+ >
+ 🔧
+
+ )}
@@ -813,6 +828,25 @@ const styles = StyleSheet.create({
fontWeight: '500',
color: '#192126',
},
+ debugButton: {
+ width: 32,
+ height: 32,
+ borderRadius: 16,
+ backgroundColor: '#FF6B6B',
+ alignItems: 'center',
+ justifyContent: 'center',
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 3,
+ },
+ debugButtonText: {
+ fontSize: 12,
+ },
sectionTitle: {
diff --git a/hooks/useBackgroundTasks.ts b/hooks/useBackgroundTasks.ts
index 2ea74fd..a713cb7 100644
--- a/hooks/useBackgroundTasks.ts
+++ b/hooks/useBackgroundTasks.ts
@@ -1,15 +1,15 @@
-import { BackgroundTaskType as BackgroundTask, backgroundTaskManager, TaskStatusType as TaskStatus } from '@/services/backgroundTaskManager';
-import * as BackgroundFetch from 'expo-background-fetch';
+import { BackgroundTaskConfig, backgroundTaskManager, TaskStatus } from '@/services/backgroundTaskManager';
+import * as BackgroundTask from 'expo-background-task';
import { useCallback, useEffect, useState } from 'react';
export interface UseBackgroundTasksReturn {
// 状态
isInitialized: boolean;
taskStatuses: TaskStatus[];
- registeredTasks: BackgroundTask[];
+ registeredTasks: BackgroundTaskConfig[];
// 方法
- registerTask: (task: BackgroundTask) => Promise;
+ registerTask: (task: BackgroundTaskConfig) => Promise;
unregisterTask: (taskId: string) => Promise;
executeTask: (taskId: string, data?: any) => Promise;
executeAllTasks: () => Promise<{ [taskId: string]: 'success' | 'failed' }>;
@@ -17,15 +17,15 @@ export interface UseBackgroundTasksReturn {
cleanupTaskStatuses: () => Promise;
// 后台任务状态
- backgroundTaskStatus: BackgroundFetch.BackgroundFetchStatus | null;
+ backgroundTaskStatus: BackgroundTask.BackgroundTaskStatus | null;
getBackgroundTaskStatus: () => Promise;
}
export const useBackgroundTasks = (): UseBackgroundTasksReturn => {
const [isInitialized, setIsInitialized] = useState(false);
const [taskStatuses, setTaskStatuses] = useState([]);
- const [registeredTasks, setRegisteredTasks] = useState([]);
- const [backgroundTaskStatus, setBackgroundTaskStatus] = useState(null);
+ const [registeredTasks, setRegisteredTasks] = useState([]);
+ const [backgroundTaskStatus, setBackgroundTaskStatus] = useState(null);
// 初始化
useEffect(() => {
@@ -49,7 +49,7 @@ export const useBackgroundTasks = (): UseBackgroundTasksReturn => {
}, []);
// 注册任务
- const registerTask = useCallback(async (task: BackgroundTask) => {
+ const registerTask = useCallback(async (task: BackgroundTaskConfig) => {
await backgroundTaskManager.registerTask(task);
refreshData();
}, [refreshData]);
diff --git a/services/backgroundTaskManager.ts b/services/backgroundTaskManager.ts
index 8eed91d..bb11fd3 100644
--- a/services/backgroundTaskManager.ts
+++ b/services/backgroundTaskManager.ts
@@ -1,16 +1,38 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
-import * as BackgroundFetch from 'expo-background-fetch';
+import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
+// 后台任务名称常量
+const BACKGROUND_TASK_NAME = 'health-background-task';
+
+// 必须在全局作用域定义任务处理器
+TaskManager.defineTask(BACKGROUND_TASK_NAME, async () => {
+ console.log('======= 后台任务被系统调用 =======');
+ const now = new Date();
+ console.log(`后台任务执行时间: ${now.toISOString()}`);
+
+ try {
+ // 获取后台任务管理器实例并执行所有注册的任务
+ const manager = BackgroundTaskManager.getInstance();
+ console.log('开始执行所有注册的任务...');
+ const results = await manager.executeAllTasks();
+ console.log('后台任务执行结果:', results);
+
+ // 返回成功状态
+ return BackgroundTask.BackgroundTaskResult.Success;
+ } catch (error) {
+ console.error('后台任务执行失败:', error);
+ return BackgroundTask.BackgroundTaskResult.Failed;
+ }
+});
+
// 任务类型定义
-export interface BackgroundTask {
+export interface BackgroundTaskConfig {
id: string;
name: string;
handler: (data?: any) => Promise;
options?: {
minimumInterval?: number; // 最小间隔时间(分钟)
- stopOnTerminate?: boolean; // 应用终止时是否停止
- startOnBoot?: boolean; // 设备重启时是否启动
};
}
@@ -27,9 +49,10 @@ export interface TaskStatus {
// 后台任务管理器类
class BackgroundTaskManager {
private static instance: BackgroundTaskManager;
- private tasks: Map = new Map();
+ private tasks: Map = new Map();
private taskStatuses: Map = new Map();
private isInitialized = false;
+ private systemTaskRegistered = false;
// 单例模式
public static getInstance(): BackgroundTaskManager {
@@ -49,11 +72,11 @@ class BackgroundTaskManager {
this.isInitialized = true;
// 注册后台任务
- await this.registerBackgroundTask();
-
+ await this.registerSystemBackgroundTask();
+
// 加载已保存的任务状态
await this.loadTaskStatuses();
-
+
console.log('后台任务管理器初始化成功');
} catch (error) {
console.error('后台任务管理器初始化失败:', error);
@@ -61,53 +84,49 @@ class BackgroundTaskManager {
}
}
- // 注册后台任务
- private async registerBackgroundTask(): Promise {
- const BACKGROUND_FETCH_TASK = 'background-fetch-task';
+ // 注册系统后台任务
+ private async registerSystemBackgroundTask(): Promise {
+ console.log('开始注册系统后台任务...');
- console.log('注册后台获取任务');
-
- // 定义后台获取任务
- TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
- console.log('后台获取任务被系统调用');
- const now = new Date();
-
- try {
- console.log(`开始执行后台任务 - ${now.toISOString()}`);
-
- // 执行所有注册的任务
- const results = await this.executeAllTasks();
-
- console.log('后台任务执行完成:', results);
-
- // 返回成功状态
- return BackgroundFetch.BackgroundFetchResult.NewData;
- } catch (error) {
- console.error('后台任务执行失败:', error);
- return BackgroundFetch.BackgroundFetchResult.Failed;
- }
- });
-
- // 注册后台获取任务
try {
- const status = await BackgroundFetch.getStatusAsync();
- if (status === BackgroundFetch.BackgroundFetchStatus.Available) {
- await BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
- minimumInterval: 15 * 60, // 15分钟(以秒为单位)
- stopOnTerminate: false,
- startOnBoot: true,
- });
- console.log('后台获取任务注册成功');
+ // 检查后台任务状态
+ const status = await BackgroundTask.getStatusAsync();
+ console.log('后台任务服务状态:', BackgroundTask.BackgroundTaskStatus[status]);
+
+ if (status === BackgroundTask.BackgroundTaskStatus.Available) {
+ // 检查任务是否已经注册
+ const isRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_TASK_NAME);
+ console.log('系统任务是否已注册:', isRegistered);
+
+ if (!isRegistered) {
+ // 注册后台任务
+ await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_NAME);
+ console.log('✅ 系统后台任务注册成功');
+ this.systemTaskRegistered = true;
+ } else {
+ console.log('✅ 系统后台任务已经注册,跳过重复注册');
+ this.systemTaskRegistered = true;
+ }
} else {
- console.warn('后台获取不可用,状态:', status);
+ const statusText = Object.keys(BackgroundTask.BackgroundTaskStatus).find(
+ key => BackgroundTask.BackgroundTaskStatus[key as keyof typeof BackgroundTask.BackgroundTaskStatus] === status
+ );
+ console.warn('❌ 后台任务服务不可用,状态:', statusText || status);
+ console.warn('可能的原因:');
+ console.warn('- 设备省电模式开启');
+ console.warn('- 后台应用刷新被禁用');
+ console.warn('- 设备电量过低');
+ this.systemTaskRegistered = false;
}
} catch (error) {
- console.error('注册后台获取任务失败:', error);
+ console.error('❌ 注册系统后台任务失败:', error);
+ this.systemTaskRegistered = false;
+ throw error;
}
}
// 注册自定义任务
- public async registerTask(task: BackgroundTask): Promise {
+ public async registerTask(task: BackgroundTaskConfig): Promise {
try {
// 检查任务是否已存在
if (this.tasks.has(task.id)) {
@@ -165,10 +184,10 @@ class BackgroundTaskManager {
}
console.log(`开始执行任务: ${taskId}`);
-
+
// 执行任务
await task.handler(data);
-
+
// 更新任务状态
const status = this.taskStatuses.get(taskId);
if (status) {
@@ -181,14 +200,14 @@ class BackgroundTaskManager {
console.log(`任务 ${taskId} 执行成功`);
} catch (error) {
console.error(`执行任务 ${taskId} 失败:`, error);
-
+
// 更新错误状态
const status = this.taskStatuses.get(taskId);
if (status) {
status.lastError = error instanceof Error ? error.message : String(error);
await this.saveTaskStatuses();
}
-
+
throw error;
}
}
@@ -196,8 +215,8 @@ class BackgroundTaskManager {
// 执行所有任务
public async executeAllTasks(): Promise<{ [taskId: string]: 'success' | 'failed' }> {
const results: { [taskId: string]: 'success' | 'failed' } = {};
-
- for (const [taskId, task] of this.tasks) {
+
+ for (const [taskId, task] of Array.from(this.tasks.entries())) {
try {
await this.executeTask(taskId);
results[taskId] = 'success';
@@ -206,7 +225,7 @@ class BackgroundTaskManager {
results[taskId] = 'failed';
}
}
-
+
return results;
}
@@ -221,13 +240,13 @@ class BackgroundTaskManager {
}
// 获取已注册的任务列表
- public getRegisteredTasks(): BackgroundTask[] {
+ public getRegisteredTasks(): BackgroundTaskConfig[] {
return Array.from(this.tasks.values());
}
// 检查后台任务状态
- public async getBackgroundTaskStatus(): Promise {
- return await BackgroundFetch.getStatusAsync();
+ public async getBackgroundTaskStatus(): Promise {
+ return await BackgroundTask.getStatusAsync();
}
// 保存任务状态到本地存储
@@ -259,50 +278,92 @@ class BackgroundTaskManager {
public async cleanupTaskStatuses(): Promise {
const now = new Date();
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
-
+
for (const [taskId, status] of this.taskStatuses) {
if (status.lastExecution && status.lastExecution < thirtyDaysAgo && !status.isRegistered) {
this.taskStatuses.delete(taskId);
}
}
-
+
await this.saveTaskStatuses();
}
- // 调试函数:强制触发后台任务执行
+ // 手动触发后台任务(仅开发环境)
+ public async triggerTaskForTesting(): Promise {
+ if (!__DEV__) {
+ console.warn('⚠️ triggerTaskForTesting 仅在开发环境可用');
+ return;
+ }
+
+ try {
+ console.log('🧪 触发后台任务进行测试...');
+ await BackgroundTask.triggerTaskWorkerForTestingAsync();
+ console.log('✅ 后台任务测试触发成功');
+ } catch (error) {
+ console.error('❌ 触发后台任务测试失败:', error);
+ }
+ }
+
+ // 调试函数:显示后台任务状态
public async debugExecuteBackgroundTask(): Promise {
- console.log('=== 调试:手动触发后台任务执行 ===');
-
+ console.log('===============================');
+ console.log('🔧 调试:后台任务状态检查');
+ console.log('===============================');
+
try {
// 获取后台任务状态
const status = await this.getBackgroundTaskStatus();
- console.log('后台获取状态:', status);
-
- // 执行所有注册的任务
- console.log('当前注册的任务数量:', this.tasks.size);
+ const statusText = Object.keys(BackgroundTask.BackgroundTaskStatus).find(
+ key => BackgroundTask.BackgroundTaskStatus[key as keyof typeof BackgroundTask.BackgroundTaskStatus] === status
+ );
+ console.log('📊 后台任务服务状态:', statusText || status);
+
+ // 检查系统任务是否已注册
+ const isSystemTaskRegistered = await TaskManager.isTaskRegisteredAsync(BACKGROUND_TASK_NAME);
+ console.log('🔄 系统后台任务是否已注册:', isSystemTaskRegistered);
+ console.log('🔄 管理器中的系统任务状态:', this.systemTaskRegistered);
+
+ // 显示自定义任务信息
+ console.log('📝 当前注册的自定义任务数量:', this.tasks.size);
this.tasks.forEach((task, id) => {
- console.log(`- 任务ID: ${id}, 名称: ${task.name}`);
+ console.log(` - 任务ID: ${id}, 名称: ${task.name}`);
});
-
+
+ if (this.tasks.size === 0) {
+ console.warn('⚠️ 没有注册的自定义任务');
+ }
+
+ // 手动执行所有任务
+ console.log('🚀 手动执行所有注册的任务...');
const results = await this.executeAllTasks();
- console.log('任务执行结果:', results);
-
- // 显示任务状态
+ console.log('✅ 任务执行结果:', results);
+
+ // 显示详细的任务状态
+ console.log('📈 任务执行统计:');
const taskStatuses = this.getAllTaskStatuses();
- taskStatuses.forEach(status => {
- console.log(`任务 ${status.id} 状态:`, {
- isRegistered: status.isRegistered,
- executionCount: status.executionCount,
- lastExecution: status.lastExecution?.toISOString(),
- lastError: status.lastError
+ taskStatuses.forEach(taskStatus => {
+ console.log(` 📊 任务 ${taskStatus.id}:`, {
+ 注册状态: taskStatus.isRegistered ? '✅ 已注册' : '❌ 未注册',
+ 执行次数: taskStatus.executionCount,
+ 最后执行: taskStatus.lastExecution?.toLocaleString('zh-CN') || '从未执行',
+ 最后错误: taskStatus.lastError || '无'
});
});
-
+
+ // 开发环境下触发测试
+ if (__DEV__) {
+ console.log('🧪 开发环境:触发后台任务测试...');
+ await this.triggerTaskForTesting();
+ }
+
+ console.log('===============================');
+ console.log('✅ 调试检查完成');
+ console.log('===============================');
+
} catch (error) {
- console.error('调试执行失败:', error);
+ console.error('❌ 调试执行失败:', error);
+ console.log('===============================');
}
-
- console.log('=== 调试执行完成 ===');
}
}
@@ -310,4 +371,4 @@ class BackgroundTaskManager {
export const backgroundTaskManager = BackgroundTaskManager.getInstance();
// 导出类型
-export type { BackgroundTask as BackgroundTaskType, TaskStatus as TaskStatusType };
+export type { BackgroundTaskConfig as BackgroundTaskType, TaskStatus as TaskStatusType };
diff --git a/services/notifications.ts b/services/notifications.ts
index f0e4f27..ebfbe61 100644
--- a/services/notifications.ts
+++ b/services/notifications.ts
@@ -182,7 +182,7 @@ export class NotificationService {
}
/**
- * 检查用户是否允许推送通知
+ * 检查用户是否允许推送通知(不包括系统权限检查,仅检查用户偏好)
*/
private async isNotificationAllowed(): Promise {
try {
@@ -193,16 +193,35 @@ export class NotificationService {
return false;
}
+ return true;
+ } catch (error) {
+ console.error('检查推送权限失败:', error);
+ // 如果检查失败,默认允许(避免阻塞重要的健康提醒)
+ return true;
+ }
+ }
+
+ /**
+ * 完整的权限检查(包括系统权限和用户偏好)
+ */
+ private async hasFullNotificationPermission(): Promise {
+ try {
// 检查系统权限
const permissionStatus = await this.getPermissionStatus();
if (permissionStatus !== 'granted') {
- console.log('系统推送权限未授予');
+ console.log('系统推送权限未授予:', permissionStatus);
+ return false;
+ }
+
+ // 检查用户偏好
+ const userPreferenceEnabled = await this.isNotificationAllowed();
+ if (!userPreferenceEnabled) {
return false;
}
return true;
} catch (error) {
- console.error('检查推送权限失败:', error);
+ console.error('完整权限检查失败:', error);
return false;
}
}
@@ -215,11 +234,11 @@ export class NotificationService {
trigger?: Notifications.NotificationTriggerInput
): Promise {
try {
- // 检查用户是否允许推送通知
- const isAllowed = await this.isNotificationAllowed();
- if (!isAllowed) {
- console.log('推送通知被用户偏好设置或系统权限阻止,跳过发送');
- return 'blocked_by_user_preference';
+ // 检查完整权限(系统权限 + 用户偏好)
+ const hasPermission = await this.hasFullNotificationPermission();
+ if (!hasPermission) {
+ console.log('推送通知被系统权限或用户偏好设置阻止,跳过发送');
+ return 'blocked_by_permission_or_preference';
}
const notificationId = await Notifications.scheduleNotificationAsync({
@@ -234,10 +253,10 @@ export class NotificationService {
trigger: trigger || null, // null表示立即发送
});
- console.log('本地通知已安排,ID:', notificationId);
+ console.log('✅ 本地通知已安排,ID:', notificationId);
return notificationId;
} catch (error) {
- console.error('安排本地通知失败:', error);
+ console.error('❌ 安排本地通知失败:', error);
throw error;
}
}
@@ -246,6 +265,7 @@ export class NotificationService {
* 发送立即通知
*/
async sendImmediateNotification(notification: NotificationData): Promise {
+ console.log('📱 准备发送立即通知:', notification.title);
return this.scheduleLocalNotification(notification);
}