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 } }