Branch SDK 深度分析

138 阅读11分钟

Branch SDK 分析和自建归因

1. Branch SDK的主要目的

Branch SDK是一个移动应用归因和深度链接解决方案,主要实现以下功能:

  1. 深度链接(Deep Linking):创建可以直接将用户引导到应用内特定内容的链接
  2. 归因分析(Attribution):跟踪用户安装来源和用户行为
  3. 跨平台用户识别:在不同设备和平台上识别用户
  4. 应用内容索引:使应用内容可被搜索引擎索引
  5. 用户分享和邀请:提供应用内分享功能和邀请追踪

2. 实现方式

2.1 核心组件

  1. Branch类:SDK的主入口,提供初始化和主要功能接口
  2. BNCPreferenceHelper:管理SDK的配置和持久化数据
  3. BNCServerInterface/BNCServerRequest:处理与Branch服务器的通信
  4. BNCDeviceInfo:收集设备信息用于用户识别
  5. BranchUniversalObject:表示可以被分享或链接的内容对象

2.2 主要实现流程

2.2.1 初始化和会话管理
// Branch.m中的初始化流程
- (void)initSessionWithLaunchOptions:(NSDictionary *)options {
    // 设置设备信息
    // 检查是否是从深度链接启动
    // 发送初始化请求到服务器
}
2.2.2 深度链接处理

SDK通过监听应用启动方式(URL Scheme、Universal Links等)来处理深度链接:

// 处理Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity {
    // 解析链接
    // 提取参数
    // 通知应用
}
2.2.3 设备识别

通过BNCDeviceInfoBNCSystemObserver收集设备信息,用于用户识别:

  • 设备ID (IDFA或供应商ID)
  • 操作系统和版本
  • 设备型号
  • 安装信息
2.2.4 数据存储

使用BNCPreferenceHelper管理数据持久化:

  • 会话信息
  • 用户标识
  • 安装参数
  • 设备令牌
2.2.5 网络通信

通过BNCServerInterfaceBNCNetworkService与Branch服务器通信:

  • 初始化会话请求
  • 创建短链接请求
  • 事件追踪请求
  • 用户识别请求
2.2.6 内容分享

使用BranchUniversalObjectBranchLinkProperties创建可分享内容:

BranchUniversalObject *buo = [[BranchUniversalObject alloc] initWithCanonicalIdentifier:@"item/123"];
[buo setTitle:@"Item Title"];
// 设置其他属性

BranchLinkProperties *lp = [[BranchLinkProperties alloc] init];
[lp setChannel:@"facebook"];
// 设置其他链接属性

// 生成短链接
[buo getShortUrlWithLinkProperties:lp completion:^(NSString *url, NSError *error) {
    // 使用生成的短链接
}];
2.2.7 事件追踪

通过BranchEvent追踪用户行为:

BranchEvent *event = [BranchEvent standardEvent:BranchStandardEventPurchase];
[event setTransactionID:@"12344555"];
[event setCurrency:BNCCurrencyUSD];
[event setRevenue:@(1.5)];
[event logEvent];
2.2.8 SKAdNetwork支持

通过BNCSKAdNetwork提供Apple的SKAdNetwork广告归因支持,用于iOS 14+的隐私保护环境下的广告归因。

2.3 技术特点

  1. 单例模式:大量使用单例模式管理SDK实例和服务
  2. 类别扩展:使用Objective-C类别扩展标准类功能
  3. 异步操作:网络请求和数据处理采用异步方式
  4. 队列管理:使用操作队列管理请求顺序和并发
  5. 延迟加载:使用懒加载模式提高性能
  6. 隐私保护:适应iOS隐私保护机制(如IDFA权限请求)

2.4 关键文件功能

  • BNCConfig.m: 定义SDK版本和API端点
  • BNCDeviceInfo.m: 设备信息收集
  • BNCPreferenceHelper.m: 配置和数据持久化
  • BNCServerRequest.m: 服务器请求基类
  • Branch.m: SDK主类和入口点
  • BranchUniversalObject.m: 内容对象表示
  • BranchEvent.m: 事件追踪

