iOS全埋点方案中,有哪些时间追踪功能?

2026-05-25 19:361阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3264个文字,预计阅读时间需要14分钟。

iOS全埋点方案中,有哪些时间追踪功能?

前言:我们使用事件模型(Event Model)来描述用户的各种行为。事件模型包括事件(Event)和用户(User)两个核心实体。

在描述用户行为时,我们重点关注以下几个要点:

前言

​ 我们使用“事件模型( Event 模型)”来描述用户的各种行为,事件模型包括事件( Event )和用户( User )两个核心实体。我们在描述用户行为时,往往只需要描述清楚几个要点,即可将整个行为描述清楚,要点

包括:是谁、什么时间、什么地点、以什么方式、干了什么。而事件( Event )和用户( User )这两个实体结合在一起就可以达到这一目的。

Event 实体

一个完整的事件( Event ),包含如下的几个关键因素:

Who:即参与这个事件的用户是谁。

When:即这个事件发生的实际时间。

Where:即事件发生的地点。

How:即用户从事这个事件的方式。这个概念就比较广了,包括用户使用的设备、使用的浏览器、使用的 App 版本、操作系统版本、进入的渠道、跳转过来时的 referer 等,目前,神策分析预置了如下字段用来描述这类信息,使用者也可以根据自己的需要来增加相应的自定义字段。

What:以字段的方式记录用户所做的事件的具体内容。

$app_version:应用版本 $city:城市 $manufacturer:设备制造商,字符串类型,如"Apple" $model:设备型号,字符串类型,如"iphone6" $os:操作系统,字符串类型,如"iOS" $os_version:操作系统版本,字符串类型,如"8.1.1" $screen_height:屏幕高度,数字类型,如 1920 $screen_width:屏幕宽度,数字类型,如 1080 $wifi:是否 WIFI,BOOL 类型,如 true User 实体

​ 每个 User 实体对应一个真实的用户,每个用户有各种属性,常见的属性例如:年龄、性别,和业务相关的属性则可能有:会员等级、当前积分、好友数等等。这些描述用户的字段,就是用户属性。

​ 接下来我们主要说的 When 这个因素,即时间。包括事件发生的时间戳和统计事件持续的时长。

一、事件发生的时间戳

​ 时间纠正:如果 T2 和 T3 相差太大,我们可以确定当前用户手机的时间戳是不准确的,及比服务器的时间晚了一个小时,因此,我们认为事件发生的时间 T1 也晚了一个小时,这就达到时间纠正的效果。

二、统计事件持续时长

​ 事件持续时长,是用来统计用户的某个行为或者动作持续了多次事件(比如,观看了某个视频)的。统计事件持续时长,就像一个计时器,当用户的某个行为或者动作发生时,就开始计时;当行为或者动作结束时就停止计时,这个 事件间隔(在事件中,我们用 $event_duration 来表示 )为用户发生这个行为或者动作的持续时长。

2.1 实现步骤

​ 为了方便统计时长,我们需要新增两个方法:

开始计时:- trackTimerStart:

停止计时:-trackTimerEnd:properties:

​ 当某个行为或者活动开始时,调用 - trackTimerStart:开始计时,此时并不会触发事件,仅仅是 SDK 内部记录耨个事件的开始的时间戳。当这个行为或者活动结束时,调用 -trackTimerEnd:properties:结束计时器,然后 SDK 计算持续时长 $event_duration 属性的值并触发事件。

实现步骤:

第一步:新增 SensorsAnalyticsSDK 文件的类别 Timer ,并新增 - trackTimerStart: 和 - trackTimerEnd: properties: 方法的声明

iOS全埋点方案中,有哪些时间追踪功能?

#import <SensorsSDK/SensorsSDK.h> NS_ASSUME_NONNULL_BEGIN @interface SensorsAnalyticsSDK (Timer) /// 开始统计事件时长 /// @param event 事件名 - (void)trackTimerStart:(NSString *)event; /// 结束事件时长统计,计算时长 /// @param event 事件名 与开始时事件名一一对应 /// @param properties 事件属性 - (void)trackTimerEnd:(NSString *)event properties:(nullable NSDictionary *) properties; @end NS_ASSUME_NONNULL_END

