前言 將本地存儲的事件數據同步到伺服器,然後經過服務端的存儲、抽取、分析和展示,充分發揮數據真正的價值。 一、數據同步 第一步:在 SensorsSDK 項目中,新增 SensorsAnalyticsNetwork 工具類,並新增 serverURL 用於保存伺服器 URL 地址 #import ...
前言
將本地存儲的事件數據同步到伺服器,然後經過服務端的存儲、抽取、分析和展示,充分發揮數據真正的價值。
一、數據同步
第一步:在 SensorsSDK 項目中,新增 SensorsAnalyticsNetwork 工具類,並新增 serverURL 用於保存伺服器 URL 地址
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SensorsAnalyticsNetwork : NSObject
/// 數據上報的伺服器
@property (nonatomic, strong) NSURL *serverURL;
@end
NS_ASSUME_NONNULL_END
第二步:新增 - initWithServerURL: 初始化方法,並禁用 - init 初始化方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SensorsAnalyticsNetwork : NSObject
/// 數據上報的伺服器
@property (nonatomic, strong) NSURL *serverURL;
/// 禁止使用 - init 方法進行初始化
- (instancetype)init NS_UNAVAILABLE;
/// 指定初始化方法
/// @param serverURL 伺服器 URL 地址
- (instancetype)initWithServerURL:(NSURL *)serverURL NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
第三步:在 SensorsAnalyticsNetwork.m 中新增 NSURLSession 類型的 session 屬性,併在 - initWithServerURL:方法中進行初始化
@interface SensorsAnalyticsNetwork() <NSURLSessionDelegate>
@property (nonatomic, strong) NSURLSession *session;
@end
@implementation SensorsAnalyticsNetwork
- (instancetype)initWithServerURL:(NSURL *)serverURL {
self = [super init];
if (self) {
_serverURL = serverURL;
// 創建預設的 session 配置對象
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 設置當個逐級連接數為 5
configuration.HTTPMaximumConnectionsPerHost = 5;
// 設置請求的超時事件
configuration.timeoutIntervalForRequest = 30;
// 容許使用蜂窩網路連接
configuration.allowsCellularAccess = YES;
// 創建一個網路請求回調和完成操作的線程池
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 設置同步運行的最大操作數為 1, 即個操作FIFO
queue.maxConcurrentOperationCount = 1;
// 通過配置對象創建一個 session 對象
_session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:queue];
}
return self;
}
第四步:新增 - buildJSONStringWithEvents:方法,將事件數字轉出字元串
/// 將事件數組轉換成字元串
- (NSString *)buildJSONStringWithEvents:(NSArray<NSString *> *)events {
return [NSString stringWithFormat:@"[\n%@\n]", [events componentsJoinedByString:@".\n"]];
}
第五步:新增 - buildRequestWithJSONString:方法,用於根據 serverURL 和事件字元串來創建 NSURLRequest 請求
- (NSURLRequest *)buildRequestWithJSONString:(NSString *)json {
// 通過伺服器 URL 地址創建請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.serverURL];
// 設置請求的body
request.HTTPBody = [json dataUsingEncoding:NSUTF8StringEncoding];
// 請求方法
request.HTTPMethod = @"POST";
return request;
}
第六步:新增 - flushEvents: 方法,用於同步數據
/// 網路請求結束處理回調類型
typedef void(^SAURLSessionTaskCompletionHandler) (NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error);
- (BOOL)flushEvents:(NSArray<NSString *> *)events {
// 將事件數組組裝成JSON字元串
NSString *jsonString = [self buildJSONStringWithEvents:events];
// 創建請求對象
NSURLRequest *request = [self buildRequestWithJSONString:jsonString];
// 數據上傳結果
__block BOOL flushSuccess = NO;
// 使用 GCD 中信號量,實現線程鎖
dispatch_semaphore_t flushSemaphore = dispatch_semaphore_create(0);
SAURLSessionTaskCompletionHandler handler = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
// 當前請求發送錯誤,列印信息錯誤
NSLog(@"Flush events error: %@", error);
dispatch_semaphore_signal(flushSemaphore);
return;
}
// 獲取請求結束返回的狀態碼
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
// 當狀態碼為 2xx 時,表示事件發送成功
if (statusCode >= 200 && statusCode < 300) {
// 列印上傳成功的數據
NSLog(@"Flush events success: %@", jsonString);
// 數據上報成功
flushSuccess = YES;
} else {
// 事件信息發送失敗
NSString *desc = [NSString stringWithFormat:@"Flush events error, statusCode: %d, events: %@", (int)statusCode, jsonString];
NSLog(@"Flush events error: %@", desc);
}
dispatch_semaphore_signal(flushSemaphore);
};
// 通過 request 創建請求任務
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:handler];
// 執行任務
[task resume];
// 等待請求完成
dispatch_semaphore_wait(flushSemaphore, DISPATCH_TIME_FOREVER);
// 返回數據上傳結果
return flushSuccess;
}
第七步:在 SensorsAnalyticsSDK.m 文件中新增 SensorsAnalyticsNetwork 類型的 network 對象,併在 - init 方法中進行初始化
#import "SensorsAnalyticsNetwork.h"
/// 發送網路請求對象
@property (nonatomic, strong) SensorsAnalyticsNetwork *network;
- (instancetype)initWithServerURL:(NSString *)urlString {
self = [super init];
if (self) {
_automaticProperties = [self collectAutomaticProperties];
// 設置是否需是被動啟動標記
_launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever;
_loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId];
_trackTimer = [NSMutableDictionary dictionary];
_enterBackgroundTrackTimerEvents = [NSMutableArray array];
_fileStroe = [[SensorsAnalyticsFileStore alloc] init];
_database = [[SensorsAnalyticsDatabase alloc] init];
_network = [[SensorsAnalyticsNetwork alloc] initWithServerURL:[NSURL URLWithString:urlString]];
// 添加應用程式狀態監聽
[self setupListeners];
}
return self;
}
第八步:暴露 - flush 數據上報的方法,並實現。
/// 向伺服器同步本地所有數據
- (void)flush;
- (void)flush {
// 預設向服務端一次發送 50 條數據
[self flushByEventCount:SensorsAnalyticsDefalutFlushEventCount];
}
- (void)flushByEventCount:(NSUInteger) count {
// 獲取本地數據
NSArray<NSString *> *events = [self.database selectEventsForCount:count];
// 當本地存儲的數據為0或者上傳 失敗時,直接返回,退出遞歸調用
if (events.count == 0 || ![self.network flushEvents:events]) {
return;
}
// 當刪除數據失敗時,直接返回,退出遞歸調用,防止死迴圈
if ([self.database deleteEventsForCount:count]) {
return;
}
// 繼續刪除本地的其他數據
[self flushByEventCount:count];
}
第九步:測試驗證
問題描述:serverURL 是在 -init 方法中硬編碼的。我們需要支持 SDK 初始化的時候傳入
第一步:在 SensorsAnalyticsSDK.h 文件中禁止直接使用 - init 方法初始化 SDK,並新增 + startWithServerURL:方法的聲明
@interface SensorsAnalyticsSDK : NSObject
/// 設備 ID (匿名 ID)
@property (nonatomic, copy) NSString *anonymousId;
/// 事件開始發生的時間戳
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *trackTimer;
/// 獲取 SDK 實例方法
/// 返回單例對象
+ (SensorsAnalyticsSDK *)sharedInstance;
/// 用戶登陸設置登陸 ID
/// @param loginId 用戶的登陸 ID
- (void)login:(NSString *)loginId;
/// 當前的時間
+ (double)currentTime;
/// 系統啟動時間
+ (double)systemUpTime;
/// 向伺服器同步本地所有數據
- (void)flush;
- (instancetype)init NS_UNAVAILABLE;
/// 初始化 SDK
/// @param urlString 接受數據的服務端URL
+ (void)startWithServerURL:(NSString *) urlString;
@end
第二步:實現 - initWithServerURL:方法的初始化
- (instancetype)initWithServerURL:(NSString *)urlString {
self = [super init];
if (self) {
_automaticProperties = [self collectAutomaticProperties];
// 設置是否需是被動啟動標記
_launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever;
_loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId];
_trackTimer = [NSMutableDictionary dictionary];
_enterBackgroundTrackTimerEvents = [NSMutableArray array];
_fileStroe = [[SensorsAnalyticsFileStore alloc] init];
_database = [[SensorsAnalyticsDatabase alloc] init];
_network = [[SensorsAnalyticsNetwork alloc] initWithServerURL:[NSURL URLWithString:urlString]];
// 添加應用程式狀態監聽
[self setupListeners];
}
return self;
}
第三步:實現 + startWithServerURL: 類方法
static SensorsAnalyticsSDK *sharedInstace = nil;
+ (void)startWithServerURL:(NSString *)urlString {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstace = [[SensorsAnalyticsSDK alloc] initWithServerURL:urlString];
});
}
第四步: 修改 + sharedInstance 方法
+ (SensorsAnalyticsSDK *)sharedInstance {
return sharedInstace;
}
第五步:測試驗證
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[SensorsAnalyticsSDK startWithServerURL:@"https//:www.baidu.com"];
[[SensorsAnalyticsSDK sharedInstance] track:@"MyFirstTrack" properties:@{@"testKey": @"testValue"}];
return YES;
}
二、數據同步策略
上面的集成,需要手動觸發。但是作為一個標準的數據採集 SDK,必須包含一些自動同步數據的策略,一方面是為了降低用戶使用 SDK 的難度和成本,另一方面是為了確保數據的正確性、完整性和及時性。
2.1 基本原則
- 策略一:客戶端本地已經緩存的事件超過一定條數時同步數據
- 策略二:客戶端每隔一定的時間不同一次數據
- 策略三:應用程式進入後天時嘗試不同本地已緩存的所有數據
因為事件和事件之間是有先後順序的,因此,在同步數據的時候,需要嚴格按照事件觸發的時間吸納後順序同步數據。所以我們需要先優化 SDK 中 - flush 方法,並使其在隊列中執行。
第一步:在 SensorsAnalyticsSDK.m 中新增 dispatch_queue_t 類型的屬性 serialQueue,併在 -initWithServerURL:方法中進行初始化
/// 隊列
@property (nonatomic, strong) dispatch_queue_t serialQueue;
- (instancetype)initWithServerURL:(NSString *)urlString {
self = [super init];
if (self) {
_automaticProperties = [self collectAutomaticProperties];
// 設置是否需是被動啟動標記
_launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever;
_loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId];
_trackTimer = [NSMutableDictionary dictionary];
_enterBackgroundTrackTimerEvents = [NSMutableArray array];
_fileStroe = [[SensorsAnalyticsFileStore alloc] init];
_database = [[SensorsAnalyticsDatabase alloc] init];
_network = [[SensorsAnalyticsNetwork alloc] initWithServerURL:[NSURL URLWithString:urlString]];
NSString *queueLabel = [NSString stringWithFormat:@"cn.sensorsdata.%@.%p", self.class, self];
_serialQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL);
// 添加應用程式狀態監聽
[self setupListeners];
}
return self;
}
第二步:修改 -flush 方法,並使其在隊列中執行
- (void)flush {
dispatch_async(self.serialQueue, ^{
// 預設向服務端一次發送 50 條數據
[self flushByEventCount:SensorsAnalyticsDefalutFlushEventCount];
});
}
第三步:優化 - track: properties: 方法,並使其在隊列中執行
- (void)track:(NSString *)eventName properties:(nullable NSDictionary<NSString *, id> *)properties {
NSMutableDictionary *event = [NSMutableDictionary dictionary];
// 設置事件 distinct_id 欄位,用於唯一標識一個用戶
event[@"distinct_id"] = self.loginId ?: self.anonymousId;
// 設置事件名稱
event[@"event"] = eventName;
// 事件發生的時間戳,單位毫秒
event[@"time"] = [NSNumber numberWithLong:NSDate.date.timeIntervalSince1970 *1000];
NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary];
// 添加預置屬性
[eventProperties addEntriesFromDictionary:self.automaticProperties];
// 添加自定義屬性
[eventProperties addEntriesFromDictionary:properties];
// 判斷是否是被動啟動狀態
if (self.isLaunchedPassively) {
eventProperties[@"$app_state"] = @"background";
}
// 設置事件屬性
event[@"propeerties"] = eventProperties;
dispatch_async(self.serialQueue, ^{
// 列印
[self printEvent:event];
// [self.fileStroe saveEvent:event];
[self.database insertEvent:event];
});
}
2.2 策略一
客戶端本地已經緩存的事件超過一定條數時同步數據。
實現策略:每次事件觸發併入庫後,檢查一下已緩存的事件條數是否超過了定義的閾值,如果已達到,調用 - flush 方法同步數據。
第一步:添加屬性 flushBulkSize ,表示允許本地緩存的事件最大條數
/// 當本地緩存的事件達到最大條數時,上次數據(預設 100 條)
@property (nonatomic, assign) NSInteger flushBulkSize;
第二步:在 - initWithServerURL: 初始化中,初始化化 flushBulkSize 屬性,預設值設置為 100 條
- (instancetype)initWithServerURL:(NSString *)urlString {
self = [super init];
if (self) {
_automaticProperties = [self collectAutomaticProperties];
// 設置是否需是被動啟動標記
_launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever;
_loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId];
_trackTimer = [NSMutableDictionary dictionary];
_enterBackgroundTrackTimerEvents = [NSMutableArray array];
_fileStroe = [[SensorsAnalyticsFileStore alloc] init];
_database = [[SensorsAnalyticsDatabase alloc] init];
_network = [[SensorsAnalyticsNetwork alloc] initWithServerURL:[NSURL URLWithString:urlString]];
NSString *queueLabel = [NSString stringWithFormat:@"cn.sensorsdata.%@.%p", self.class, self];
_serialQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL);
_flushBulkSize = 100;
// 添加應用程式狀態監聽
[self setupListeners];
}
return self;
}
第三步:在 - track: properties: 方法,事件入庫之後,判斷本地緩存的事件條數是否大於 flushBulkSize ,入股大於,則觸發數據同步
- (void)track:(NSString *)eventName properties:(nullable NSDictionary<NSString *, id> *)properties {
NSMutableDictionary *event = [NSMutableDictionary dictionary];
// 設置事件 distinct_id 欄位,用於唯一標識一個用戶
event[@"distinct_id"] = self.loginId ?: self.anonymousId;
// 設置事件名稱
event[@"event"] = eventName;
// 事件發生的時間戳,單位毫秒
event[@"time"] = [NSNumber numberWithLong:NSDate.date.timeIntervalSince1970 *1000];
NSMutableDictionary *eventProperties = [NSMutableDictionary dictionary];
// 添加預置屬性
[eventProperties addEntriesFromDictionary:self.automaticProperties];
// 添加自定義屬性
[eventProperties addEntriesFromDictionary:properties];
// 判斷是否是被動啟動狀態
if (self.isLaunchedPassively) {
eventProperties[@"$app_state"] = @"background";
}
// 設置事件屬性
event[@"propeerties"] = eventProperties;
dispatch_async(self.serialQueue, ^{
// 列印
[self printEvent:event];
// [self.fileStroe saveEvent:event];
[self.database insertEvent:event];
});
if (self.database.eventCount >= self.flushBulkSize) {
[self flush];
}
}
2.3 策略二
客戶端每隔一定的時間同步一次數據,(比如預設 15 秒)
實現策略:開啟一個定時器,每隔一定時間調用一次 -flush 方法。
第一步:添加 flushInterval 屬性,兩次數據發送的時間間隔,然後再 - initWithServerURL: 初始化方法中初始化
/// 兩次數據發送的時間間隔,單位為秒
@property (nonatomic) NSUInteger flushInterval;
- (instancetype)initWithServerURL:(NSString *)urlString {
self = [super init];
if (self) {
_automaticProperties = [self collectAutomaticProperties];
// 設置是否需是被動啟動標記
_launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever;
_loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId];
_trackTimer = [NSMutableDictionary dictionary];
_enterBackgroundTrackTimerEvents = [NSMutableArray array];
_fileStroe = [[SensorsAnalyticsFileStore alloc] init];
_database = [[SensorsAnalyticsDatabase alloc] init];
_network = [[SensorsAnalyticsNetwork alloc] initWithServerURL:[NSURL URLWithString:urlString]];
NSString *queueLabel = [NSString stringWithFormat:@"cn.sensorsdata.%@.%p", self.class, self];
_serialQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL);
_flushBulkSize = 100;
_flushInterval = 15;
// 添加應用程式狀態監聽
[self setupListeners];
[self startFlushTimer];
}
return self;
}
第二步:新增 flushTimer 屬性,並實現 定時器方法
/// 定時上次事件的定時器
@property (nonatomic, strong) NSTimer *flushTimer;
#pragma mark - FlushTimer
- (void)startFlushTimer {
if (self.flushTimer) {
return;
}
NSTimeInterval interval = self.flushInterval < 5 ? 5 : self.flushInterval;
self.flushTimer = [NSTimer timerWithTimeInterval:interval target:self selector:@selector(flush) userInfo:nil repeats:YES];
[NSRunLoop.currentRunLoop addTimer:self.flushTimer forMode:NSRunLoopCommonModes];
}
- (void)stopFlushTimer {
[self.flushTimer invalidate];
self.flushTimer = nil;
}
第三步:在 - initWithServerURL: 中調用開啟定時器方法 - startFlushTimer
- (instancetype)initWithServerURL:(NSString *)urlString {
self = [super init];
if (self) {
_automaticProperties = [self collectAutomaticProperties];
// 設置是否需是被動啟動標記
_launchedPassively = UIApplication.sharedApplication.backgroundTimeRemaining != UIApplicationBackgroundFetchIntervalNever;
_loginId = [[NSUserDefaults standardUserDefaults] objectForKey:SensorsAnalyticsLoginId];
_trackTimer = [NSMutableDictionary dictionary];
_enterBackgroundTrackTimerEvents = [NSMutableArray array];
_fileStroe = [[SensorsAnalyticsFileStore alloc] init];
_database = [[SensorsAnalyticsDatabase alloc] init];
_network = [[SensorsAnalyticsNetwork alloc] initWithServerURL:[NSURL URLWithString:urlString]];
NSString *queueLabel = [NSString stringWithFormat:@"cn.sensorsdata.%@.%p", self.class, self];
_serialQueue = dispatch_queue_create(queueLabel.UTF8String, DISPATCH_QUEUE_SERIAL);
_flushBulkSize = 100;
_flushInterval = 15;
// 添加應用程式狀態監聽
[self setupListeners];
[self startFlushTimer];
}
return self;
}
第四步:實現 - setFlushInterval: 方法
- (void)setFlushInterval:(NSUInteger)flushInterval {
if (_flushInterval != flushInterval) {
_flushInterval = flushInterval;
// 上傳本地緩存所有數據
[self flush];
// 先暫停定時器
[self stopFlushTimer];
// 重新開始定時器
[self startFlushTimer];
}
}
第五步:在 - applicationDidEnterBackground: 方法中停止定時器,在 - applicationDidBecomeActive: 中開啟定時器
- (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];
}
}];
// 停止計時器
[self stopFlushTimer];
}
- (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"];
// 開啟定時器
[self startFlushTimer];
}
2.4 策略三
應用程式進入後天時嘗試不同本地已緩存的所有數據
實現策略:通過 - beginBackgroundTaskWithExpirationHandler 方法,該方法可以讓我們在應用程式進入後臺最多有3分鐘的時間來處理數據。
第一步:新增 - flushByEventCount: 方法 新增 background 參數,表示是否後臺任務發起同步數據
- (void)flushByEventCount:(NSUInteger) count background:(BOOL)background{
if (background) {
__block BOOL isContinue = YES;
dispatch_sync(dispatch_get_main_queue(), ^{
// 當運行時間大於請求超時時間時,為保證資料庫刪除時應用程式不被強殺,不在繼續上傳
isContinue = UIApplication.sharedApplication.backgroundTimeRemaining >= 30;
});
if (!isContinue) {
return;
}
}
// 獲取本地數據
NSArray<NSString *> *events = [self.database selectEventsForCount:count];
// 當本地存儲的數據為0或者上傳 失敗時,直接返回,退出遞歸調用
if (events.count == 0 || ![self.network flushEvents:events]) {
return;
}
// 當刪除數據失敗時,直接返回,退出遞歸調用,防止死迴圈
if ([self.database deleteEventsForCount:count]) {
return;
}
// 繼續刪除本地的其他數據
[self flushByEventCount:count background:background];
}
第二步:- flush 調用修改後的方法
- (void)flush {
dispatch_async(self.serialQueue, ^{
// 預設向服務端一次發送 50 條數據
[self flushByEventCount:SensorsAnalyticsDefalutFlushEventCount background:NO];
});
}
第三步:- applicationDidEnterBackground: 添加後臺同步數據的任務
- (void)applicationDidEnterBackground:(NSNotification *)notification {
NSLog(@"Application did enter background.");
// 還原標記位
self.applicationWillResignActive = NO;
// 觸發 AppEnd 事件
// [self track:@"$AppEnd" properties:nil];
[self trackTimerEnd:@"$AppEnd" properties:nil];
UIApplication *application = UIApplication.sharedApplication;
// 初始化標記位
__block UIBackgroundTaskIdentifier backgroundTaskIdentifier = UIBackgroundTaskInvalid;
// 結束後臺任務
void (^endBackgroundTast)(void) = ^() {
[application endBackgroundTask:backgroundTaskIdentifier];
backgroundTaskIdentifier = UIBackgroundTaskInvalid;
};
// 標記長時間運行的後臺任務
backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
endBackgroundTast();
}];
dispatch_async(self.serialQueue, ^{
// 發送數據
[self flushByEventCount:SensorsAnalyticsDefalutFlushEventCount background:YES];
// 結束後臺任務
endBackgroundTast();
});
// 暫停所有事件時長統計
[self.trackTimer enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSDictionary * _Nonnull obj, BOOL * _Nonnull stop) {
if (![obj[@"is_pause"] boolValue]) {
[self.enterBackgroundTrackTimerEvents addObject:key];
[self trackTimerPause:key];
}
}];
// 停止計時器
[self stopFlushTimer];
}
第四步:測試驗證