# 分析方案
## 变更内容总结 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:
265
docs/BACKGROUND_TASK_FIX_SUMMARY.md
Normal file
265
docs/BACKGROUND_TASK_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# iOS 后台任务修复总结
|
||||
|
||||
## 问题分析
|
||||
|
||||
经过仔细检查代码,发现iOS端后台任务无法自动运行的根本原因:
|
||||
|
||||
### 1. 缺少应用生命周期监听
|
||||
- **问题**:后台任务只在应用初始化时调度一次,没有在应用进入后台时主动调度
|
||||
- **影响**:应用进入后台后,系统不知道需要执行后台任务
|
||||
|
||||
### 2. 任务类型选择不当
|
||||
- **问题**:使用了 `BGProcessingTaskRequest`(用于长时间处理任务)
|
||||
- **应该使用**:`BGAppRefreshTaskRequest`(用于定期刷新数据)
|
||||
- **影响**:系统可能不会按预期调度任务
|
||||
|
||||
### 3. 任务调度不连续
|
||||
- **问题**:任务完成后没有自动重新调度下一次任务
|
||||
- **影响**:任务只执行一次就停止了
|
||||
|
||||
### 4. 延迟时间设置不合理
|
||||
- **问题**:30分钟的延迟时间对于测试来说太长
|
||||
- **影响**:难以验证功能是否正常工作
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. AppDelegate.swift 修改
|
||||
|
||||
#### 添加应用生命周期监听
|
||||
```swift
|
||||
public override func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
super.applicationDidEnterBackground(application)
|
||||
|
||||
// 当应用进入后台时,调度后台任务
|
||||
if #available(iOS 13.0, *) {
|
||||
scheduleBackgroundTask()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 添加后台任务调度方法
|
||||
```swift
|
||||
@available(iOS 13.0, *)
|
||||
private func scheduleBackgroundTask() {
|
||||
let identifier = "com.anonymous.digitalpilates.task"
|
||||
|
||||
// 取消之前的任务请求
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier)
|
||||
|
||||
let request = BGAppRefreshTaskRequest(identifier: identifier)
|
||||
// 设置最早开始时间为15分钟后
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
NSLog("[AppDelegate] 后台任务已调度,将在15分钟后执行")
|
||||
} catch {
|
||||
NSLog("[AppDelegate] 调度后台任务失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. BackgroundTaskBridge.swift 修改
|
||||
|
||||
#### 改进任务处理逻辑
|
||||
```swift
|
||||
@available(iOS 13.0, *)
|
||||
private func handle(task: BGTask) {
|
||||
// ... 现有代码 ...
|
||||
|
||||
guard self.hasListeners else {
|
||||
NSLog("[BackgroundTaskBridge] 没有JS监听器,直接完成任务")
|
||||
task.setTaskCompleted(success: false)
|
||||
self.currentTask = nil
|
||||
// 重新调度下一次任务
|
||||
self.rescheduleTask()
|
||||
return
|
||||
}
|
||||
|
||||
// ... 发送事件到JS ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 添加自动重新调度方法
|
||||
```swift
|
||||
@available(iOS 13.0, *)
|
||||
private func rescheduleTask() {
|
||||
guard let identifier = self.identifier else { return }
|
||||
|
||||
let request = BGAppRefreshTaskRequest(identifier: identifier)
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: self.defaultDelay)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
NSLog("[BackgroundTaskBridge] 已重新调度后台任务,延迟: \(self.defaultDelay)秒")
|
||||
} catch {
|
||||
NSLog("[BackgroundTaskBridge] 重新调度后台任务失败: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 改进任务调度方法
|
||||
```swift
|
||||
@available(iOS 13.0, *)
|
||||
private func scheduleTask(after delay: TimeInterval) throws {
|
||||
guard let identifier else {
|
||||
throw NSError(...)
|
||||
}
|
||||
|
||||
// 取消之前的任务请求
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier)
|
||||
|
||||
// 使用 BGAppRefreshTaskRequest
|
||||
let request = BGAppRefreshTaskRequest(identifier: identifier)
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: delay)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
NSLog("[BackgroundTaskBridge] 后台任务已调度,标识符: \(identifier),延迟: \(delay)秒")
|
||||
} catch {
|
||||
NSLog("[BackgroundTaskBridge] 调度后台任务失败: \(error.localizedDescription)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. backgroundTaskManagerV2.ts 修改
|
||||
|
||||
#### 减少默认延迟时间
|
||||
```typescript
|
||||
const DEFAULT_RESCHEDULE_INTERVAL_SECONDS = 60 * 15; // 从30分钟改为15分钟
|
||||
```
|
||||
|
||||
#### 改进初始化逻辑
|
||||
```typescript
|
||||
async initialize(): Promise<void> {
|
||||
// ... 现有代码 ...
|
||||
|
||||
await NativeBackgroundModule.configure({
|
||||
identifier: BACKGROUND_TASK_IDENTIFIER,
|
||||
taskType: 'refresh', // 从 'processing' 改为 'refresh'
|
||||
requiresNetworkConnectivity: false,
|
||||
requiresExternalPower: false,
|
||||
defaultDelay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS,
|
||||
});
|
||||
|
||||
this.isInitialized = true;
|
||||
log.info('[BackgroundTaskManagerV2] 已初始化并注册 iOS 后台任务');
|
||||
|
||||
// 立即调度一次后台任务
|
||||
await this.scheduleNextTask();
|
||||
}
|
||||
```
|
||||
|
||||
#### 添加任务调度方法
|
||||
```typescript
|
||||
private async scheduleNextTask(): Promise<void> {
|
||||
if (!isIosBackgroundModuleAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await NativeBackgroundModule.schedule({
|
||||
delay: DEFAULT_RESCHEDULE_INTERVAL_SECONDS
|
||||
});
|
||||
log.info('[BackgroundTaskManagerV2] 已调度下一次后台任务');
|
||||
} catch (error) {
|
||||
log.error('[BackgroundTaskManagerV2] 调度后台任务失败', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 完整的后台任务流程
|
||||
|
||||
1. **应用启动**
|
||||
- `AppDelegate` 在 `didFinishLaunchingWithOptions` 中注册后台任务处理器
|
||||
- `BackgroundTaskManagerV2` 初始化并配置后台任务
|
||||
- 立即调度第一次后台任务
|
||||
|
||||
2. **应用进入后台**
|
||||
- `AppDelegate.applicationDidEnterBackground` 被调用
|
||||
- 调用 `scheduleBackgroundTask()` 调度后台任务
|
||||
- 设置15分钟后开始执行
|
||||
|
||||
3. **系统执行后台任务**
|
||||
- 系统在合适的时机(15分钟后或更晚)唤醒应用
|
||||
- 调用 `AppDelegate` 中注册的任务处理器
|
||||
- 任务处理器通过 NotificationCenter 通知 `BackgroundTaskBridge`
|
||||
|
||||
4. **任务执行**
|
||||
- `BackgroundTaskBridge` 发送事件到 JavaScript
|
||||
- `BackgroundTaskManagerV2` 接收事件并执行后台任务
|
||||
- 执行喝水提醒、挑战鼓励、断食通知等任务
|
||||
|
||||
5. **任务完成**
|
||||
- JavaScript 调用 `complete()` 方法
|
||||
- `BackgroundTaskBridge` 标记任务完成
|
||||
- 自动重新调度下一次任务
|
||||
|
||||
6. **持续循环**
|
||||
- 每次任务完成后都会重新调度
|
||||
- 确保后台任务持续执行
|
||||
|
||||
## 关键改进点
|
||||
|
||||
### 1. 使用 BGAppRefreshTaskRequest
|
||||
- 更适合定期刷新数据的场景
|
||||
- 系统调度更可靠
|
||||
- 执行频率更高
|
||||
|
||||
### 2. 应用生命周期集成
|
||||
- 在应用进入后台时主动调度
|
||||
- 确保任务不会被遗漏
|
||||
|
||||
### 3. 自动重新调度
|
||||
- 任务完成后自动调度下一次
|
||||
- 形成持续的执行循环
|
||||
|
||||
### 4. 更短的延迟时间
|
||||
- 15分钟的延迟更适合测试
|
||||
- 提高任务执行频率
|
||||
|
||||
### 5. 更好的错误处理
|
||||
- 添加详细的日志输出
|
||||
- 处理各种异常情况
|
||||
- 确保任务不会中断
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 真机测试(推荐)
|
||||
1. 构建并安装到真机
|
||||
2. 启用后台应用刷新
|
||||
3. 将应用切换到后台
|
||||
4. 等待15-30分钟
|
||||
5. 检查是否收到通知
|
||||
|
||||
### Xcode 模拟测试
|
||||
1. 在 Xcode 中运行应用
|
||||
2. 选择 Debug > Simulate Background Fetch
|
||||
3. 观察日志输出
|
||||
4. 验证任务执行
|
||||
|
||||
### 注意事项
|
||||
- 模拟器支持有限,建议在真机上测试
|
||||
- 系统调度时间不确定,需要耐心等待
|
||||
- 低电量模式会限制后台任务
|
||||
- 确保后台应用刷新已启用
|
||||
|
||||
## 预期效果
|
||||
|
||||
修复后,iOS 后台任务应该能够:
|
||||
|
||||
1. ✅ 在应用进入后台时自动调度
|
||||
2. ✅ 每15-30分钟执行一次(取决于系统调度)
|
||||
3. ✅ 执行喝水提醒、挑战鼓励、断食通知等任务
|
||||
4. ✅ 任务完成后自动重新调度
|
||||
5. ✅ 持续运行,不会中断
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `ios/OutLive/AppDelegate.swift` - 应用生命周期和任务注册
|
||||
- `ios/OutLive/BackgroundTaskBridge.swift` - 原生后台任务桥接
|
||||
- `services/backgroundTaskManagerV2.ts` - JavaScript 后台任务管理器
|
||||
- `docs/BACKGROUND_TASK_TESTING.md` - 详细测试指南
|
||||
162
docs/BACKGROUND_TASK_TESTING.md
Normal file
162
docs/BACKGROUND_TASK_TESTING.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# iOS 后台任务测试指南
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. 主要问题
|
||||
- **缺少应用生命周期监听**:之前没有在应用进入后台时主动调度任务
|
||||
- **任务类型不匹配**:使用了 `BGProcessingTaskRequest` 而不是更适合的 `BGAppRefreshTaskRequest`
|
||||
- **调度时机不当**:只在初始化时调度一次,没有持续调度
|
||||
- **延迟时间过长**:30分钟的延迟导致测试困难
|
||||
|
||||
### 2. 修复方案
|
||||
|
||||
#### AppDelegate.swift
|
||||
- 添加 `applicationDidEnterBackground` 方法,在应用进入后台时自动调度任务
|
||||
- 使用 `BGAppRefreshTaskRequest` 替代 `BGProcessingTaskRequest`
|
||||
- 设置15分钟的最早开始时间
|
||||
|
||||
#### BackgroundTaskBridge.swift
|
||||
- 改进任务处理逻辑,在任务完成或失败时自动重新调度
|
||||
- 添加 `rescheduleTask` 方法确保任务持续执行
|
||||
- 取消旧任务请求,避免重复调度
|
||||
- 添加更详细的日志输出
|
||||
|
||||
#### backgroundTaskManagerV2.ts
|
||||
- 将默认延迟从30分钟减少到15分钟
|
||||
- 在初始化完成后立即调度第一次任务
|
||||
- 使用 `refresh` 类型而不是 `processing` 类型
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 在真机上测试(推荐)
|
||||
|
||||
1. **准备工作**
|
||||
```bash
|
||||
# 构建并安装到真机
|
||||
npx expo run:ios --device
|
||||
```
|
||||
|
||||
2. **启用后台应用刷新**
|
||||
- 打开 iOS 设置 > 通用 > 后台应用刷新
|
||||
- 确保总开关已打开
|
||||
- 找到 "Out Live" 并启用
|
||||
|
||||
3. **检查后台任务状态**
|
||||
- 在应用的统计页面,点击"测试后台任务"按钮
|
||||
- 查看后台任务状态是否为"可用"
|
||||
|
||||
4. **触发后台任务**
|
||||
|
||||
方法一:使用 Xcode 模拟
|
||||
```bash
|
||||
# 在 Xcode 中,选择 Debug > Simulate Background Fetch
|
||||
# 或者使用命令行
|
||||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.anonymous.digitalpilates.task"]
|
||||
```
|
||||
|
||||
方法二:自然触发
|
||||
- 将应用切换到后台
|
||||
- 等待15-30分钟
|
||||
- 系统会在合适的时机自动执行后台任务
|
||||
|
||||
5. **验证任务执行**
|
||||
- 检查是否收到通知(喝水提醒、挑战鼓励等)
|
||||
- 查看 Xcode 控制台日志
|
||||
- 检查应用内的最后后台检查时间
|
||||
|
||||
### 使用 Xcode 调试
|
||||
|
||||
1. **连接设备并运行应用**
|
||||
```bash
|
||||
# 在 Xcode 中打开项目
|
||||
open ios/OutLive.xcworkspace
|
||||
```
|
||||
|
||||
2. **设置断点**
|
||||
- 在 `BackgroundTaskBridge.swift` 的 `handle(task:)` 方法设置断点
|
||||
- 在 `AppDelegate.swift` 的 `handleBackgroundTask` 方法设置断点
|
||||
|
||||
3. **模拟后台任务**
|
||||
- 运行应用
|
||||
- 在 Xcode 菜单中选择 `Debug` > `Simulate Background Fetch`
|
||||
- 观察断点是否被触发
|
||||
|
||||
4. **查看日志**
|
||||
```
|
||||
[AppDelegate] 后台任务已调度,将在15分钟后执行
|
||||
[BackgroundTaskBridge] 收到来自 AppDelegate 的后台任务
|
||||
[BackgroundTaskBridge] 发送后台任务执行事件到JS
|
||||
[BackgroundTaskManagerV2] 收到后台任务事件
|
||||
```
|
||||
|
||||
### 在模拟器上测试(有限支持)
|
||||
|
||||
⚠️ **注意**:iOS 模拟器对后台任务的支持有限,某些功能可能无法正常工作。
|
||||
|
||||
1. **运行应用**
|
||||
```bash
|
||||
npx expo run:ios
|
||||
```
|
||||
|
||||
2. **手动触发测试**
|
||||
- 在应用的统计页面,点击"测试后台任务"按钮
|
||||
- 这会模拟后台任务执行,但不会真正测试系统调度
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 后台任务状态显示"受限制"或"被拒绝"
|
||||
- 检查 iOS 设置中的后台应用刷新是否已启用
|
||||
- 检查设备的低电量模式是否已关闭
|
||||
- 重启设备后重试
|
||||
|
||||
### 2. 后台任务从不执行
|
||||
- 确保应用已完全进入后台(不是挂起状态)
|
||||
- 等待足够长的时间(至少15-30分钟)
|
||||
- 检查设备是否有足够的电量和网络连接
|
||||
- 使用 Xcode 的模拟功能进行测试
|
||||
|
||||
### 3. 日志显示"BGTaskSchedulerErrorDomain 错误1"
|
||||
- 这在模拟器上是正常的
|
||||
- 在真机上测试以验证功能
|
||||
|
||||
### 4. 任务执行但没有发送通知
|
||||
- 检查通知权限是否已授予
|
||||
- 检查通知设置是否正确
|
||||
- 查看应用日志确认任务逻辑是否正确执行
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 启用详细日志
|
||||
在开发环境中,后台任务调试工具会自动启用,提供详细的日志输出。
|
||||
|
||||
### 2. 检查待处理的任务
|
||||
在应用中调用 `getPendingRequests()` 查看已调度的后台任务:
|
||||
```typescript
|
||||
const requests = await BackgroundTaskManager.getInstance().getPendingRequests();
|
||||
console.log('待处理的后台任务:', requests);
|
||||
```
|
||||
|
||||
### 3. 查看最后执行时间
|
||||
```typescript
|
||||
const lastCheck = await BackgroundTaskManager.getInstance().getLastBackgroundCheckTime();
|
||||
console.log('最后后台检查时间:', new Date(lastCheck));
|
||||
```
|
||||
|
||||
### 4. 使用系统日志
|
||||
```bash
|
||||
# 在终端中查看设备日志
|
||||
xcrun simctl spawn booted log stream --predicate 'subsystem contains "com.anonymous.digitalpilates"'
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **在真机上测试**:模拟器的后台任务支持有限
|
||||
2. **耐心等待**:系统调度后台任务需要时间,不要期望立即执行
|
||||
3. **监控电量**:低电量模式会限制后台任务
|
||||
4. **检查设置**:确保后台应用刷新已启用
|
||||
5. **使用 Xcode 模拟**:在开发阶段使用 Xcode 的模拟功能快速测试
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [Apple 官方文档:Background Tasks](https://developer.apple.com/documentation/backgroundtasks)
|
||||
- [WWDC 2019: Advances in App Background Execution](https://developer.apple.com/videos/play/wwdc2019/707/)
|
||||
Reference in New Issue
Block a user