第二步:在 SensorsAnalyticsSDK 中新增 trackTimer 属性,用于记录事件开始发生的时间戳,并在 - init 方法中进行初始化。

/// 事件开始发生的时间戳 @property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *trackTimer; - (instancetype)init { self = [super init]; if (self) { _automaticProperties = [self collectAutomaticProperties]; // 设置是否需是被动启动标记 _launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever; _loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId]; _trackTimer = [NSMutableDictionary dictionary]; // 添加应用程序状态监听 [self setupListeners]; } return self; }

第三步:在 SensorsAnalyticsSDK 文件中新增 + currentTime 方法,用于获取用户当期的时间戳。

// 获取手机当前时间戳 + (double)currentTime { return [[NSDate date] timeIntervalSince1970] * 1000; }

第四步:在 SensorsAnalyticsSDK+Timer 类别中实现 - trackTimerStart: 和 - trackTimerEnd: properties: 方法

#import "SensorsAnalyticsSDK+Timer.h" static NSString * const SensorsAnalyticsEventBeginKey = @"event_begin"; @implementation SensorsAnalyticsSDK (Timer) - (void)trackTimerStart:(NSString *)event { self.trackTimer[event] = @{SensorsAnalyticsEventBeginKey: @([SensorsAnalyticsSDK currentTime])}; } - (void)trackTimerEnd:(NSString *)event properties:(NSDictionary *)properties { NSDictionary *evnetTimer = self.trackTimer[event]; if (!evnetTimer) { return [self track:event properties:properties]; } NSMutableDictionary *p = [NSMutableDictionary dictionaryWithDictionary:properties]; // 移除 [self.trackTimer removeObjectForKey:event]; // 事件开始时间 double beginTime = [(NSNumber *)evnetTimer[SensorsAnalyticsEventBeginKey] doubleValue]; // 获取当前系统事件 double currentTime = [SensorsAnalyticsSDK currentTime]; // 计算事件时长 double eventDuration = currentTime - beginTime; eventDuration = [[NSString stringWithFormat:@"%.3lf", eventDuration] floatValue]; // 设置事件时长属性 [p setObject:@(eventDuration) forKey:@"$event_duration"]; // 触发事件 [self track:event properties:p]; } @end

第五步:测试验证

[[SensorsAnalyticsSDK sharedInstance] trackTimerStart:@"doSomething"]; [[SensorsAnalyticsSDK sharedInstance] trackTimerEnd:@"doSomething" properties:nil];

{ "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$event_duration" : 2046.623046875, "$app_version" : "1.0", "$os_version" : "15.2", "$lib" : "iOS" }, "event" : "doSomething", "time" : 1649382527225, "distinct_id" : "1234567" }

可能存在问题: 如果调用 - trackTimerStart: 和 - trackTimerEnd: properties: 方法之间用户调整了手机时间,可能会出现下面的问题

  • 统计的 $event_duration 可能接近于0
  • 统计的 $event_duration 可能非常大,可能超过一个月
  • 统计的 $event_duration 可能为负数

​ 这是因为我们目前是借助手机客户端的时间来计算 $event_duration 的,一旦用户调整了手机的时间,必然会影响 $event_duration 属性的计算。

解决方法:引入 systemUpTime(系统启动事件,也叫开机时间), 指设备开机后一共运行了多少秒(设备休眠不同统计在内),并且不会受到系统时间更改影响。我们可以使用 systemUpTime 来计算 $event_duration 属性。

​ 在 SensorsAnalyticsSDK 文件中新增 + systemUpTime 方法

// 系统启动时间 + (double)systemUpTime { return NSProcessInfo.processInfo.systemUptime * 1000; }

​ 将 SensorsAnalyticsSDK+Timer 中 调用 + currentTime 改成 + systemUpTime 方法。至此就解决了事件持续时长统计不准确的问题。

2.2 事件的暂停和恢复

​ 引入事件暂停和恢复的方法:

暂停统计时长方法:- trackTimerPause:

恢复统计时长方法:- trackTimerResume:

实现步骤:

第一步:在 SensorsAnalyticsSDK+Timer 文件中新增 - trackTimerPause: - trackTimerResume: 方法

@interface SensorsAnalyticsSDK (Timer) /// 暂停事件统计时长 /// @param event 事件名 - (void)trackTimerPause:(NSString *)event; /// 恢复事件统计时长 /// @param event 事件名 - (void)trackTimerResume:(NSString *)event; @end

