You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何判定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:59:37