- 添加新的processing任务标识符到iOS配置文件 - 重构BackgroundTaskBridge支持不同任务类型(refresh/processing) - 增强后台任务日志记录和调试信息 - 修复任务类型配置不匹配问题 - 改进任务调度逻辑和错误处理机制 - 添加任务执行时间戳记录用于调试 - 移除notification-settings中未使用的AuthGuard依赖
235 lines
8.7 KiB
Swift
235 lines
8.7 KiB
Swift
import Expo
|
||
import React
|
||
import ReactAppDependencyProvider
|
||
import BackgroundTasks
|
||
|
||
@UIApplicationMain
|
||
public class AppDelegate: ExpoAppDelegate {
|
||
var window: UIWindow?
|
||
|
||
var reactNativeDelegate: ExpoReactNativeFactoryDelegate?
|
||
var reactNativeFactory: RCTReactNativeFactory?
|
||
|
||
public override func application(
|
||
_ application: UIApplication,
|
||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||
) -> Bool {
|
||
// 注意:必须在 super.application() 之前注册后台任务
|
||
// 这是 BGTaskScheduler 的硬性要求,否则任务永远不会被触发
|
||
if #available(iOS 13.0, *) {
|
||
registerBackgroundTasks()
|
||
}
|
||
|
||
let delegate = ReactNativeDelegate()
|
||
let factory = ExpoReactNativeFactory(delegate: delegate)
|
||
delegate.dependencyProvider = RCTAppDependencyProvider()
|
||
|
||
reactNativeDelegate = delegate
|
||
reactNativeFactory = factory
|
||
bindReactNativeFactory(factory)
|
||
|
||
#if os(iOS) || os(tvOS)
|
||
window = UIWindow(frame: UIScreen.main.bounds)
|
||
factory.startReactNative(
|
||
withModuleName: "main",
|
||
in: window,
|
||
launchOptions: launchOptions)
|
||
#endif
|
||
|
||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||
}
|
||
|
||
// MARK: - Application Lifecycle
|
||
|
||
public override func applicationDidEnterBackground(_ application: UIApplication) {
|
||
super.applicationDidEnterBackground(application)
|
||
|
||
// 后台任务调度现在由 BackgroundTaskBridge 统一管理
|
||
// 这里只需要确保应用有足够的时间完成后台任务
|
||
if #available(iOS 13.0, *) {
|
||
NSLog("[AppDelegate] 应用进入后台,后台任务由 BackgroundTaskBridge 管理")
|
||
}
|
||
}
|
||
|
||
// MARK: - Background Task Registration
|
||
|
||
@available(iOS 13.0, *)
|
||
private func registerBackgroundTasks() {
|
||
let identifier = "com.anonymous.digitalpilates.task"
|
||
|
||
// 注册后台任务处理器
|
||
// 必须在应用启动完成前注册,否则会崩溃
|
||
BGTaskScheduler.shared.register(
|
||
forTaskWithIdentifier: identifier,
|
||
using: nil
|
||
) { [weak self] task in
|
||
NSLog("[AppDelegate] ✅ 后台任务被系统触发: \(identifier)")
|
||
// 尝试通知 BackgroundTaskBridge 处理任务
|
||
// 如果 bridge 不可用,标记任务完成
|
||
self?.handleBackgroundTask(task, identifier: identifier)
|
||
}
|
||
|
||
NSLog("[AppDelegate] 后台任务已在应用启动时注册: \(identifier)")
|
||
|
||
// 额外注册 processing 类型的任务(如果需要)
|
||
let processingIdentifier = "com.anonymous.digitalpilates.processing"
|
||
BGTaskScheduler.shared.register(
|
||
forTaskWithIdentifier: processingIdentifier,
|
||
using: nil
|
||
) { [weak self] task in
|
||
NSLog("[AppDelegate] ✅ Processing 后台任务被系统触发: \(processingIdentifier)")
|
||
self?.handleBackgroundTask(task, identifier: processingIdentifier)
|
||
}
|
||
|
||
NSLog("[AppDelegate] Processing 后台任务已注册: \(processingIdentifier)")
|
||
}
|
||
|
||
@available(iOS 13.0, *)
|
||
private func handleBackgroundTask(_ task: BGTask, identifier: String) {
|
||
NSLog("[AppDelegate] ====== 后台任务被触发 ======")
|
||
NSLog("[AppDelegate] 任务标识符: \(identifier)")
|
||
NSLog("[AppDelegate] 任务类型: \(type(of: task))")
|
||
NSLog("[AppDelegate] 触发时间: \(Date().description)")
|
||
|
||
// 记录任务开始
|
||
let defaults = UserDefaults.standard
|
||
defaults.set(Date().timeIntervalSince1970, forKey: "last_background_task_trigger")
|
||
NSLog("[AppDelegate] 已记录任务触发时间到UserDefaults")
|
||
|
||
// 设置任务过期处理器(iOS 给的执行时间有限,通常30秒)
|
||
task.expirationHandler = { [weak self] in
|
||
NSLog("[AppDelegate] ⚠️ 后台任务即将过期,强制完成")
|
||
task.setTaskCompleted(success: false)
|
||
|
||
// 确保重新调度下一次任务
|
||
if let strongSelf = self {
|
||
strongSelf.scheduleNextBackgroundTask(identifier: identifier)
|
||
}
|
||
}
|
||
|
||
// 尝试获取 BackgroundTaskBridge 实例来处理任务
|
||
// 如果 React Native bridge 还未初始化,我们仍然可以执行一些基本的后台工作
|
||
guard let bridge = reactNativeFactory?.bridge,
|
||
bridge.isValid else {
|
||
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"),
|
||
object: nil,
|
||
userInfo: ["task": task, "identifier": identifier]
|
||
)
|
||
} else {
|
||
NSLog("[AppDelegate] ❌ BackgroundTaskBridge 模块未找到")
|
||
NSLog("[AppDelegate] 执行基本的后台任务并调度下一次")
|
||
self.executeBasicBackgroundMaintenance(task: task, identifier: identifier)
|
||
}
|
||
}
|
||
}
|
||
|
||
@available(iOS 13.0, *)
|
||
private func executeBasicBackgroundMaintenance(task: BGTask, identifier: String) {
|
||
NSLog("[AppDelegate] ===== 执行基本后台维护任务 =====")
|
||
NSLog("[AppDelegate] 标识符: \(identifier)")
|
||
NSLog("[AppDelegate] 开始时间: \(Date().description)")
|
||
|
||
// 在后台线程执行基本维护
|
||
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) {
|
||
NSLog("[AppDelegate] ===== 调度下一次后台任务 =====")
|
||
NSLog("[AppDelegate] 标识符: \(identifier)")
|
||
|
||
let request: BGTaskRequest
|
||
if identifier.contains("processing") {
|
||
request = BGProcessingTaskRequest(identifier: identifier)
|
||
if let processingRequest = request as? BGProcessingTaskRequest {
|
||
processingRequest.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||
processingRequest.requiresNetworkConnectivity = false
|
||
processingRequest.requiresExternalPower = false
|
||
}
|
||
} else {
|
||
request = BGAppRefreshTaskRequest(identifier: identifier)
|
||
if let appRefreshRequest = request as? BGAppRefreshTaskRequest {
|
||
appRefreshRequest.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
||
}
|
||
}
|
||
|
||
let formatter = DateFormatter()
|
||
formatter.dateFormat = "HH:mm:ss"
|
||
let scheduledTime = formatter.string(from: Date(timeIntervalSinceNow: 15 * 60))
|
||
NSLog("[AppDelegate] 计划执行时间: \(scheduledTime)")
|
||
|
||
do {
|
||
try BGTaskScheduler.shared.submit(request)
|
||
NSLog("[AppDelegate] ✅ 成功调度下一次后台任务,15分钟后")
|
||
} catch {
|
||
NSLog("[AppDelegate] ❌ 调度下一次后台任务失败: \(error.localizedDescription)")
|
||
}
|
||
}
|
||
|
||
// Linking API
|
||
public override func application(
|
||
_ app: UIApplication,
|
||
open url: URL,
|
||
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
|
||
) -> Bool {
|
||
return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
|
||
}
|
||
|
||
// Universal Links
|
||
public override func application(
|
||
_ application: UIApplication,
|
||
continue userActivity: NSUserActivity,
|
||
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
|
||
) -> Bool {
|
||
let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||
return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
|
||
}
|
||
}
|
||
|
||
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
|
||
// Extension point for config-plugins
|
||
|
||
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
||
// needed to return the correct URL for expo-dev-client.
|
||
bridge.bundleURL ?? bundleURL()
|
||
}
|
||
|
||
override func bundleURL() -> URL? {
|
||
#if DEBUG
|
||
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
|
||
#else
|
||
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
||
#endif
|
||
}
|
||
}
|