Branch SDK 分析和自建归因
1. Branch SDK的主要目的
Branch SDK是一个移动应用归因和深度链接解决方案,主要实现以下功能:
- 深度链接(Deep Linking):创建可以直接将用户引导到应用内特定内容的链接
- 归因分析(Attribution):跟踪用户安装来源和用户行为
- 跨平台用户识别:在不同设备和平台上识别用户
- 应用内容索引:使应用内容可被搜索引擎索引
- 用户分享和邀请:提供应用内分享功能和邀请追踪
2. 实现方式
2.1 核心组件
- Branch类:SDK的主入口,提供初始化和主要功能接口
- BNCPreferenceHelper:管理SDK的配置和持久化数据
- BNCServerInterface/BNCServerRequest:处理与Branch服务器的通信
- BNCDeviceInfo:收集设备信息用于用户识别
- 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 设备识别
通过BNCDeviceInfo和BNCSystemObserver收集设备信息,用于用户识别:
- 设备ID (IDFA或供应商ID)
- 操作系统和版本
- 设备型号
- 安装信息
2.2.4 数据存储
使用BNCPreferenceHelper管理数据持久化:
- 会话信息
- 用户标识
- 安装参数
- 设备令牌
2.2.5 网络通信
通过BNCServerInterface和BNCNetworkService与Branch服务器通信:
- 初始化会话请求
- 创建短链接请求
- 事件追踪请求
- 用户识别请求
2.2.6 内容分享
使用BranchUniversalObject和BranchLinkProperties创建可分享内容:
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 技术特点
- 单例模式:大量使用单例模式管理SDK实例和服务
- 类别扩展:使用Objective-C类别扩展标准类功能
- 异步操作:网络请求和数据处理采用异步方式
- 队列管理:使用操作队列管理请求顺序和并发
- 延迟加载:使用懒加载模式提高性能
- 隐私保护:适应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 实现原理
-
用户标识匹配:
- 当用户点击Branch链接但尚未安装应用时,Branch服务器会记录用户的设备指纹信息
- 用户安装应用后,SDK会收集设备信息并发送到服务器
- 服务器通过匹配设备指纹确定这是同一用户
-
首次打开处理:
- 应用首次启动时,
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 延迟深度链接的关键组件
-
BranchOpenRequest:
- 处理应用启动时的服务器通信
- 接收服务器返回的延迟深度链接数据
-
BNCPreferenceHelper:
- 存储安装参数(
installParams) - 当识别到延迟深度链接时,这些参数会被填充
- 存储安装参数(
-
设备指纹识别:
BNCDeviceInfo收集设备信息用于匹配- 包括IDFA、供应商ID、IP地址等多个因素
-
回调机制:
- 通过
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时,服务端会使用"概率匹配"技术,通过多种信号组合来确定用户身份:
关键匹配信号
-
IP地址匹配
- 浏览器点击和应用安装的IP地址比较
- 考虑到移动网络IP可能变化,可能会查看IP地址段或运营商信息
-
时间接近度
- 点击事件与安装事件的时间间隔
- 通常时间越接近,匹配置信度越高
-
设备特征
- 浏览器的User-Agent与应用报告的设备型号/OS版本比较
- 浏览器中可获取的屏幕分辨率与应用报告的屏幕信息比较
-
地理位置
- 浏览器中获取的地理位置(如果用户允许)与应用报告的位置比较
- 即使是粗略的地理位置(城市级别)也有助于提高匹配准确性
-
参考来源(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%之间,取决于多种因素:
影响匹配准确性的因素
- 时间间隔:点击到安装的时间越短,匹配准确性越高
- 网络环境:用户在相同网络环境下点击和安装,准确性更高
- 设备变化:如果用户在一个设备上点击,在另一设备上安装,准确性大幅降低
- IP稳定性:移动网络IP变化频繁,降低匹配准确性
- 用户量:高流量应用面临更多匹配挑战,因为可能有多个相似用户同时安装
6. 总结
Branch SDK是一个功能强大的移动应用归因和深度链接解决方案,其核心价值在于提供了一套完整的系统,使开发者能够实现跨平台的用户识别、深度链接和归因分析,同时适应不断变化的移动平台隐私政策。
延迟深度链接是Branch SDK的核心特性之一,即使在没有精确标识符(如IDFA/IDFV)的情况下,也能通过多信号概率匹配、浏览器指纹技术、参数传递等方式实现相对可靠的延迟深度链接功能。
自行实现类似功能需要考虑设备指纹构建、服务端存储结构、匹配算法实现、多阶段匹配策略、安全性和隐私保护等多个方面,并且需要不断优化匹配算法以提高准确性。