3. 延迟深度链接实现

Branch SDK支持延迟深度链接(Deferred Deep Linking)功能,这是它的核心特性之一。

3.1 实现原理

  1. 用户标识匹配

    • 当用户点击Branch链接但尚未安装应用时,Branch服务器会记录用户的设备指纹信息
    • 用户安装应用后,SDK会收集设备信息并发送到服务器
    • 服务器通过匹配设备指纹确定这是同一用户
  2. 首次打开处理

    • 应用首次启动时,BranchOpenRequest会发送设备信息到Branch服务器
    • 服务器识别这是通过链接引导的安装,返回原始链接的参数
    • SDK将这些参数传递给应用,应用可以据此导航到特定内容

3.2 关键代码实现

Branch.m中,初始化过程会处理延迟深度链接:

- (void)initSessionWithLaunchOptions:(NSDictionary *)options isReferrable:(BOOL)isReferrable {
    // 初始化会话
    BranchOpenRequest *openRequest = [[BranchOpenRequest alloc] initWithCallback:^(BOOL success, NSError *error) {
        if (success) {
            // 处理延迟深度链接数据
            NSDictionary *installParams = [self getLatestReferringParams];
            if (installParams && self.sessionDelegate) {
                [self.sessionDelegate onInitFinished:installParams error:nil];
            }
        }
    }];
    
    // 设置是否可归因(影响延迟深度链接的匹配窗口)
    [openRequest setIsReferrable:isReferrable];
    
    // 添加设备指纹信息
    [openRequest setDeviceFingerPrintID:[BNCPreferenceHelper sharedInstance].randomizedDeviceToken];
    
    // 发送请求
    [self.requestQueue enqueue:openRequest];
    [self processNextQueueItem];
}

3.3 延迟深度链接的关键组件

  1. BranchOpenRequest

    • 处理应用启动时的服务器通信
    • 接收服务器返回的延迟深度链接数据
  2. BNCPreferenceHelper

    • 存储安装参数(installParams)
    • 当识别到延迟深度链接时,这些参数会被填充
  3. 设备指纹识别

    • BNCDeviceInfo收集设备信息用于匹配
    • 包括IDFA、供应商ID、IP地址等多个因素
  4. 回调机制

    • 通过initSessionWithCallback:提供的回调接收延迟深度链接数据
    • 开发者可以在回调中处理导航逻辑

3.4 使用示例

应用开发者可以这样使用延迟深度链接功能:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 初始化Branch SDK
    Branch *branch = [Branch getInstance];
    
    // 设置延迟深度链接回调
    [branch initSessionWithLaunchOptions:launchOptions andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error) {
        if (!error && params) {
            // 检查是否是首次安装
            if (params[@"~referring_link"]) {
                NSString *deepLinkPath = params[@"path"];
                if (deepLinkPath) {
                    // 导航到特定内容
                    [self navigateToContent:deepLinkPath];
                }
            }
        }
    }];
    
    return YES;
}

4. 自行实现延迟深度链接的服务端匹配机制

如果想自己实现类似Branch SDK的延迟深度链接功能,服务端匹配是核心环节。

4.1 设备指纹构建

首先需要从客户端收集足够的设备信息来构建唯一的"设备指纹":

关键设备信息
  • 硬件标识符

    • IDFA (iOS广告标识符)
    • IDFV (iOS供应商标识符)
    • Android的广告ID
    • 设备序列号(谨慎使用,受隐私政策限制)
  • 网络信息

    • IP地址
    • 用户代理字符串(User-Agent)
    • 网络运营商信息
  • 设备特征

    • 设备型号
    • 操作系统版本
    • 屏幕分辨率
    • 已安装应用列表(如果可获取)
    • 语言和地区设置
  • 应用信息

    • 应用版本
    • 安装时间
    • 首次启动时间

4.2 服务端存储结构

