App关闭时推送通知行为异常的技术咨询
解决iOS点击远程通知(含Deep Link)在App关闭状态下异常的问题
哥们,我之前踩过一模一样的坑!你现在遇到的问题核心原因很明确:当App处于完全关闭(冷启动)状态时,点击远程通知启动App,不会触发你写的那两个didReceiveRemoteNotification方法,而是会走App启动的入口方法application(_:didFinishLaunchingWithOptions:)(iOS 12及以下)或者SceneDelegate的scene(_:willConnectTo:options:)(iOS 13+)。
下面给你一套完整的解决方案,覆盖所有场景:
第一步:抽离公共的通知处理逻辑
先把你原来在didReceiveRemoteNotification里处理Deep Link的代码,抽成一个独立的私有方法,这样不管是冷启动还是后台唤醒,都复用同一套逻辑,避免重复代码和不一致的问题:
private func handleRemoteNotification(userInfo: [AnyHashable: Any]) { // 这里放你原来处理Deep Link的核心逻辑 // 示例:从userInfo里提取Deep Link URL并跳转 guard let deepLinkString = userInfo["deep_link"] as? String, let deepLinkUrl = URL(string: deepLinkString) else { return } // 注意:冷启动时App UI可能未完全初始化,直接跳转可能出问题 // 这里可以根据情况处理:比如暂存URL,等App就绪后再跳转 if UIApplication.shared.applicationState == .active { // App在前台,直接跳转 navigateToDeepLink(url: deepLinkUrl) } else { // App从后台或冷启动,暂存URL,等主界面加载完成后跳转 UserDefaults.standard.set(deepLinkString, forKey: "PendingDeepLink") // 可以通过NotificationCenter发送通知,通知主控制器处理跳转 NotificationCenter.default.post(name: NSNotification.Name("HandlePendingDeepLink"), object: nil) } } // 示例:跳转逻辑(根据你的App路由方式调整) private func navigateToDeepLink(url: URL) { // 比如用UIApplication打开,或者用你自己的路由框架 UIApplication.shared.open(url, options: [:], completionHandler: nil) }
第二步:适配冷启动场景
iOS 12及以下(AppDelegate)
在application(_:didFinishLaunchingWithOptions:)里检查是否是通过远程通知启动的,是的话调用公共处理方法:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 检查启动参数里的远程通知 if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] { handleRemoteNotification(userInfo: remoteNotification) } // 其他初始化代码... // 检查是否有暂存的Deep Link,App就绪后处理 if let pendingDeepLink = UserDefaults.standard.string(forKey: "PendingDeepLink"), let url = URL(string: pendingDeepLink) { navigateToDeepLink(url: url) UserDefaults.standard.removeObject(forKey: "PendingDeepLink") } return true }
iOS 13+(SceneDelegate)
如果你的App适配了多场景,还要在SceneDelegate里处理冷启动的通知:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // 检查是否是远程通知启动 if let remoteNotification = connectionOptions.remoteNotification { handleRemoteNotification(userInfo: remoteNotification.userInfo) } // 其他初始化代码... } // 后台唤醒时的通知处理 func scene(_ scene: UIScene, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { handleRemoteNotification(userInfo: userInfo) completionHandler(.newData) // 根据实际处理结果返回对应的状态 } // 场景激活时处理暂存的Deep Link func sceneDidBecomeActive(_ scene: UIScene) { if let pendingDeepLink = UserDefaults.standard.string(forKey: "PendingDeepLink"), let url = URL(string: pendingDeepLink) { navigateToDeepLink(url: url) UserDefaults.standard.removeObject(forKey: "PendingDeepLink") } }
第三步:改造原有的通知处理方法
把原来的两个didReceiveRemoteNotification方法改成调用公共函数,同时别忘了处理fetchCompletionHandler:
// iOS 10以下的前台/后台通知处理 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { handleRemoteNotification(userInfo: userInfo) } // 带后台刷新的通知处理 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { handleRemoteNotification(userInfo: userInfo) // 根据你的处理结果返回对应的状态:.newData/.noData/.failed completionHandler(.newData) }
关键注意点
- 冷启动的UI就绪问题:冷启动时App的ViewController可能还没加载完成,直接跳转会导致崩溃或无响应,所以建议暂存Deep Link,等App的主界面激活后再处理(比如在
sceneDidBecomeActive或主控制器的viewDidAppear里)。 - iOS版本适配:如果你的App同时支持iOS 12及以下和iOS 13+,要同时处理AppDelegate和SceneDelegate的逻辑,避免遗漏场景。
- completionHandler的调用:带
fetchCompletionHandler的方法必须调用这个闭包,否则会影响App的后台刷新权限。
内容的提问来源于stack exchange,提问作者tara tandel
相关产品推荐
相关产品推荐

