iOS全埋点时间相关解决方案

前端APP 投稿 46800 0 评论

iOS全埋点时间相关解决方案

前言

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

Event 实体

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

Where:即事件发生的地点。

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

一、事件发生的时间戳

   

二、统计事件持续时长

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

2.1 实现步骤

开始计时:-  trackTimerStart:

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

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

#import <SensorsSDK/SensorsSDK.h>

NS_ASSUME_NONNULL_BEGIN

@interface SensorsAnalyticsSDK (Timer


/// 开始统计事件时长
/// @param event 事件名
- (voidtrackTimerStart:(NSString *event;


/// 结束事件时长统计,计算时长
/// @param event 事件名 与开始时事件名一一对应
/// @param properties 事件属性
- (voidtrackTimerEnd:(NSString *event properties:(nullable NSDictionary * properties;

@end

NS_ASSUME_NONNULL_END

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

/// 事件开始发生的时间戳
@property (nonatomic, strong NSMutableDictionary<NSString *, NSDictionary *> *trackTimer;

- (instancetypeinit {
    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 方法,用于获取用户当期的时间戳。

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

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

#import "SensorsAnalyticsSDK+Timer.h"

static NSString * const SensorsAnalyticsEventBeginKey = @"event_begin";

@implementation SensorsAnalyticsSDK (Timer
 
- (voidtrackTimerStart:(NSString *event {
    self.trackTimer[event] = @{SensorsAnalyticsEventBeginKey: @([SensorsAnalyticsSDK currentTime]};
}

- (voidtrackTimerEnd:(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 可能为负数

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

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

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

2.2 事件的暂停和恢复

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

实现步骤:

@interface SensorsAnalyticsSDK (Timer

/// 暂停事件统计时长
/// @param event 事件名
- (voidtrackTimerPause:(NSString *event;


/// 恢复事件统计时长
/// @param event 事件名
- (voidtrackTimerResume:(NSString *event;

@end
static NSString * const SensorsAnalyticsEventDurationKey = @"event_duration";
static NSString * const SensorsAnalyticsEventIsPauseKey = @"is_pause";

- (voidtrackTimerPause:(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;
}

- (voidtrackTimerResume:(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: 方法

- (voidtrackTimerEnd:(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;

- (instancetypeinit {
    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;
}

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

- (voidapplicationDidEnterBackground:(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];
        }
    }];
}

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

- (voidapplicationDidBecomeActive:(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 事件时长

实现步骤:

- (voidapplicationDidBecomeActive:(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];

- (voidapplicationDidEnterBackground:(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 个问题:

  • 如何计算最好一个页面的界面预览事件时长

  • 如何处理嵌套子页面的预览事件时长

具体实现:后续介绍

编程笔记 » iOS全埋点时间相关解决方案

赞同 (78) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