点击事件表
CREATE TABLE click_events (
    id VARCHAR(36) PRIMARY KEY,
    click_timestamp TIMESTAMP,
    link_id VARCHAR(36),
    campaign_id VARCHAR(36),
    ip_address VARCHAR(45),
    user_agent TEXT,
    device_type VARCHAR(50),
    os_version VARCHAR(20),
    browser VARCHAR(50),
    referrer TEXT,
    device_fingerprint_hash VARCHAR(64),
    additional_data JSON,
    expiry_timestamp TIMESTAMP
);
安装/打开事件表
CREATE TABLE install_events (
    id VARCHAR(36) PRIMARY KEY,
    install_timestamp TIMESTAMP,
    device_id VARCHAR(100),
    device_id_type VARCHAR(20),
    ip_address VARCHAR(45),
    user_agent TEXT,
    device_model VARCHAR(50),
    os_name VARCHAR(20),
    os_version VARCHAR(20),
    language VARCHAR(10),
    country VARCHAR(2),
    device_fingerprint_hash VARCHAR(64),
    matched_click_id VARCHAR(36),
    match_confidence FLOAT,
    app_version VARCHAR(20)
);

4.3 匹配算法实现

步骤1: 生成设备指纹哈希

当用户点击链接或安装应用时:

def generate_device_fingerprint(device_data):
    # 创建指纹基础
    fingerprint_base = {
        'ip': device_data.get('ip_address', ''),
        'ua': device_data.get('user_agent', ''),
        'model': device_data.get('device_model', ''),
        'os': f"{device_data.get('os_name', '')} {device_data.get('os_version', '')}",
        'screen': f"{device_data.get('screen_width', '')}x{device_data.get('screen_height', '')}"
    }
    
    # 添加唯一标识符(如果有)
    if 'idfa' in device_data and device_data['idfa']:
        fingerprint_base['idfa'] = device_data['idfa']
    elif 'idfv' in device_data and device_data['idfv']:
        fingerprint_base['idfv'] = device_data['idfv']
    
    # 创建哈希
    fingerprint_string = json.dumps(fingerprint_base, sort_keys=True)
    return hashlib.sha256(fingerprint_string.encode()).hexdigest()
步骤2: 点击事件处理

当用户点击深度链接但尚未安装应用时:

def store_click_event(link_id, device_data, campaign_id=None):
    # 生成设备指纹
    fingerprint_hash = generate_device_fingerprint(device_data)
    
    # 设置过期时间(例如7天后)
    expiry = datetime.now() + timedelta(days=7)
    
    # 存储点击事件
    click_id = str(uuid.uuid4())
    db.execute("""
        INSERT INTO click_events 
        (id, click_timestamp, link_id, campaign_id, ip_address, 
         user_agent, device_type, os_version, browser, device_fingerprint_hash, 
         additional_data, expiry_timestamp)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
    """, (
        click_id, datetime.now(), link_id, campaign_id, 
        device_data.get('ip_address'), device_data.get('user_agent'),
        device_data.get('device_type'), device_data.get('os_version'),
        device_data.get('browser'), fingerprint_hash,
        json.dumps(device_data), expiry
    ))
    
    return click_id
步骤3: 安装匹配算法

当用户安装并首次打开应用时:

def match_install_with_click(install_data):
    # 生成安装设备的指纹
    install_fingerprint = generate_device_fingerprint(install_data)
    
    # 存储安装事件
    install_id = str(uuid.uuid4())
    db.execute("""
        INSERT INTO install_events
        (id, install_timestamp, device_id, device_id_type, ip_address,
         user_agent, device_model, os_name, os_version, language,
         country, device_fingerprint_hash, app_version)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
    """, (
        install_id, datetime.now(), 
        install_data.get('device_id'), install_data.get('device_id_type'),
        install_data.get('ip_address'), install_data.get('user_agent'),
        install_data.get('device_model'), install_data.get('os_name'),
        install_data.get('os_version'), install_data.get('language'),
        install_data.get('country'), install_fingerprint,
        install_data.get('app_version')
    ))
    
    # 查找匹配的点击事件
    potential_matches = find_potential_matches(install_data, install_fingerprint)
    
    # 如果找到匹配
    if potential_matches:
        best_match = select_best_match(potential_matches, install_data)
        if best_match:
            # 更新安装事件与匹配的点击关联
            db.execute("""
                UPDATE install_events
                SET matched_click_id = %s, match_confidence = %s
                WHERE id = %s
            """, (best_match['click_id'], best_match['confidence'], install_id))
            
            # 获取关联的深度链接数据
            link_data = get_link_data(best_match['link_id'])
            return link_data
    
    return None

