See You Again

iOS 「后台应用刷新」踩坑指南

近期遇到了一个很棘手的问题,我们自身统计的 iOS App 日活和第三方统计(友盟)相差很大,少了一倍多,安卓的统计则差不多。

因为日活数据的准确性很重要,领导比较重视。

为了排查和定位问题,我们实时了各种手段:前端代码打点/异常分支打点、后端接口打点、Nginx IP/日志分析、第三方日志打点、用户行为数据分析……耗时一个多月,最终发现问题的元凶竟然是……

日活的定义

按照正常的理解,用户打开 App,使 App 切换到前台(不管打开多少次)即算当天一个日活(UV),如果一直处于后台(对用户不可见)是不能算的。

一般计算是按照设备 ID 去重,例如昨天 100 UV 代表昨天有 100 个不同的设备访问,但是真实的人数可能只有 90 个,因为有些人有多个设备。

还有一种算法是按照登陆用户数算,一个人在不同的设备登陆同一个帐号只会算一个日活,但是因为游客没有帐号,所以所有的游客会被算到一个日活。

常用的是第一种算法,按每日设备数计算。

App 对日活统计的实现

根据 iOS 的生命周期定义,当进入 applicationDidBecomeActive 方法时,即代表 App 被前台激活,此时往服务端上报一个埋点,该埋点包含了用户的设备信息、基础信息等。

为了确保埋点传输的可靠性,需要做一些策略保障,例如缓存、失败重传等。

这块的代码和逻辑实现,经过多次确认是没有问题的,做过的尝试:

友盟对日活统计的实现

这个是个黑盒,其客服也没有一个很明确的答复。不过,从线下的测试看,它的计算也是比较准确和实时的,没有发现什么可疑的地方。

发现问题

在分析接口日志的过程中,发现某些接口的 UV (A)很高,但是埋点接口的 UV (B)很低,通过分析 Nginx 日志的 IP 数也可以确认这一点,也就是说有很多用户(A - B)没有触发埋点接口,但是触发了其他接口,什么情况的用户(C)会不经过 App 的生命周期方法而访问接口呢?

进一步的,对请求 UV 高的接口(A)增加了一些调试信息,包括用户的环境信息、状态信息等,进而发现 C 群体用户的 App 状态基本都是 Background,也就是在后台发送的!此时有一种大胆的猜测,App 会被后台激活?!

线下实验

基于上面的问题和猜测,进行验证:

  1. 准备一批 iOS 手机,安装一个特殊版本号的包,确保每个手机都打开了「后台应用刷新」
  2. 第一天,每个手机都打开一次 App,观察第一方和第三方统计的 UV,是否匹配,然后全部放到后台
  3. 第二天,不打开该特殊包,但是正常打开其他应用,观察第一方和第三方是否会统计 UV (正常都是 0)
  4. 第三天,关闭每个手机的「后台应用刷新」开关;不打开该特殊包,但是正常打开其他应用,观察第一方和第三方是否会统计 UV (正常都是 0)

实验结果,第一天的统计结果都是正确的。但是第二天早上,发现第三方竟然有统计结果(其中5 台设备,统计了 2 个 UV),经过复查 Nginx 日志,确实发现有 2 个设备在后台请求了某些接口,而不经过埋点接口,所以第一方统计的 UV 是 0。

也就是说 App 被后台激活的时候,能够触发第三方的日活接口,但是没有触发第一方的埋点接口,所以导致了两种统计的误差。

后台应用刷新

此时我们将焦点放到「后台应用刷新」上,发现这是一种系统行为,App 并不能显式关闭这个机制。

网友有一些讨论,这种机制是 iOS 根据用户行为习惯自学习的(理解是一种预加载、预刷新的机制),并没有具体细节,也没有找到特定的执行套路,经过验证也并不和推送强相关,比较玄学。

那么问题来了,你家的 App 是不是也是日活虚高呢?

2020-08-12 喜欢

Copyright © 2015-2020 BY-NC-ND 4.0

回到顶部 ↑