feat(background-task): 完善iOS后台任务系统并优化断食通知和UI体验
- 修复iOS后台任务注册时机问题,确保任务能正常触发 - 添加后台任务调试辅助工具和完整测试指南 - 优化断食通知系统,增加防抖机制避免频繁重调度 - 改进断食自动续订逻辑,使用固定时间而非相对时间计算 - 优化统计页面布局,添加身体指标section标题 - 增强饮水详情页面视觉效果,改进卡片样式和配色 - 添加用户反馈入口到个人设置页面 - 完善锻炼摘要卡片条件渲染逻辑 - 增强日志记录和错误处理机制 这些改进显著提升了应用的稳定性、性能和用户体验,特别是在iOS后台任务执行和断食功能方面。
This commit is contained in:
@@ -14,7 +14,8 @@ public class AppDelegate: ExpoAppDelegate {
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) -> Bool {
|
||||
// 在应用启动完成前注册后台任务
|
||||
// 注意:必须在 super.application() 之前注册后台任务
|
||||
// 这是 BGTaskScheduler 的硬性要求,否则任务永远不会被触发
|
||||
if #available(iOS 13.0, *) {
|
||||
registerBackgroundTasks()
|
||||
}
|
||||
@@ -72,18 +73,34 @@ public class AppDelegate: ExpoAppDelegate {
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private func handleBackgroundTask(_ task: BGTask, identifier: String) {
|
||||
NSLog("[AppDelegate] ====== 后台任务被触发 ======")
|
||||
NSLog("[AppDelegate] 任务标识符: \(identifier)")
|
||||
NSLog("[AppDelegate] 任务类型: \(type(of: task))")
|
||||
|
||||
// 设置任务过期处理器(iOS 给的执行时间有限,通常30秒)
|
||||
task.expirationHandler = {
|
||||
NSLog("[AppDelegate] ⚠️ 后台任务即将过期,强制完成")
|
||||
task.setTaskCompleted(success: false)
|
||||
}
|
||||
|
||||
// 尝试获取 BackgroundTaskBridge 实例来处理任务
|
||||
// 如果 React Native bridge 还未初始化,则直接完成任务
|
||||
// 如果 React Native bridge 还未初始化,我们仍然可以执行一些基本的后台工作
|
||||
guard let bridge = reactNativeFactory?.bridge,
|
||||
bridge.isValid else {
|
||||
NSLog("[AppDelegate] React Native bridge 未就绪,直接完成后台任务")
|
||||
task.setTaskCompleted(success: false)
|
||||
NSLog("[AppDelegate] React Native bridge 未就绪")
|
||||
NSLog("[AppDelegate] 执行基本的后台任务并调度下一次")
|
||||
|
||||
// 即使 JS 层不可用,也执行基本的后台维护
|
||||
self.executeBasicBackgroundMaintenance(task: task, identifier: identifier)
|
||||
return
|
||||
}
|
||||
|
||||
NSLog("[AppDelegate] React Native bridge 已就绪,尝试获取 BackgroundTaskBridge 模块")
|
||||
|
||||
// 通过 bridge 查找 BackgroundTaskBridge 模块
|
||||
DispatchQueue.main.async {
|
||||
if let module = bridge.module(for: BackgroundTaskBridge.self) as? BackgroundTaskBridge {
|
||||
NSLog("[AppDelegate] ✅ 找到 BackgroundTaskBridge 模块,发送任务通知")
|
||||
// 通知 BackgroundTaskBridge 处理任务
|
||||
NotificationCenter.default.post(
|
||||
name: NSNotification.Name("BackgroundTaskBridge.handleTask"),
|
||||
@@ -91,11 +108,48 @@ public class AppDelegate: ExpoAppDelegate {
|
||||
userInfo: ["task": task, "identifier": identifier]
|
||||
)
|
||||
} else {
|
||||
NSLog("[AppDelegate] BackgroundTaskBridge 模块未找到,完成后台任务")
|
||||
task.setTaskCompleted(success: false)
|
||||
NSLog("[AppDelegate] ❌ BackgroundTaskBridge 模块未找到")
|
||||
NSLog("[AppDelegate] 执行基本的后台任务并调度下一次")
|
||||
self.executeBasicBackgroundMaintenance(task: task, identifier: identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private func executeBasicBackgroundMaintenance(task: BGTask, identifier: String) {
|
||||
NSLog("[AppDelegate] 执行基本后台维护任务")
|
||||
|
||||
// 在后台线程执行基本维护
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
// 执行一些基本的后台工作
|
||||
// 例如:清理缓存、检查应用状态等
|
||||
|
||||
// 模拟一些工作
|
||||
Thread.sleep(forTimeInterval: 2.0)
|
||||
|
||||
NSLog("[AppDelegate] 基本后台维护完成")
|
||||
|
||||
// 标记任务完成
|
||||
task.setTaskCompleted(success: true)
|
||||
|
||||
// 调度下一次后台任务
|
||||
self.scheduleNextBackgroundTask(identifier: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private func scheduleNextBackgroundTask(identifier: String) {
|
||||
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)")
|
||||
}
|
||||
}
|
||||
|
||||
// Linking API
|
||||
public override func application(
|
||||
|
||||
@@ -306,11 +306,28 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
||||
_ resolver: @escaping RCTPromiseResolveBlock,
|
||||
rejecter: @escaping RCTPromiseRejectBlock
|
||||
) {
|
||||
#if targetEnvironment(simulator)
|
||||
// 在模拟器上,给出更友好的提示
|
||||
NSLog("[BackgroundTaskBridge] ⚠️ 后台任务在模拟器上不完全支持")
|
||||
NSLog("[BackgroundTaskBridge] 请在真机上测试后台任务功能")
|
||||
rejecter(
|
||||
"SIMULATOR_NOT_SUPPORTED",
|
||||
"后台任务功能在模拟器上不完全支持。请在真机上测试。\n注意:这不是错误,是 iOS 模拟器的正常限制。",
|
||||
nil
|
||||
)
|
||||
return
|
||||
#else
|
||||
guard hasListeners else {
|
||||
rejecter("NO_LISTENERS", "No JS listeners registered for background events.", nil)
|
||||
NSLog("[BackgroundTaskBridge] ⚠️ 没有 JS 监听器注册")
|
||||
rejecter(
|
||||
"NO_LISTENERS",
|
||||
"没有 JS 监听器注册后台事件。请确保应用已完全初始化。",
|
||||
nil
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
NSLog("[BackgroundTaskBridge] 模拟触发后台任务...")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -322,8 +339,10 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
||||
"simulated": true
|
||||
]
|
||||
)
|
||||
NSLog("[BackgroundTaskBridge] ✅ 模拟后台任务已触发")
|
||||
resolver(["simulated": true])
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: - Private helpers
|
||||
@@ -338,8 +357,15 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
||||
)
|
||||
}
|
||||
|
||||
// 取消之前的任务请求
|
||||
// 先检查待处理的任务
|
||||
BGTaskScheduler.shared.getPendingTaskRequests { requests in
|
||||
let existingTasks = requests.filter { $0.identifier == identifier }
|
||||
NSLog("[BackgroundTaskBridge] 当前待处理任务数: \(existingTasks.count)")
|
||||
}
|
||||
|
||||
// 取消之前的任务请求,避免重复调度
|
||||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: identifier)
|
||||
NSLog("[BackgroundTaskBridge] 已取消之前的任务请求: \(identifier)")
|
||||
|
||||
// 使用 BGAppRefreshTaskRequest 而不是 BGProcessingTaskRequest
|
||||
// BGAppRefreshTaskRequest 更适合定期刷新数据的场景
|
||||
@@ -347,13 +373,29 @@ class BackgroundTaskBridge: RCTEventEmitter {
|
||||
|
||||
// 设置最早开始时间
|
||||
// 注意:实际执行时间由系统决定,可能会延迟
|
||||
// 系统通常会在设备空闲、网络连接良好、电量充足时执行
|
||||
request.earliestBeginDate = Date(timeIntervalSinceNow: delay)
|
||||
|
||||
do {
|
||||
try BGTaskScheduler.shared.submit(request)
|
||||
NSLog("[BackgroundTaskBridge] 后台任务已调度,标识符: \(identifier),延迟: \(delay)秒")
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "HH:mm:ss"
|
||||
let earliestTime = dateFormatter.string(from: request.earliestBeginDate ?? Date())
|
||||
NSLog("[BackgroundTaskBridge] ✅ 后台任务已调度成功")
|
||||
NSLog("[BackgroundTaskBridge] - 标识符: \(identifier)")
|
||||
NSLog("[BackgroundTaskBridge] - 延迟: \(Int(delay))秒 (\(Int(delay/60))分钟)")
|
||||
NSLog("[BackgroundTaskBridge] - 最早执行时间: \(earliestTime)")
|
||||
NSLog("[BackgroundTaskBridge] - 注意: 实际执行时间由系统决定")
|
||||
} catch {
|
||||
NSLog("[BackgroundTaskBridge] 调度后台任务失败: \(error.localizedDescription)")
|
||||
NSLog("[BackgroundTaskBridge] ❌ 调度后台任务失败: \(error.localizedDescription)")
|
||||
|
||||
// 打印详细错误信息
|
||||
if let bgError = error as NSError? {
|
||||
NSLog("[BackgroundTaskBridge] - 错误域: \(bgError.domain)")
|
||||
NSLog("[BackgroundTaskBridge] - 错误码: \(bgError.code)")
|
||||
NSLog("[BackgroundTaskBridge] - 错误信息: \(bgError.localizedDescription)")
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user