5. 浏览器点击与iOS应用安装匹配

当用户在浏览器中点击链接,然后安装iOS应用时,没有IDFA/IDFV可供匹配。这种情况下,Branch SDK和类似服务仍然能够实现匹配,主要依靠以下技术手段:

5.1 概率匹配技术

没有IDFA/IDFV时,服务端会使用"概率匹配"技术,通过多种信号组合来确定用户身份:

关键匹配信号
  1. IP地址匹配

    • 浏览器点击和应用安装的IP地址比较
    • 考虑到移动网络IP可能变化,可能会查看IP地址段或运营商信息
  2. 时间接近度

    • 点击事件与安装事件的时间间隔
    • 通常时间越接近,匹配置信度越高
  3. 设备特征

    • 浏览器的User-Agent与应用报告的设备型号/OS版本比较
    • 浏览器中可获取的屏幕分辨率与应用报告的屏幕信息比较
  4. 地理位置

    • 浏览器中获取的地理位置(如果用户允许)与应用报告的位置比较
    • 即使是粗略的地理位置(城市级别)也有助于提高匹配准确性
  5. 参考来源(Referrer)

    • 点击前的来源网站
    • 广告活动标识符

5.2 浏览器指纹技术

现代浏览器指纹技术可以创建相对唯一的设备标识:

// 浏览器端收集的指纹信息示例
function collectBrowserFingerprint() {
    return {
        userAgent: navigator.userAgent,
        language: navigator.language,
        screenResolution: `${screen.width}x${screen.height}`,
        screenColorDepth: screen.colorDepth,
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        platformVersion: navigator.platform,
        doNotTrack: navigator.doNotTrack,
        cookiesEnabled: navigator.cookieEnabled,
        canvasFingerprint: generateCanvasFingerprint(),
        webglFingerprint: generateWebGLFingerprint(),
        installedFonts: detectInstalledFonts(),
        installedPlugins: Array.from(navigator.plugins).map(p => p.name),
        touchSupport: 'ontouchstart' in window
    };
}

5.3 服务端匹配算法优化

当没有IDFA/IDFV时,匹配算法需要特别优化:

def match_browser_click_to_ios_install(browser_click_data, ios_install_data):
    # 初始化匹配分数
    match_score = 0
    max_score = 100
    
    # 1. IP地址匹配 (高权重)
    if browser_click_data.get('ip_address') == ios_install_data.get('ip_address'):
        match_score += 35
    else:
        # 检查IP地址段匹配 (前两个八位组)
        browser_ip_parts = browser_click_data.get('ip_address', '').split('.')
        ios_ip_parts = ios_install_data.get('ip_address', '').split('.')
        
        if len(browser_ip_parts) >= 2 and len(ios_ip_parts) >= 2:
            if browser_ip_parts[0] == ios_ip_parts[0] and browser_ip_parts[1] == ios_ip_parts[1]:
                match_score += 25  # 部分IP匹配
    
    # 2. 时间接近度 (高权重)
    click_time = datetime.fromisoformat(browser_click_data.get('timestamp'))
    install_time = datetime.fromisoformat(ios_install_data.get('install_time'))
    time_diff_hours = (install_time - click_time).total_seconds() / 3600
    
    # 1小时内满分,24小时内线性降低
    if time_diff_hours <= 1:
        match_score += 30
    elif time_diff_hours <= 24:
        match_score += 30 * (1 - (time_diff_hours - 1) / 23)
    
    # 3. 设备特征匹配
    # 从User-Agent提取设备信息
    browser_device_info = extract_device_info_from_user_agent(browser_click_data.get('user_agent', ''))
    
    # 操作系统匹配
    if browser_device_info.get('os') == 'iOS' and browser_device_info.get('os_version') == ios_install_data.get('os_version'):
        match_score += 15
    
    # 设备型号匹配 (如果UA包含)
    if browser_device_info.get('model') and browser_device_info.get('model') == ios_install_data.get('device_model'):
        match_score += 10
    
    # 4. 地理位置匹配
    if browser_click_data.get('geo_city') == ios_install_data.get('geo_city'):
        match_score += 10
    
    # 计算匹配置信度 (0-1)
    confidence = match_score / max_score
    
    return {
        'is_match': confidence >= 0.7,  # 设置阈值
        'confidence': confidence,
        'match_signals': {
            'ip_match': browser_click_data.get('ip_address') == ios_install_data.get('ip_address'),
            'time_diff_hours': time_diff_hours,
            'os_match': browser_device_info.get('os_version') == ios_install_data.get('os_version'),
            'geo_match': browser_click_data.get('geo_city') == ios_install_data.get('geo_city')
        }
    }

