急速收藏:4套iOS SDK的H5打通方案

在介绍 iOS SDK 的 H5 打通方案之前,我们先了解一下什么是 App 与 H5 打通。

所谓 “打通”,是指 H5 集成 JavaScript 数据采集 SDK 后,H5 触发的事件不是直接同步给服务端,而是先发给 App 端的数据采集 SDK,经 App 端数据采集 SDK 二次加工处理后缓存到本地,再经过合适的上传策略同步到服务端。

二、APP 与 H5 打通的原因

关于 App 与 H5 打通的原因,我们主要是从以下几个角度考虑:

2.1数据丢失率

在业界,App 端采集数据的丢失率一般在 1% 左右,而 H5 采集数据的丢失率一般在 5% 左右(主要是因为缓存、网络或切换页面等原因)。

因此,如果 App 与 H5 打通,H5 触发的所有事件都可以先发给 App 端数据采集 SDK,经过 App 端二次加工处理后存入本地缓存,在符合特定策略之后再进行数据同步,即可把数据丢失率由 5% 降到 1% 左右。

2.2 数据准确性

众所周知,H5 无法直接获取设备相关的信息,只能通过解析 UserAgent 值获取到有限的信息。而解析 UserAgent 值,至少会面临以下两个问题:

(1)有些信息通过解析 UserAgent 值根本获取不到,比如应用程序的版本号、设备具体型号等;

(2)有些信息通过解析 UserAgent 值可以获取到,但内容可能不正确。

如果 App 与 H5 打通,由 App 端数据采集 SDK 补充这些信息,即可确保事件信息的准确性和完整性。

2.3 用户标识

如果用户在 App 端注册或登录之前使用我们的产品,我们一般都是使用匿名 ID 来标识用户。而 App 与 H5 标识匿名用户的规则不一样(iOS 一般使用 IDFA 或 IDFV,H5 一般使用 Cookie),从而就会导致一个用户使用了我们的产品,结果产生了两个匿名用户。如果 App 与 H5 打通,就可以将两个匿名 ID 做归一化处理(以 App 端匿名 ID 为准)。.4

2.4 基础功能

基于 App 与 H5 打通,可以实现诸如 App 内嵌 H5 可视化全埋点、App 内嵌 H5 弹框等更加高级的功能。

介绍完打通的原因之后,我们来看下 App 与 H5 如何进行打通。

三、打通方案演进

关于 App 与 H5 的打通,曾经一直是我们的一个痛点,为此我们也做了持续地探索和迭代。在这个过程中我们踩了很多坑,也积累了一些经验,现在将按照方案演进的顺序为大家一一介绍这几种方式,并分析其背景、实现和优缺点。

方案一

背景和原理

iOS SDK 从 v1.6.8 版本开始支持 App 与 H5 打通,也就是打通方案的原始版本。

众所周知,在实际开发中,一个技术方案的使用,都是为了解决某些问题。在早期的事件分析中,H5 的匿名 ID(一般使用 Cookie)经常变化,并且与 App 端(iOS 一般使用 IDFA 或 IDFV)不同。因此,导致一个 App 用户在内嵌 H5 页面的行为序列无法和原生页面的行为联系起来,为业务分析造成很大困扰。但是,如果将 App 端的匿名 ID 传给 H5,就能保证 App 内嵌 H5 页面使用的匿名 ID 和原生页面一致,从而解决上述问题。

原理:

在 WebView 加载 H5 页面完成后,iOS SDK 调用 JS 方法将匿名 ID 传给 JS SDK,JS SDK 采集的埋点数据就可以使用 App 的匿名 ID,从而使得 App 进入 H5 页面前后的用户行为序列准确关联。主要流程如图 3-1 所示:

具体实现

具体实现主要分为下面几个步骤:

(1)从图 3-1 的打通方案流程可以知道,为了保证 JS SDK 已经加载完成,iOS SDK 需要在 H5 页面加载完成后才能调用 JS 方法传值。那么监听 H5 页面加载完成的时机,便成了打通的关键。

对于 UIWebView 而言,我们知道从 UIWebViewDelegate 的代理方法中可以获取 UIWebView 加载 H5 页面的进度,UIWebViewDelegate 的代理方法如下:

