1。前言页面浏览时间用于统计用户在页面停留的时间长度。神策分析iOSSDK,在页面浏览时间自动采集功能上线之前,客户是通过手动调用开始计时和结束计时的相关接口实现页面浏览时间的采集。这种人工采集的方式对客户的业务代码侵入性很大,客户的使用成本比较高。因此,为解决上述问题,神策分析iOSSDK3.1.5[1]版本推出了页面浏览时间自动采集功能[2]。该功能可以自动采集页面浏览时间,无需用户手动调用接口。在实现这个功能的过程中,我们做了很多尝试。我们来看看自动收集页面浏览时间的两种解决方案。二、征收方案分析2.1.方案一该方案主要针对单页情况。采集原理是:当进入某个页面或应用程序进入前台时,定时器开始计时;当应用程序退回到后台或进入新的页面时(此时认为当前页面已经消失)结束计时。具体采集逻辑如下:当收到应用进入前台的通知时,定时器开始计时;当页面的生命周期方法-viewDidAppear:被执行时,触发上一个页面的关闭事件并记录页面浏览时间,同时启动当前页面。页面计时;当收到应用进入后台的通知时,触发当前页面的关闭事件,记录页面浏览时间。优点:采集逻辑简单;业务代码侵入性较小;埋点成本低;应用强杀可以正常收集页面浏览时间。缺点:不支持多页面,无法满足父子页面同时存在时的采集需求;它不支持暂停和恢复计时器。2.2.方案2该方案支持单页和多页两种情况。采集原理为:进入某个页面或应用程序进入前台时定时器开始计时,页面消失或应用程序退回后台时结束。具体采集逻辑如下:当收到应用进入前台的通知时,定时器开始计时;当页面的生命周期方法-viewDidAppear:被执行时,定时器开始计时;当收到应用进入后台的通知时,定时器结束计时,触发当前页面的关闭事件,记录页面浏览时间;当页面生命周期方法-viewDidDisappear:被执行时,定时器结束计时,触发当前页面的关闭事件,记录页面浏览时间。优点:支持多页面;业务代码侵入性较小;嵌入点成本低;应用查杀可以正常收集页面浏览时间。缺点:弹出的子页面会阻塞父页面,只要父页面不执行——viewDidDisappear:方法就不会结束计时;它不支持暂停和恢复计时器。2.3.小结通过以上分析,我们可以知道两种方案各有优缺点。但是方案一不支持多页面场景,所以最终我们选择了方案二作为自动采集页面浏览时间的方案。3、具体实现在介绍自动采集页面浏览时间[2]的具体实现之前,我们先来看一下SDK生命周期的概念。3.1.SDK生命周期SDK生命周期是应用生命周期和SDK内部逻辑的结合,列出了SDK需要的状态://SDK生命周期状态typedefNS_ENUM(NSUInteger,SAAppLifecycleState){SAAppLifecycleStateInit,SAAppLifecycleStateStart,//应用冷(热)启动SAAppLifecycleStateStartPassively,//被动启动[3]SAAppLifecycleStateEnd,//退出SAAppLifecycleStateTerminate,//Terminate};这样你只需要关注SDK的状态变化,就可以准确触发各种事件。例如,如果SDK的状态变为SAAppLifecycleStateEnd,则表示应用已经退出,此时应该触发页面的关闭事件。代码如下:-(void)appLifecycleStateWillChange:(NSNotification*)notification{NSDictionary*userInfo=notification.userInfo;SAAppLifecycleStatenewState=[userInfo[kSAAppLifecycleNewStateKey]integerValue];//冷(热)启动if(newState==SAAppLifecycleStateStart){//开始计时return;}//退出应用程序if(newState==SAAppLifecycleStateEnd){//结束计时}}3.2.采集过程如果要使用自动采集页面浏览时间的功能,只需要将SAConfigOptions实例的enableTrackPageLeave属性设置为JustYES即可。另外为了兼容应用崩溃场景,在崩溃时重新发出页面关闭事件并记录页面浏览时间。自动收集页面浏览时间的流程如图3-1所示:图3-1自动收集页面浏览时间流程图Collection,如果启用,钩住UIViewController的-viewDidAppear:和-viewDidDisappear:方法。代码如下://判断是否开启页面浏览时间收集选择器(sensorsdata_pageLeave_viewDidAppear:)错误:NULL];[UIViewControllersa_swizzleMethod:@selector(viewDidDisappear:)withMethod:@selector(sensorsdata_pageLeave_viewDidDisappear:)错误:NULL];,其中key是UIViewController的地址,value包含计时开始的时间戳)UIViewController的地址是否存在:如果存在则忽略;如果不存在,则记录当前UIViewController的地址和当前时刻的时间戳。另外,当应用程序进入前台时,timestamp中记录的时间戳需要更新为当前时间。代码如下://进入新页面-(void)trackPageEnter:(UIViewController*)viewController{if(![selfshouldTrackViewController:viewController]){return;}NSString*address=[NSStringstringWithFormat:@"%p",viewController];//判断时间戳中是否存在UIViewController的地址if(self.timestamp[address]){return;}//如果不是,则将当前UIViewController的地址和时刻添加到时间戳中NSMutableDictionary*properties=[[NSMutableDictionaryalloc]init];属性[kSAPageLeaveTimestamp]=@([[NSDatedate]timeIntervalSince1970]);properties[kSAPageLeaveAutoTrackProperties]=[selfpropertiesWithViewController:viewController];self.timestamp[address]=properties;}-(void)appLifecycleStateWillChange:(NSNotification*)notification{NSDictionary*userInfo=notification.userInfo;SAAppLifecycleStatenewState=[userInfo[kSAAppLifecycleNewStateKey]integerValue];//冷(热)启动,应用进入前台if(newState==SAAppLifecycleStateStart){//将timestamp中的所有值更新为当前时间[self.timestampenumerateKeysAndObjectsUsingBlock:^(NSString*_Nonnullkey,NSMutableDictionary*_Nonnullobj,BOOL*_Nonnullstop){obj[kSAPageLeaveTimestamp]=@([[NSDatedate]timeIntervalSince1970]);}];返回;}}3.3.3。页面消失、应用返回后台、应用崩溃时计时结束。让我们分别看看如何处理这些场景。3.3.3.1.PageDisappeared页面消失时,获取当前UIViewController地址,查询timestamp中对应的值。如果没有值,直接返回。如果有值,则执行以下步骤:计算页面浏览时长=当前时间-开始时间;触发$AppPageLeave事件,并添加属性event_duration记录页面浏览时长;删除timestamp中对应的key-value。代码如下://当页面消失时,判断当前UIViewController是否为需要计时的UIViewController-(void)trackPageLeave:(UIViewController*)viewController{if(![selfshouldTrackViewController:viewController]){return;}//获取当前UIViewController的地址,查询timestamp中对应的key-value,NSString*address=[NSStringstringWithFormat:@"%p",viewController];//如果没有值,直接返回if(!self.timestamp[address]){return;}//页面浏览时间=当前时间-开始时间NSTimeIntervalcurrentTimestamp=[[NSDatedate]timeIntervalSince1970];NSMutableDictionary*properties=self.timestamp[地址];NSNumber*timestamp=properties[kSAPageLeaveTimestamp];NSTimeIntervalstartTimestamp=[timestampdoubleValue];NSMutableDictionary*tempProperties=[[NSMutableDictionaryalloc]initWithDictionary:properties[kSAPageLeaveAutoTrackProperties]];NSTimeIntervalduration=(currentTimestamp-startTimestamp)<24*60*60?(currentTimestamp-startTimestamp):0;tempProperties[kSAEventDurationProperty]=@([[NSStringstringWithFormat:@"%.3f",duration]floatValue]);//调用触发页面离开事件的方法[selftrackWithProperties:tempProperties];//删除时间戳对应的key-valueself.timestamp[address]=nil;}//触发页面离开事件-(void)trackWithProperties:(NSDictionary*)properties{SAPresetEventObject*object=[[SAPresetEventObjectalloc]initWithEventId:kSAEventNameAppPageLeave];[SensorsAnalyticsSDK.sharedInstanceasyncTrackEventperjectObject::properties];}3.3.3.2。当应用返回后台时,遍历timestamp的key-value,计算页面浏览时长=当前时间-开始时间;然后触发$AppPageLeave事件并添加属性event_duration记录页面浏览时间代码如下://应用退出到后台-(void)appLifecycleStateWillChange:(NSNotification*)notification{NSDictionary*userInfo=notification.userInfo;SAAppLifecycleStatenewState=[userInfo[kSAAppLifecycleNewStateKey]integerValue];//应用退出,调用结束计时方法if(newState==SAAppLifecycleStateEnd){[selftrackEvents];}}//当应用退出到后台时,遍历timestamp的key-value,触发$AppPageLeave,时长为currentTimestamp-startTimestamp-(void)trackEvents{//遍历timestampkey-value[self.timestampenumerateKeysAndObjectsUsingBlock:^(NSString*_Nonnullkey,NSMutableDictionary*_Nonnullobj,BOOL*_Nonnullstop){NSTimeIntervalcurrentTimestamp=[[NSDatedate]timeIntervalSince1970];NSNumber*timestamp=obj[kSAPage]TimestampTimestart;LeavestNS=[timestampdoubleValue];NSMutableDictionary*tempProperties=[[NSMutableDictionaryalloc]initWithDictionary:obj[kSAPageLeaveAutoTrackProperties]];//计算页面浏览时间NSTimeIntervalduration=(currentTimestamp-startTimestamp)<24*60*60?(currentTimestamp-startTimestamp):0;tempProperties[kSAEventDurationProperty]=@([[NSStringstringWithFormat:@"%.3f",duration]floatValue]);]//触发页面离开事件[selftrackWithProperties:[tempPropertiescopy]];}];}3.3.3.3。应用崩溃如果想在应用崩溃时自动收集页面浏览时间,需要将SAConfigOptions实例的enableTrackAppCrash属性设置为YES,因为我们的崩溃收集是一个独立的模块,需要单独开启。当应用崩溃时,遍历timestamp的key-value,计算页面浏览时间=当前时间-开始时间;然后触发$AppPageLeave事件,并添加属性event_duration记录页面浏览时间。代码如下所示://Appcrashes-(void)trackPageLeaveWhenCrashed{if(!self.enable){return;}如果(!self.configOptions.enableTrackPageLeave){返回;}[SACommonUtilityperformBlockOnMainThread:^{if(UIApplication.sharedApplication.applicationState==UIApplicationStateActive){[self.appPageLeaveTrackertrackEvents];}}];}//应用崩溃时,遍历timestamp的key-value,触发$AppPageLeave,时长为currentTimestamp-startTimestamp;-(void)trackEvents{//遍历timestamp的key-value[self.timestampenumerateKeysAndObjectsUsingBlock:^(NSString*_Nonnullkey,NSMutableDictionary*_Nonnullobj,BOOL*_Nonnullstop){NSTimeIntervalcurrentTimestamp=[[NSDatedate]timeIntervalSince1970];NSNumber*Timestamp=obj[NSTimeIntervalstartTimestamp=[timestampdoubleValue];NSMutableDictionary*tempProperties=[[NSMutableDictionaryalloc]initWithDictionary:obj[kSAPageLeaveAutoTrackProperties]];//计算页面浏览时长NSTimeIntervalduration=(currentTimestamp-startTimestamp)<24*60*60?(currentTimestamp-startTimestamp):0;tempProperties[kSAEventDurationProperty]=@([[NSStringstringWithFormat:@"%.3f",duration]floatValue]);]//触发页面离开事件[selftrackWithProperties:[tempPropertiescopy]];}];}3.4。支持场景说到这里大家肯定想知道神策分析iOSSDK支持自动采集哪些场景的页面浏览时间这里总结了11个场景供大家参考,如表3-1所示:表3-1常用问支持自动收集页面浏览时间的场景问题关于自动收集页面浏览时间的功能,我们遇到了一些常见的问题,比如:被动激活[3]会不会影响页面浏览时间的收集;我们来看看这些问题的答案:在被动启动过程中,如果执行了-viewDidAppear:方法,会记录时间戳,但会在应用打开后重新启动定时器。所以页面浏览时间是从点击应用到离开页面的时间,从实际情况来看也是合理的(毕竟被动启动页面是看不到页面的);如果页面被阻塞,并且没有执行-viewDidDisappear:方法,那么阻塞时间也计入页面浏览时间。对于这种场景,其实是不合理的,因为页面被屏蔽后,就相当于看不见了。所以,对于这一点,我们后面会进行优化;只要-viewDidAppear:方法被执行,我们就会收集它。因此,当父子页面同时存在时,将统计各自的页面浏览时间。总结本文主要介绍神策分析iOSSDK如何自动采集页面浏览时间。我希望您在阅读本文后能够清楚地了解如何实现它。详情请参考神策分析iOSSDK源码[1]。目前我们自动收集页面浏览时间的功能还在更新迭代中。欢迎在开源社区与我们交流。参考文献[1]https://github.com/sensorsdat...[2]https://manual.sensorsdata.cn...(iOS)v1.13-%E9%87%87%E9%9B%86%E9%A1%B5%E9%9D%A2%E6%B5%8F%E8%A7%88%E6%97%B6%E9%95%BF[3]https://manual.sensorsdata.cn。..(iOS)v1.13-App%E8%A2%AB%E5%8A%A8%E5%90%AF%E5%8A%A8($AppStartPassively)%E4%BA%8B%E4%BB%B6%E8%AF%B4%E6%98%8E