5.4 增强匹配的技术手段

Cookie到应用的桥接

一些服务使用临时标识符在浏览器和应用之间建立桥接:

// 浏览器端
function generateClickIdentifier() {
    const clickId = generateUUID();
    localStorage.setItem('temp_click_id', clickId);
    
    // 添加到目标URL
    const targetUrl = `https://app.store.com/app?click_id=${clickId}`;
    return targetUrl;
}
// iOS应用端
- (void)checkLaunchOptions:(NSDictionary *)launchOptions {
    NSURL *url = launchOptions[UIApplicationLaunchOptionsURLKey];
    if (url) {
        NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
        NSArray *queryItems = components.queryItems;
        
        for (NSURLQueryItem *item in queryItems) {
            if ([item.name isEqualToString:@"click_id"]) {
                // 找到了点击标识符
                [self reportInstallWithClickId:item.value];
                break;
            }
        }
    }
}
引用参数传递

通过App Store的引荐机制传递参数:

// 生成带引荐参数的App Store链接
function generateAppStoreLink(campaignId, clickId) {
    return `https://apps.apple.com/app/id123456789?ct=${campaignId}&pt=${clickId}`;
}
  • Cookie桥接​​:仅适用于已安装场景,需配合剪贴板或设备指纹弥补局限。
  • ​App Store参数化链接​​:​​可生成但参数会被丢弃​​,必须通过​​设备指纹/SDK/剪贴板​​实现安装后归因。

5.5 匹配准确性评估

实际应用中,浏览器到iOS应用的匹配准确性通常在60%-80%之间,取决于多种因素:

影响匹配准确性的因素
  1. 时间间隔:点击到安装的时间越短,匹配准确性越高
  2. 网络环境:用户在相同网络环境下点击和安装,准确性更高
  3. 设备变化:如果用户在一个设备上点击,在另一设备上安装,准确性大幅降低
  4. IP稳定性:移动网络IP变化频繁,降低匹配准确性
  5. 用户量:高流量应用面临更多匹配挑战,因为可能有多个相似用户同时安装

6. 总结

Branch SDK是一个功能强大的移动应用归因和深度链接解决方案,其核心价值在于提供了一套完整的系统,使开发者能够实现跨平台的用户识别、深度链接和归因分析,同时适应不断变化的移动平台隐私政策。

延迟深度链接是Branch SDK的核心特性之一,即使在没有精确标识符(如IDFA/IDFV)的情况下,也能通过多信号概率匹配、浏览器指纹技术、参数传递等方式实现相对可靠的延迟深度链接功能。

自行实现类似功能需要考虑设备指纹构建、服务端存储结构、匹配算法实现、多阶段匹配策略、安全性和隐私保护等多个方面,并且需要不断优化匹配算法以提高准确性。