Files
digital-pilates/ios/OutLive/AppDelegate.swift
richarjiang d282abd146 feat(background): 增强iOS后台任务系统,添加processing任务类型支持
- 添加新的processing任务标识符到iOS配置文件
- 重构BackgroundTaskBridge支持不同任务类型(refresh/processing)
- 增强后台任务日志记录和调试信息
- 修复任务类型配置不匹配问题
- 改进任务调度逻辑和错误处理机制
- 添加任务执行时间戳记录用于调试
- 移除notification-settings中未使用的AuthGuard依赖
2025-11-13 17:12:57 +08:00

235 lines
8.7 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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