因此,我们在 UIWebView 的 H5 页面加载完成调用 JS 方法,只需要实现 - webViewDidFinishLoad: 方法即可,如下所示:

UIWebView

对于 WKWebView 而言,查阅 Apple 的 WKWebView 相关 API 文档没有合适的代理方法去监听 H5 页面加载完成,不过发现了 loading 这个属性,说明如下:

loading 属性表示当前页面是否正在加载,如果 loading = NO(即页面不再加载了),表示 H5 页面已经加载完成。因此,我们使用 KVO 监听 loading 的属性变化,就可以知道 WKWebView 加载 H5 页面是否完成,具体实现如下所示:

WKWebView

(2)监听 H5 页面加载完成后,即表示 JS SDK 已经准备就绪。此时使用 WebView 调用 JS 方法,将 App 端使用的匿名 ID 传给 JS SDK 即可,具体实现如下所示:

(3)JS SDK 后续采集的所有事件,都使用 iOS SDK 传递的匿名 ID 作为当前的用户标识,具体实现如下所示:

优点:

  • 由于 iOS SDK 不用处理 JS 端的数据,只需要传少量信息到 JS 端,对 iOS SDK 的侵入较小;

  • 可以同时兼容 UIWebView 和 WKWebView,满足更多客户需求。

缺点:

  • H5 产生的埋点数据,继续由 JS SDK 上报到服务端,数据丢失的风险较大;

  • 如果 WebView 加载完成,JS SDK 尚未加载,会导致打通失败;

  • 只要开启打通,iOS SDK 就会把 App 端的匿名 ID 发给 JS SDK 。由于不会区分项目,这样可能会导致数据错乱。例如,客户 A 的 App 内嵌了客户 B 的 H5,可能导致客户 B 的 H5 页面使用了客户 A 的匿名 ID,然后数据还是发到了客户 B 的服务端,导致客户 B 的用户数据错乱;

  • 对于客户来说打通的集成比较复杂。例如,如果客户 App 项目中使用了多个 UIWebView 或 WKWebView ,需要在多处实现协议方法并调用 SDK 接口,接入的工作量会比较大。

3.2

方案二

背景和原理

为了减小加载 JS SDK 对客户 H5 页面的影响,后期 JS SDK 支持异步加载(即 H5 页面加载完成后才开始加载 JS SDK)。这就导致一个问题:可能 WebView 加载完成时,JS SDK 尚未加载,此时方案一会打通失败。为了解决这个问题,我们开发出了方案二。

因为 JS SDK 支持异步加载,所以 iOS SDK 不再依赖于 WebView 加载完成的状态去判断 JS SDK 是否加载完成。因此,方案二的关键问题是:iOS SDK 应该什么时机去调用 JS 的方法发送 App 端的数据,即 iOS SDK 怎么才能知道 JS SDK 加载完成?

原理:

