如何判定iOS应用启动来源(自然/非自然)及多渠道追踪问题
如何在iOS中精准追踪应用启动来源
我完全懂你的困扰——多个deeplink/推送SDK各有各的回调逻辑,但要理清到底是哪个渠道触发了应用启动,确实得花点心思捋清楚。下面是我在实际项目中验证过的几个实践方案,帮你精准区分不同的启动来源:
1. 先搭好全局追踪的基础框架
首先,你需要定义一个枚举来涵盖所有需要追踪的启动类型,再在AppDelegate/SceneDelegate里设置一个全局标记变量,用来记录最终的启动来源:
enum LaunchSource { case natural // 自然启动 case branch // Branch链接启动 case appsflyer // AppsFlyer链接启动 case ownPush // 自有推送启动 case brazePush // Braze推送启动 // 可以根据需求新增更多类型 } var currentLaunchSource: LaunchSource = .natural
核心原则是:只在首次确定启动来源时赋值,避免后续回调覆盖正确结果,所以每次更新标记前都要判断当前是否还是默认的.natural。
2. 针对不同SDK的回调逐个处理
Deeplink类SDK(Branch/AppsFlyer)
这类SDK的回调主要集中在处理URL或UserActivity的方法里,要按顺序判断:
- Branch:在
application(_:continue:restorationHandler:)或SceneDelegate的scene(_:continue:)中处理:func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL { if currentLaunchSource == .natural && Branch.getInstance().handleDeepLink(url) { currentLaunchSource = .branch return true } } // 接着判断AppsFlyer if currentLaunchSource == .natural && AppsFlyerLib.shared().continueUserActivity(userActivity, restorationHandler: restorationHandler) { currentLaunchSource = .appsflyer return true } return false } - AppsFlyer:还要在
application(_:open:options:)中补充处理直接打开URL的场景:func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { if currentLaunchSource == .natural && AppsFlyerLib.shared().handleOpen(url, options: options) { currentLaunchSource = .appsflyer return true } if currentLaunchSource == .natural && Branch.getInstance().handleDeepLink(url) { currentLaunchSource = .branch return true } return false }
推送通知类SDK(自有推送/Braze)
推送启动的关键是判断应用状态——只有从后台/未启动状态被推送唤醒时,才属于启动来源:
- 自有推送:在推送回调里检查自定义标识字段:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let isWakingUp = application.applicationState == .inactive || application.applicationState == .background if isWakingUp && currentLaunchSource == .natural { if let source = userInfo["source"] as? String, source == "own_push" { currentLaunchSource = .ownPush } } completionHandler(.noData) } - Braze(原AppBoy):通过推送payload里的Braze专属字段判断:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let isWakingUp = application.applicationState == .inactive || application.applicationState == .background if isWakingUp && currentLaunchSource == .natural { if userInfo["ab"] != nil || userInfo["braze"] != nil { currentLaunchSource = .brazePush } } // 别忘了调用Braze的原生处理方法 Appboy.sharedInstance()?.handleNotification(userInfo, inApplication: application, withCompletionHandler: completionHandler) }
3. 处理冷启动的特殊场景
应用完全关闭后被打开的冷启动,要在didFinishLaunchingWithOptions或scene(_:willConnectTo:options:)里先检查启动参数:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 先判断推送启动 if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] { if remoteNotification["ab"] != nil { currentLaunchSource = .brazePush } else if remoteNotification["source"] as? String == "own_push" { currentLaunchSource = .ownPush } } // 再判断deeplink启动 if currentLaunchSource == .natural, let url = launchOptions?[.url] as? URL { if Branch.getInstance().handleDeepLink(url) { currentLaunchSource = .branch } else if AppsFlyerLib.shared().handleOpen(url, options: launchOptions) { currentLaunchSource = .appsflyer } } // 异步初始化SDK的场景,比如Branch的initSession Branch.getInstance().initSession(launchOptions: launchOptions) { params, error in if currentLaunchSource == .natural && error == nil && params != nil { currentLaunchSource = .branch } } AppsFlyerLib.shared().start() return true }
4. 避坑小贴士
- 回调顺序优先级:有些SDK的回调是异步的(比如Branch的
initSession),要在它的回调里补充检查,确保不会漏掉冷启动时的deeplink来源。 - 不要重复覆盖:每次更新
currentLaunchSource前,都要判断当前是否还是.natural,避免已经确定的来源被后续无关回调覆盖。 - 全面测试:一定要覆盖冷启动、热启动、推送唤醒、各类deeplink启动的所有场景,确保每个来源都能被正确识别。
这样一套逻辑下来,你就能清晰区分所有启动来源了,后续新增SDK也只要在对应回调里补充标记逻辑就行,扩展性很强。
内容的提问来源于stack exchange,提问作者Daniel Larsson
相关产品推荐
相关产品推荐