static NSString * const SensorsAnalyticsEventDurationKey = @"event_duration"; static NSString * const SensorsAnalyticsEventIsPauseKey = @"is_pause"; - (void)trackTimerPause:(NSString *)event { NSMutableDictionary *eventTimer = [self.trackTimer[event] mutableCopy]; // 如果没有开始,直接返回 if (!eventTimer) { return; } // 如果该事件时长统计已经结束,直接返回,不做任何处理 if ([eventTimer[SensorsAnalyticsEventIsPauseKey] boolValue]) { return; } // 获取当前系统启动时间 double systemUpTime = [SensorsAnalyticsSDK systemUpTime]; // 获取事件开始时间 double beginTime = [eventTimer[SensorsAnalyticsEventBeginKey] doubleValue]; // 计算暂停前统计的时长 double duration = [eventTimer[SensorsAnalyticsEventDurationKey] doubleValue] + systemUpTime - beginTime; eventTimer[SensorsAnalyticsEventDurationKey] = @(duration); // 事件处于暂停状态 eventTimer[SensorsAnalyticsEventIsPauseKey] = @(YES); self.trackTimer[event] = eventTimer; } - (void)trackTimerResume:(NSString *)event { NSMutableDictionary *eventTimer = [self.trackTimer[event] mutableCopy]; // 如果没有开始,直接返回 if (!eventTimer) { return; } // 如果该事件时长统计没有暂停,直接返回,不做任何处理 if ([eventTimer[SensorsAnalyticsEventIsPauseKey] boolValue]) { return; } // 获取当前系统启动时间 double systemUpTime = [SensorsAnalyticsSDK systemUpTime]; // 重置事件开始事件 eventTimer[SensorsAnalyticsEventBeginKey] = @(systemUpTime); // 将事件暂停被标记设置为 NO eventTimer[SensorsAnalyticsEventIsPauseKey] = @(NO); self.trackTimer[event] = eventTimer; }

第二步:修改 - trackTimerEnd: properties: 方法

- (void)trackTimerEnd:(NSString *)event properties:(NSDictionary *)properties { NSDictionary *eventTimer = self.trackTimer[event]; if (!eventTimer) { return [self track:event properties:properties]; } NSMutableDictionary *p = [NSMutableDictionary dictionaryWithDictionary:properties]; // 移除 [self.trackTimer removeObjectForKey:event]; if ([eventTimer[SensorsAnalyticsEventIsPauseKey] boolValue]) { // 获取事件时长 double eventDuration = [eventTimer[SensorsAnalyticsEventDurationKey] doubleValue]; // 设置事件时长属性 p[@"$event_duration"] = @([[NSString stringWithFormat:@"%.3lf", eventDuration] floatValue]); } else { // 事件开始时间 double beginTime = [(NSNumber *)eventTimer[SensorsAnalyticsEventBeginKey] doubleValue]; // 获取当前系统事件 double currentTime = [SensorsAnalyticsSDK systemUpTime]; // 计算事件时长 double eventDuration = currentTime - beginTime + [eventTimer[SensorsAnalyticsEventDurationKey] doubleValue]; eventDuration = [[NSString stringWithFormat:@"%.3lf", eventDuration] floatValue]; // 设置事件时长属性 [p setObject:@(eventDuration) forKey:@"$event_duration"]; } // 触发事件 [self track:event properties:p]; }

第三步:测试验证

{ "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$event_duration" : 1663.958984375, "$app_version" : "1.0", "$os_version" : "15.2", "$lib" : "iOS" }, "event" : "doSomething", "time" : 1649398840807, "distinct_id" : "1234567" } 2.3 后台状态下的事件时长

​ 以上问题:当应用程序进入后台后,由于我们是通过记录事件开始时间,然后在事件结束时,计算时间差来计算事件的持续时长 ,包括了进入后台的时间。因为在应用程序进入后台时,我们应该调用暂停的方法,当应用程序回到前台运行时,我们调用恢复事件方法。

​ 实现步骤:

第一步:在 SensorsAnalyticsSDK 文件中新增一个属性 enterBackgroundTrackTimerEvents 用来保存进入后台时未暂停的事件名。然后在 -init 方法中进行初始化

/// 保存进入后台时未暂停的事件名称 @property (nonatomic, strong) NSMutableArray<NSString *> *enterBackgroundTrackTimerEvents; - (instancetype)init { self = [super init]; if (self) { _automaticProperties = [self collectAutomaticProperties]; // 设置是否需是被动启动标记 _launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever; _loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId]; _trackTimer = [NSMutableDictionary dictionary]; _enterBackgroundTrackTimerEvents = [NSMutableArray array]; // 添加应用程序状态监听 [self setupListeners]; } return self; }

第二步:在应用程序进入后台时,调用暂停方法,将所有未暂停的事件暂停

- (void)applicationDidEnterBackground:(NSNotification *)notification { NSLog(@"Application did enter background."); // 还原标记位 self.applicationWillResignActive = NO; // 触发 AppEnd 事件 [self track:@"$AppEnd" properties:nil]; // 暂停所有事件时长统计 [self.trackTimer enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) { if (![obj[@"is_pause"] boolValue]) { [self.enterBackgroundTrackTimerEvents addObject:key]; [self trackTimerPause:key]; } }]; }

第三步:在应用程序进入前台的时候,调用事件恢复启动

- (void)applicationDidBecomeActive:(NSNotification *)notification { NSLog(@"Application did enter active."); // 还原标记位 if (self.applicationWillResignActive) { self.applicationWillResignActive = NO; return; } // 将被动启动标记位设置为 NO,正常记录事件 self.launchedPassively = NO; // 触发 AppStart 事件 [self track:@"$AppStart" properties:nil]; // 恢复所有的事件时长统计 for (NSString *event in self.enterBackgroundTrackTimerEvents) { [self trackTimerStart:event]; } [self.enterBackgroundTrackTimerEvents removeAllObjects]; }

第四步:测试运行

三、全埋点事件时长 3.1 $AppEnd 事件时长

​ 当收到 UIApplicationDidBecomeActiveNotification 本地通知时,调用 - trackTimerStart:方法开始计时,当收到 UIApplicationDidEnterBackgroundNotification 本地通知时,调用 - track: properties: 方法结束计时。

实现步骤:

第一步:修改 - applicationDidBecomeActive:方法,在结束时调用 - trackTimerStart:方法

- (void)applicationDidBecomeActive:(NSNotification *)notification { NSLog(@"Application did enter active."); // 还原标记位 if (self.applicationWillResignActive) { self.applicationWillResignActive = NO; return; } // 将被动启动标记位设置为 NO,正常记录事件 self.launchedPassively = NO; // 触发 AppStart 事件 [self track:@"$AppStart" properties:nil]; // 恢复所有的事件时长统计 for (NSString *event in self.enterBackgroundTrackTimerEvents) { [self trackTimerStart:event]; } [self.enterBackgroundTrackTimerEvents removeAllObjects]; // 开始 $AppEnd 事件计时 [self trackTimerStart:@"$AppEnd"]; }

第二步:修改 - applicationDidEnterBackground: 方法,将 [self track:@"$AppEnd" properties:nil]; 修改成 [self trackTimerEnd:@"$AppEnd" properties:nil];

- (void)applicationDidEnterBackground:(NSNotification *)notification { NSLog(@"Application did enter background."); // 还原标记位 self.applicationWillResignActive = NO; // 触发 AppEnd 事件 // [self track:@"$AppEnd" properties:nil]; [self trackTimerEnd:@"$AppEnd" properties:nil]; // 暂停所有事件时长统计 [self.trackTimer enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) { if (![obj[@"is_pause"] boolValue]) { [self.enterBackgroundTrackTimerEvents addObject:key]; [self trackTimerPause:key]; } }]; }

第三步:测试验证

{ "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$event_duration" : 16705.58984375, "$app_version" : "1.0", "$os_version" : "15.2", "$lib" : "iOS" }, "event" : "$AppEnd", "time" : 1649402996456, "distinct_id" : "1234567" } 3.2 $AppViewScreen 时间时长

​ 如果按照 $AppEnd 方式实现 $AppViewScreen 时间时长,可能会存在 2 个问题:

  • 如何计算最好一个页面的界面预览事件时长
  • 如何处理嵌套子页面的预览事件时长

具体实现:后续介绍

本文共计3264个文字,预计阅读时间需要14分钟。

iOS全埋点方案中,有哪些时间追踪功能?

前言:我们使用事件模型(Event Model)来描述用户的各种行为。事件模型包括事件(Event)和用户(User)两个核心实体。

在描述用户行为时,我们重点关注以下几个要点:

前言

​ 我们使用“事件模型( Event 模型)”来描述用户的各种行为,事件模型包括事件( Event )和用户( User )两个核心实体。我们在描述用户行为时,往往只需要描述清楚几个要点,即可将整个行为描述清楚,要点

包括:是谁、什么时间、什么地点、以什么方式、干了什么。而事件( Event )和用户( User )这两个实体结合在一起就可以达到这一目的。

Event 实体

一个完整的事件( Event ),包含如下的几个关键因素:

Who:即参与这个事件的用户是谁。

When:即这个事件发生的实际时间。

Where:即事件发生的地点。

How:即用户从事这个事件的方式。这个概念就比较广了,包括用户使用的设备、使用的浏览器、使用的 App 版本、操作系统版本、进入的渠道、跳转过来时的 referer 等,目前,神策分析预置了如下字段用来描述这类信息,使用者也可以根据自己的需要来增加相应的自定义字段。

What:以字段的方式记录用户所做的事件的具体内容。

$app_version:应用版本 $city:城市 $manufacturer:设备制造商,字符串类型,如"Apple" $model:设备型号,字符串类型,如"iphone6" $os:操作系统,字符串类型,如"iOS" $os_version:操作系统版本,字符串类型,如"8.1.1" $screen_height:屏幕高度,数字类型,如 1920 $screen_width:屏幕宽度,数字类型,如 1080 $wifi:是否 WIFI,BOOL 类型,如 true User 实体

​ 每个 User 实体对应一个真实的用户,每个用户有各种属性,常见的属性例如:年龄、性别,和业务相关的属性则可能有:会员等级、当前积分、好友数等等。这些描述用户的字段,就是用户属性。

​ 接下来我们主要说的 When 这个因素,即时间。包括事件发生的时间戳和统计事件持续的时长。

一、事件发生的时间戳

​ 时间纠正:如果 T2 和 T3 相差太大,我们可以确定当前用户手机的时间戳是不准确的,及比服务器的时间晚了一个小时,因此,我们认为事件发生的时间 T1 也晚了一个小时,这就达到时间纠正的效果。

二、统计事件持续时长

​ 事件持续时长,是用来统计用户的某个行为或者动作持续了多次事件(比如,观看了某个视频)的。统计事件持续时长,就像一个计时器,当用户的某个行为或者动作发生时,就开始计时;当行为或者动作结束时就停止计时,这个 事件间隔(在事件中,我们用 $event_duration 来表示 )为用户发生这个行为或者动作的持续时长。

2.1 实现步骤

​ 为了方便统计时长,我们需要新增两个方法:

开始计时:- trackTimerStart:

停止计时:-trackTimerEnd:properties:

​ 当某个行为或者活动开始时,调用 - trackTimerStart:开始计时,此时并不会触发事件,仅仅是 SDK 内部记录耨个事件的开始的时间戳。当这个行为或者活动结束时,调用 -trackTimerEnd:properties:结束计时器,然后 SDK 计算持续时长 $event_duration 属性的值并触发事件。

实现步骤:

第一步:新增 SensorsAnalyticsSDK 文件的类别 Timer ,并新增 - trackTimerStart: 和 - trackTimerEnd: properties: 方法的声明

iOS全埋点方案中,有哪些时间追踪功能?

#import <SensorsSDK/SensorsSDK.h> NS_ASSUME_NONNULL_BEGIN @interface SensorsAnalyticsSDK (Timer) /// 开始统计事件时长 /// @param event 事件名 - (void)trackTimerStart:(NSString *)event; /// 结束事件时长统计,计算时长 /// @param event 事件名 与开始时事件名一一对应 /// @param properties 事件属性 - (void)trackTimerEnd:(NSString *)event properties:(nullable NSDictionary *) properties; @end NS_ASSUME_NONNULL_END

第二步:在 SensorsAnalyticsSDK 中新增 trackTimer 属性,用于记录事件开始发生的时间戳,并在 - init 方法中进行初始化。

/// 事件开始发生的时间戳 @property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *trackTimer; - (instancetype)init { self = [super init]; if (self) { _automaticProperties = [self collectAutomaticProperties]; // 设置是否需是被动启动标记 _launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever; _loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId]; _trackTimer = [NSMutableDictionary dictionary]; // 添加应用程序状态监听 [self setupListeners]; } return self; }

第三步:在 SensorsAnalyticsSDK 文件中新增 + currentTime 方法,用于获取用户当期的时间戳。

// 获取手机当前时间戳 + (double)currentTime { return [[NSDate date] timeIntervalSince1970] * 1000; }

第四步:在 SensorsAnalyticsSDK+Timer 类别中实现 - trackTimerStart: 和 - trackTimerEnd: properties: 方法

#import "SensorsAnalyticsSDK+Timer.h" static NSString * const SensorsAnalyticsEventBeginKey = @"event_begin"; @implementation SensorsAnalyticsSDK (Timer) - (void)trackTimerStart:(NSString *)event { self.trackTimer[event] = @{SensorsAnalyticsEventBeginKey: @([SensorsAnalyticsSDK currentTime])}; } - (void)trackTimerEnd:(NSString *)event properties:(NSDictionary *)properties { NSDictionary *evnetTimer = self.trackTimer[event]; if (!evnetTimer) { return [self track:event properties:properties]; } NSMutableDictionary *p = [NSMutableDictionary dictionaryWithDictionary:properties]; // 移除 [self.trackTimer removeObjectForKey:event]; // 事件开始时间 double beginTime = [(NSNumber *)evnetTimer[SensorsAnalyticsEventBeginKey] doubleValue]; // 获取当前系统事件 double currentTime = [SensorsAnalyticsSDK currentTime]; // 计算事件时长 double eventDuration = currentTime - beginTime; eventDuration = [[NSString stringWithFormat:@"%.3lf", eventDuration] floatValue]; // 设置事件时长属性 [p setObject:@(eventDuration) forKey:@"$event_duration"]; // 触发事件 [self track:event properties:p]; } @end

第五步:测试验证

[[SensorsAnalyticsSDK sharedInstance] trackTimerStart:@"doSomething"]; [[SensorsAnalyticsSDK sharedInstance] trackTimerEnd:@"doSomething" properties:nil];

{ "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$event_duration" : 2046.623046875, "$app_version" : "1.0", "$os_version" : "15.2", "$lib" : "iOS" }, "event" : "doSomething", "time" : 1649382527225, "distinct_id" : "1234567" }

可能存在问题: 如果调用 - trackTimerStart: 和 - trackTimerEnd: properties: 方法之间用户调整了手机时间,可能会出现下面的问题

  • 统计的 $event_duration 可能接近于0
  • 统计的 $event_duration 可能非常大,可能超过一个月
  • 统计的 $event_duration 可能为负数

​ 这是因为我们目前是借助手机客户端的时间来计算 $event_duration 的,一旦用户调整了手机的时间,必然会影响 $event_duration 属性的计算。

解决方法:引入 systemUpTime(系统启动事件,也叫开机时间), 指设备开机后一共运行了多少秒(设备休眠不同统计在内),并且不会受到系统时间更改影响。我们可以使用 systemUpTime 来计算 $event_duration 属性。

​ 在 SensorsAnalyticsSDK 文件中新增 + systemUpTime 方法

// 系统启动时间 + (double)systemUpTime { return NSProcessInfo.processInfo.systemUptime * 1000; }

​ 将 SensorsAnalyticsSDK+Timer 中 调用 + currentTime 改成 + systemUpTime 方法。至此就解决了事件持续时长统计不准确的问题。

2.2 事件的暂停和恢复

​ 引入事件暂停和恢复的方法:

暂停统计时长方法:- trackTimerPause:

恢复统计时长方法:- trackTimerResume:

实现步骤:

第一步:在 SensorsAnalyticsSDK+Timer 文件中新增 - trackTimerPause: - trackTimerResume: 方法

@interface SensorsAnalyticsSDK (Timer) /// 暂停事件统计时长 /// @param event 事件名 - (void)trackTimerPause:(NSString *)event; /// 恢复事件统计时长 /// @param event 事件名 - (void)trackTimerResume:(NSString *)event; @end

static NSString * const SensorsAnalyticsEventDurationKey = @"event_duration"; static NSString * const SensorsAnalyticsEventIsPauseKey = @"is_pause"; - (void)trackTimerPause:(NSString *)event { NSMutableDictionary *eventTimer = [self.trackTimer[event] mutableCopy]; // 如果没有开始,直接返回 if (!eventTimer) { return; } // 如果该事件时长统计已经结束,直接返回,不做任何处理 if ([eventTimer[SensorsAnalyticsEventIsPauseKey] boolValue]) { return; } // 获取当前系统启动时间 double systemUpTime = [SensorsAnalyticsSDK systemUpTime]; // 获取事件开始时间 double beginTime = [eventTimer[SensorsAnalyticsEventBeginKey] doubleValue]; // 计算暂停前统计的时长 double duration = [eventTimer[SensorsAnalyticsEventDurationKey] doubleValue] + systemUpTime - beginTime; eventTimer[SensorsAnalyticsEventDurationKey] = @(duration); // 事件处于暂停状态 eventTimer[SensorsAnalyticsEventIsPauseKey] = @(YES); self.trackTimer[event] = eventTimer; } - (void)trackTimerResume:(NSString *)event { NSMutableDictionary *eventTimer = [self.trackTimer[event] mutableCopy]; // 如果没有开始,直接返回 if (!eventTimer) { return; } // 如果该事件时长统计没有暂停,直接返回,不做任何处理 if ([eventTimer[SensorsAnalyticsEventIsPauseKey] boolValue]) { return; } // 获取当前系统启动时间 double systemUpTime = [SensorsAnalyticsSDK systemUpTime]; // 重置事件开始事件 eventTimer[SensorsAnalyticsEventBeginKey] = @(systemUpTime); // 将事件暂停被标记设置为 NO eventTimer[SensorsAnalyticsEventIsPauseKey] = @(NO); self.trackTimer[event] = eventTimer; }

第二步:修改 - trackTimerEnd: properties: 方法

- (void)trackTimerEnd:(NSString *)event properties:(NSDictionary *)properties { NSDictionary *eventTimer = self.trackTimer[event]; if (!eventTimer) { return [self track:event properties:properties]; } NSMutableDictionary *p = [NSMutableDictionary dictionaryWithDictionary:properties]; // 移除 [self.trackTimer removeObjectForKey:event]; if ([eventTimer[SensorsAnalyticsEventIsPauseKey] boolValue]) { // 获取事件时长 double eventDuration = [eventTimer[SensorsAnalyticsEventDurationKey] doubleValue]; // 设置事件时长属性 p[@"$event_duration"] = @([[NSString stringWithFormat:@"%.3lf", eventDuration] floatValue]); } else { // 事件开始时间 double beginTime = [(NSNumber *)eventTimer[SensorsAnalyticsEventBeginKey] doubleValue]; // 获取当前系统事件 double currentTime = [SensorsAnalyticsSDK systemUpTime]; // 计算事件时长 double eventDuration = currentTime - beginTime + [eventTimer[SensorsAnalyticsEventDurationKey] doubleValue]; eventDuration = [[NSString stringWithFormat:@"%.3lf", eventDuration] floatValue]; // 设置事件时长属性 [p setObject:@(eventDuration) forKey:@"$event_duration"]; } // 触发事件 [self track:event properties:p]; }

第三步:测试验证

{ "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$event_duration" : 1663.958984375, "$app_version" : "1.0", "$os_version" : "15.2", "$lib" : "iOS" }, "event" : "doSomething", "time" : 1649398840807, "distinct_id" : "1234567" } 2.3 后台状态下的事件时长

​ 以上问题:当应用程序进入后台后,由于我们是通过记录事件开始时间,然后在事件结束时,计算时间差来计算事件的持续时长 ,包括了进入后台的时间。因为在应用程序进入后台时,我们应该调用暂停的方法,当应用程序回到前台运行时,我们调用恢复事件方法。

​ 实现步骤:

第一步:在 SensorsAnalyticsSDK 文件中新增一个属性 enterBackgroundTrackTimerEvents 用来保存进入后台时未暂停的事件名。然后在 -init 方法中进行初始化

/// 保存进入后台时未暂停的事件名称 @property (nonatomic, strong) NSMutableArray<NSString *> *enterBackgroundTrackTimerEvents; - (instancetype)init { self = [super init]; if (self) { _automaticProperties = [self collectAutomaticProperties]; // 设置是否需是被动启动标记 _launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever; _loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId]; _trackTimer = [NSMutableDictionary dictionary]; _enterBackgroundTrackTimerEvents = [NSMutableArray array]; // 添加应用程序状态监听 [self setupListeners]; } return self; }

第二步:在应用程序进入后台时,调用暂停方法,将所有未暂停的事件暂停

- (void)applicationDidEnterBackground:(NSNotification *)notification { NSLog(@"Application did enter background."); // 还原标记位 self.applicationWillResignActive = NO; // 触发 AppEnd 事件 [self track:@"$AppEnd" properties:nil]; // 暂停所有事件时长统计 [self.trackTimer enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) { if (![obj[@"is_pause"] boolValue]) { [self.enterBackgroundTrackTimerEvents addObject:key]; [self trackTimerPause:key]; } }]; }

第三步:在应用程序进入前台的时候,调用事件恢复启动

- (void)applicationDidBecomeActive:(NSNotification *)notification { NSLog(@"Application did enter active."); // 还原标记位 if (self.applicationWillResignActive) { self.applicationWillResignActive = NO; return; } // 将被动启动标记位设置为 NO,正常记录事件 self.launchedPassively = NO; // 触发 AppStart 事件 [self track:@"$AppStart" properties:nil]; // 恢复所有的事件时长统计 for (NSString *event in self.enterBackgroundTrackTimerEvents) { [self trackTimerStart:event]; } [self.enterBackgroundTrackTimerEvents removeAllObjects]; }

第四步:测试运行

三、全埋点事件时长 3.1 $AppEnd 事件时长

​ 当收到 UIApplicationDidBecomeActiveNotification 本地通知时,调用 - trackTimerStart:方法开始计时,当收到 UIApplicationDidEnterBackgroundNotification 本地通知时,调用 - track: properties: 方法结束计时。

实现步骤:

第一步:修改 - applicationDidBecomeActive:方法,在结束时调用 - trackTimerStart:方法

- (void)applicationDidBecomeActive:(NSNotification *)notification { NSLog(@"Application did enter active."); // 还原标记位 if (self.applicationWillResignActive) { self.applicationWillResignActive = NO; return; } // 将被动启动标记位设置为 NO,正常记录事件 self.launchedPassively = NO; // 触发 AppStart 事件 [self track:@"$AppStart" properties:nil]; // 恢复所有的事件时长统计 for (NSString *event in self.enterBackgroundTrackTimerEvents) { [self trackTimerStart:event]; } [self.enterBackgroundTrackTimerEvents removeAllObjects]; // 开始 $AppEnd 事件计时 [self trackTimerStart:@"$AppEnd"]; }

第二步:修改 - applicationDidEnterBackground: 方法,将 [self track:@"$AppEnd" properties:nil]; 修改成 [self trackTimerEnd:@"$AppEnd" properties:nil];

- (void)applicationDidEnterBackground:(NSNotification *)notification { NSLog(@"Application did enter background."); // 还原标记位 self.applicationWillResignActive = NO; // 触发 AppEnd 事件 // [self track:@"$AppEnd" properties:nil]; [self trackTimerEnd:@"$AppEnd" properties:nil]; // 暂停所有事件时长统计 [self.trackTimer enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) { if (![obj[@"is_pause"] boolValue]) { [self.enterBackgroundTrackTimerEvents addObject:key]; [self trackTimerPause:key]; } }]; }

第三步:测试验证

{ "propeerties" : { "$model" : "x86_64", "$manufacturer" : "Apple", "$lib_version" : "1.0.0", "$os" : "iOS", "$event_duration" : 16705.58984375, "$app_version" : "1.0", "$os_version" : "15.2", "$lib" : "iOS" }, "event" : "$AppEnd", "time" : 1649402996456, "distinct_id" : "1234567" } 3.2 $AppViewScreen 时间时长

​ 如果按照 $AppEnd 方式实现 $AppViewScreen 时间时长,可能会存在 2 个问题:

  • 如何计算最好一个页面的界面预览事件时长
  • 如何处理嵌套子页面的预览事件时长

具体实现:后续介绍