在 JS SDK 加载完成并初始化后,发送一个 iframe(sensorsanalytics://getAppInfo)请求。然后在 App 的 WebView 的协议方法中拦截 H5 的页面请求。如果出现 sensorsanalytics://getAppInfo 这个请求,即认为 JS SDK 加载完成。此时,调用 JS 的方法发送 App 端的数据,就能保证打通成功。主要流程如图 3-2 所示:

具体实现

具体实现主要分为下面几个步骤:

(1)JS SDK 初始化后发送 iframe 请求:

(2)iOS SDK 拦截请求。关于拦截 H5 页面中的请求,在 UIWebView 和 WKWebView 实现略有不同,分别示例如下。

对于 UIWebView 拦截请求,需要实现 UIWebViewDelegate 中的如下代理方法:

对于 WKWebView 拦截请求,需要实现 WKNavigationDelegate 中的如下代理方法:

(3)判断 sensorsanalytics://getAppInfo 请求,调用 JS 方法发送数据:

(4)JS SDK 采集的事件,都使用 iOS SDK 发送的匿名 ID 作为当前用户的标识。

优点:

  • 这种打通方案,由于 iOS SDK 不用处理 JS 端的数据,只需要传少量信息到 JS,对 iOS SDK 的侵入较小;

  • 可以同时兼容 UIWebView 和 WKWebView,满足更多客户需要;

  • 支持 JS SDK 在 H5 页面异步加载情况下的 App 与 H5 打通。

缺点:

  • H5 产生的埋点数据,继续由 JS SDK 上报到服务端,数据丢失的风险较大;

  • 只要开启打通,JS SDK 就会把 App 的匿名 ID 发给 JS SDK 。由于不会区分项目,这样可能会导致数据错乱。例如,客户 A 的 App 内嵌了客户 B 的 H5,可能导致客户 B 的 H5 页面使用了客户 A 的匿名 ID,然后数据还是发到了客户 B 的服务端,导致客户 B 的用户数据错乱;

  • 对于客户来说打通的集成比较复杂。例如,客户 App 项目中使用了多个 UIWebView 或 WKWebView ,需要在多处实现协议方法并调用 SDK 接口,接入的工作量会比较大。

3.3

方案三

背景和原理

对于上述两种方案,对功能影响较大的两个问题是:

  • App 与 H5 打通后,App 发送匿名 ID 给 JS SDK 时不区分项目,对其他客户数据造成较大影响;

  • H5 的埋点数据通过 JS SDK 上报,数据丢失的风险较大,并且部分数据无法采集。

为了解决上述两个问题,我们对 App 与 H5 打通方案做了较大的修改,从而推出了方案三:

  • 为了解决上述第一个问题,我们单独增加了开启打通的接口。如果客户调用并开启打通,iOS SDK 会修改当前 App 环境的 UA 值,拼接当前接入 SA 的 project(项目名) 和 host(域名)。这样 JS SDK 可以判断当前 H5 页面是否需要打通,并且还可以校验是否为同一个项目;

  • 为了解决上述第二个问题,我们在 JS SDK 增加是否需要打通的判断,如果需要打通就将 H5 产生的埋点数据发送到 App,由 iOS SDK 处理并缓存到本地,然后根据合适的策略上传到服务端。

原理:

iOS SDK 修改 UA 来标记当前 App 集成神策的项目信息,JS SDK 根据 UA 值判断是否需要打通。如果需要打通,JS SDK 将埋点数据发往 App,iOS SDK 解析并处理 JS SDK 端产生的埋点数据,再根据合适的策略上报到服务端。主要流程如图 3-3 所示:

实现

具体实现主要分为下面几个步骤:

(1) iOS SDK 开启打通的接口,默认校验当前数据接收地址中的项目。也就是将神策数据接收地址中的 host 和 project 写入当前的 UA 环境,以便 JS SDK 解析:

(2)JS SDK 获取当前环境的 UA 值,如果判断当前环境为 App 内嵌 H5 并且开启打通(UA 中包含 /sa-sdk-ios/sensors-verify),就会解析 UA 中的 host 和 project。如果根据 host 和 project 判断当前 H5 与 App 集成的是同一个神策项目,则表示需要进行 App 与 H5 打通。此时 JS SDK 触发 iframe 请求,发送埋点数据到 App :

JS SDK

(3) iOS SDK 拦截 WebView 请求,解析 JS 埋点数据并进行处理和缓存(此处以 UIWebView 的实现为例,关于 WKWebView 拦截请求的方案,上文已有介绍,此处不再赘述):

WebView 中拦截请求

解析 JS SDK 触发 iframe 请求中的 URL 参数,从而获取 JS SDK 发送的埋点数据:

优点:

  • H5 产生的事件数据,iOS SDK 先经过了加工:使用 App 的匿名 ID,增加设备信息、网络和运营商信息等,从而提高了数据采集的准确性和完整性,保证了匿名  ID 的一致,为后续的行为序列分析提供了可靠的数据基础;

  • 通过 iOS SDK 缓存并上报,很大程度上降低了数据丢失的风险;

  • JS SDK 解析 H5 环境的 UA 标识判断是否打通,避免了 App  使用其他客户的 H5 可能存在的数据丢失或产生脏数据问题。

缺点:

  • 对于每个 WebView,都需要在协议方法中调用接口拦截请求,如果一个项目有多个 WebView,集成工作相对繁琐;

  • 如果客户项目禁用 UIWebView,WKWebView 目前只支持异步获取 UA 再进行修改,可能会导致两个问题:如果客户也需要修改 UA,可能会导致打通失败或者客户修改 UA 失败;如果 App 首页加载 WKWebView,这个页面的 H5 会打通失败。

  • JS SDK 发送的 iframe 请求,在客户 App 环境中,可能被误判为非法请求并进行拦截,导致打通失败。

3.4

方案四

背景和原理

随着客户数量的快速增加,越来越复杂的使用场景和客户环境,给我们的 App 与 H5 打通方案带来新的考验。在某些复杂客户环境中,一个 H5 页面可能存在于多个不同的 App 项目(可能是正式项目与测试项目,或一个集团内的多个业务线)中,各个 App 使用的神策服务器地址可能不同,并且都需要进行打通,目前上述的几种方案,都无法满足。

同时,Apple 准备禁用 UIWebView[1],越来越多的 App 开始从 UIWebView 迁移到 WKWebView 。但是,我们的 App 与 H5 打通方案,在 WKWebView 打通中存在一些遗留问题,影响了客户的使用体验。

面对客户诉求和 Apple 的新规定,我们的 App 与 H5 打通方案,也亟待再次进行优化。因此,第四版打通方案应运而生。

原理:

通过技术调研,我们发现在 iOS 的 WKWebView 中,Apple 在 js runtime 环境里事先注入了一个

window.webkit.messageHandlers.xxxx.postMessage() 方法,我们可以使用这个方法直接向 Native 层传值。

基于这一原理,iOS 开发中 Native 与 H5 的交互可以使用全新的解决方案。iOS 已经将 window.webkit.messageHandlers 向 H5 共享了当前的 webkit 环境,只要在 WKWebView 的 configuration.userContentController 中注入实现 WKScriptMessageHandler 协议的对象(即 JSBridge),H5 端通过调用 postMessage() 方法,即可直接向 Native 发送消息。

为了减轻客户开启 App 与 H5 打通的接入成本,方案四使用动态 swizzle 技术 hook 了 WKWebView 加载 URL 的方法。在拿到当前 WKWebView 对象后自动注入 JSBridge,从而避免在每个 WebView 中单独调用,提升 SDK 的集成体验。

在复杂的客户环境,针对不同校验策略的诉求,方案四使用了白名单策略。JS SDK 提供了接口设置 serverURL 的白名单集合,只要 App 设置的 serverURL 包含在白名单中,则集成 JS SDK 的 H5 都可以打通成功,巧妙地兼容了同一个 H5 需要在不同 App 项目中进行打通的场景。

方案四的主要流程如图 3-4 所示:

具体实现

具体实现主要分为下面几个步骤:

(1)开启打通后,自动执行 swizzle:

(2)WKWebView 加载 H5,调用 swizzle 的方法,注入了 SAScriptMessageHandler  对象(JavaScriptBridge)到

ScriptMessageHandler:

(3) JS SDK 发送埋点数据:

(4)在 SAScriptMessageHandler 中接收 js 发送的埋点数据:

优点:

  • 使用 WKWebView 的相关 API 实现,相对稳定可靠;

  • 一个公司的多个 APP,可以通过配置 H5 白名单,实现不同 ServerURL 的 App 都能打通 H5;

  • 如果开启 App 与 H5 打通,只需要通过初始化配置开关设置即可,不需要对每个 WebView 重复调用,方便客户集成。

缺点:

  • 目前通过 swizzle 方案 hook 了 WKWebView 加载 H5 的方法,如果 swizzle 逻辑出现问题,可能导致 WKWebView 加载 H5 失败(目前测试和使用过程中均未发现);

  • 方案只支持 WKWebView 。

四、总结

经过艰难的摸索和持续的迭代后,iOS SDK 的 H5 打通方案目前趋于稳定。当然,目前的方案只是在当前环境的最优选择。对于以后的业务变化和技术发展,我们可能会面临新的挑战和难题。如果等到那一天,我们能做的也是积极地迎接挑战,再次投入攻关和调研,找到适合我们的选择。

这个持续探索的过程,既是对方案的一次次革新,也是提高自我认知和积累技术的过程。一路走来,感慨良多,想起同事常说的一句话:“做难事,必有所得。”

参考文献:

[1]ITMS-90809: Deprecated API Usage - Apple will stop accepting submissions of apps that use UIWebView APIs.https://developer.apple.com/forums/thread/122114

更多精彩,可关注公众号:神策技术社区。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页