ToB企服应用市场:ToB评测及商务社交产业平台

标题: iOS全埋点解决方案-APP和H5打通 [打印本页]

作者: 用户国营    时间: 2022-6-23 21:11
标题: iOS全埋点解决方案-APP和H5打通
前言

​        所谓的 APP 和 H5 打通,是指 H5 集成 JavaScript 数据采集 SDK 后,H5 触发的事件不直接同步给服务器,而是先发给 APP 端的数据采集 SDK,经过 APP 端数据采集 SDK 二次加工处理后存入本地缓存再进行同步。
一、App 与 H5 打通原因

1.1 数据丢失率

​        APP 端采集数据的丢失率一般在 1% 左右,而 H5 采集数据的丢失率一般在 5% 左右(主要是因为缓存,网络或切换界面等原因)。因此,如果 APP 与 H5 打通,H5所有事件都可以先发给 APP 端数据采集 SDK,经过 APP 端二次加工处理后并入本地数据库,在符合特定策略后进行数据同步,即可把数据丢失率由 5% 降低到 1% 左右。
1.2 数据准确性

​        众所周知,H5 无法直接获取设备的相关信息,只能通过解析 UserAgeng 获取有限的信息,而解析 UserAgent 值,至少会面临下面的问题。
(1)有些信息通过解析 UserAgent 值根本获取不到,比如应用程序的版本号等。
(2)有些信息通过解析 UserAgent 值可以获取到,但是内容可能不正确。
​        如果 APP 和 H5 打通,由 APP 端数据采集 SDK 补充这些信息,即可确保事件信息的准确性和完整性。
1.3 用户标识

​        对于用户在 APP 端注册或者登录之前,我们一般都是使用用户匿名 ID 来标识用户。而 APP 和 H5 标识匿名用户的规则不一样。进而导致一个用户出现两个匿名 ID 的情况。如果 APP 和 H5 打通,就可以将两个匿名 ID 做归一化处理。
​        APP 和 H5 打通的方案有一下两种。
二、方案一:拦截请求

​        拦截 WebView 发送的 URL 请求,即如果是协定好的特定格式,可进行拦截并获取事件数据。如果不是,让请求继续加载。此时 JavaScript SDK 就需要知道,当前 H5 是在 APP 端显示环视在 Safari 浏览器显示,只有在 APP 端显示时,H5 触发事件后,JavaScript SDK 才能向 APP 发送特定的 URL 请求进行打通;如果是在 Safari 浏览器显示,JavaScript SDK 也发送请求进行打通,会导致事件丢失。对于 iOS 应用程序来说,目前常用的方案是借助 UserAgent 来进行判断,即当 H5 在 APP 端显示时,我们可以通过在当前的 UserAgent 上追加一个特殊的标记,进而告知 JavaScript SDK 当前 H5 是在 APP 端显示并需要进行打通。
2.1 修改 UserAgent

​        我们可以通过下面的方法来修改 UserAgent
  1. - (void)userAgent {
  2.     // 创建一个空的 WKWebView
  3.     self.webView = [[WKWebView alloc] initWithFrame:CGRectZero];
  4.     // 创建一个 self 的弱引用,防止循环引用
  5.     __weak typeof (self) weakSelf = self;
  6.     // 执行 JavaScript 代码,获取 WKWebView 中的 UserAgent
  7.     [self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
  8.         // 创建强引用
  9.         __strong typeof (weakSelf) strongSelf = weakSelf;
  10.         // 执行结果 result 为获取到的 UserAgent 值
  11.         NSString *userAgent = result;
  12.         // 给 UserAgent 追加自己需要的内容
  13.         userAgent = [userAgent stringByAppendingString:@" /sa-sdk-ios "];
  14.         // 将 UserAgent 字典内容注册到 NSUserDefault 中
  15.         [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}];
  16.         // 释放 webView
  17.         strongSelf.webView = nil;
  18.     }];
  19. }
复制代码
第一步:新增 SensorsAnalyticsSDK 的类别 WebView ,并新增 - addWebViewUserAgent: 方法声明
  1. NS_ASSUME_NONNULL_BEGIN
  2. @interface SensorsAnalyticsSDK (WebView)
  3. /// 在 WebView 控件中添加自定义的 UserAgent,用于实现打通方案
  4. /// @param userAgent 自定义的 UserAgent
  5. - (void)addWebViewUserAgent:(nullable NSString *)userAgent;
  6. @end
  7. NS_ASSUME_NONNULL_END
复制代码
  1. [/code]第二步:实现  - addWebViewUserAgent: 方法,并修改 UserAgent 值
  2. [code]#import "SensorsAnalyticsSDK+WebView.h"
  3. #import <WebKit/WebKit.h>
  4. @interface SensorsAnalyticsSDK (WebView)
  5. @property(nonatomic, strong) WKWebView *webView;
  6. @end
  7. @implementation SensorsAnalyticsSDK (WebView)
  8. - (void)loadUserAgent:(void(^) (NSString *))completion {
  9.     // 创建一个空的 WKWebView
  10.     self.webView = [[WKWebView alloc] initWithFrame:CGRectZero];
  11.     // 创建一个 self 的弱引用,防止循环引用
  12.     __weak typeof (self) weakSelf = self;
  13.     // 执行 JavaScript 代码,获取 WKWebView 中的 UserAgent
  14.     [self.webView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
  15.       // 创建强引用
  16.       __strong typeof (weakSelf) strongSelf = weakSelf;
  17.       // 调用回调
  18.         completion(result);
  19.       // 释放 webView
  20.       strongSelf.webView = nil;
  21.     }];
  22. }
  23. - (void)addWebViewUserAgent:(nullable NSString *)userAgent {
  24.     [self loadUserAgent:^(NSString *oldUserAgent) {
  25.         // 给 UserAgent 添加自己的内容
  26.         NSString *newUserAgent = [oldUserAgent stringByAppendingString:userAgent ?: @" /sa-sdk-ios "];
  27.         // 将 UserAgent 字典内容注册到 NSUserDefault 中
  28.         [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": newUserAgent}];
  29.     }];
  30. }
  31. @end
复制代码
​        在上面的代码中,我们实现了一个加载获取 UserAgent 值得私有方法 - loadUserAgent: 方法,该方法通过回调将 UserAgent 值返回。在 - addWebViewUserAgent: 方法中调用 - loadUserAgent: 方法获取到 UserAgent 旧值,然后追加 /sa-sdk-ios 特殊符号,最后把生成的新的 UserAgent 值注册到 NSUserDefaults 中。
2.2 是否拦截

第一步:声明 - shouldTrackWithWebView: request: 方法。
  1. /// 判断是否需要拦截并处理 JavaScript SDK 发送过来的事件数据
  2. /// @param webView 用于界面展示的 WebView 控件
  3. /// @param request 控件中的请求
  4. - (BOOL)shouldTrackWithWebView:(id)webView request:(NSURLRequest *)request;
复制代码
第二步:实现 - shouldTrackWithWebView: request: 方法。
  1. - (BOOL)shouldTrackWithWebView:(id)webView request:(NSURLRequest *)request {
  2.     // 获取请求的完整路径
  3.     NSString *urlString = request.URL.absoluteURL;
  4.     // 查找完整路径中是否包含 sensorsanalytics://trackEvent ,如果不包含,则是普通请求,不做处理,返回 NO
  5.     if ([urlString rangeOfString:SensorsAnalyticsJavaScriptTrackEventScheme].location == NSNotFound) {
  6.         return NO;
  7.     }
  8.    
  9.     NSMutableDictionary *queryItems = [NSMutableDictionary dictionary];
  10.     // 请求中的所有 Query,并解析获取数据
  11.     NSArray<NSString *> *allQuery = [request.URL.query componentsSeparatedByString:@"&"];
  12.     for (NSString *query in allQuery) {
  13.         NSArray<NSString *> *items = [query componentsSeparatedByString:@"="];
  14.         if (items.count >= 2) {
  15.             queryItems[items.firstObject] = [items.lastObject stringByRemovingPercentEncoding];
  16.         }
  17.     }
  18.     // TODO: 采集请求中的数据
  19.     return YES;
  20. }
复制代码
2.3 二次加工 H5 事件

第一步:新增 - trackFromH5WithEvent: 方法,用于对数据进行加工
  1. - (void)trackFromH5WithEvent:(NSString *)jsonString {
  2.     NSError *error = nil;
  3.     NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
  4.     NSMutableDictionary *event = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
  5.     if (error || !event) {
  6.         return;
  7.     }
  8.    
  9.     NSMutableDictionary *properties = [event[@"properties"] mutableCopy];
  10. //    [properties addEntriesFromDictionary:self.automaticProperties];
  11.     event[@"_hybrid_h5"] = @(YES);
  12.    
  13. //    event[@"distinct_id"] = self.loginId ?: self.anonymousId;
  14.    
  15. //    dispatch_async(self.serialQueue, ^{
  16. //        // 打印
  17. //        [self printEvent:event];
  18. //    //    [self.fileStroe saveEvent:event];
  19. //        [self.database insertEvent:event];
  20. //    });
  21. //
  22. //    if (self.database.eventCount >= self.flushBulkSize) {
  23. //        [self flush];
  24. //    }
  25. }
复制代码
第二步:修改 - shouldTrackWithWebView: request: 方法,添加  - trackFromH5WithEvent: 方法调用
  1. - (BOOL)shouldTrackWithWebView:(id)webView request:(NSURLRequest *)request {
  2.     // 获取请求的完整路径
  3.     NSString *urlString = request.URL.absoluteURL;
  4.     // 查找完整路径中是否包含 sensorsanalytics://trackEvent ,如果不包含,则是普通请求,不做处理,返回 NO
  5.     if ([urlString rangeOfString:SensorsAnalyticsJavaScriptTrackEventScheme].location == NSNotFound) {
  6.         return NO;
  7.     }
  8.    
  9.     NSMutableDictionary *queryItems = [NSMutableDictionary dictionary];
  10.     // 请求中的所有 Query,并解析获取数据
  11.     NSArray<NSString *> *allQuery = [request.URL.query componentsSeparatedByString:@"&"];
  12.     for (NSString *query in allQuery) {
  13.         NSArray<NSString *> *items = [query componentsSeparatedByString:@"="];
  14.         if (items.count >= 2) {
  15.             queryItems[items.firstObject] = [items.lastObject stringByRemovingPercentEncoding];
  16.         }
  17.     }
  18.     //
  19.     [self trackFromH5WithEvent:queryItems[@"event"]];
  20.    
  21.     return YES;
  22. }
复制代码
2.4 拦截

在 - webView: decidePolicyForNavigationAction: 代理方法中进行拦截
  1. #pragma mark - WKNavigationDelegate
  2. - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
  3.     if ([[SensorsAnalyticsSDK sharedInstance] shouldTrackWithWebView:webView request:navigationAction.request]) {
  4.         return decisionHandler(WKNavigationActionPolicyCancel);
  5.     }
  6.     decisionHandler(WKNavigationActionPolicyAllow);
  7. }
复制代码
2.5 测试验证

三、方案二:JavaScript 与 WebView 相互调用

​        实现原理:在 WKWebView 控件初始化之后,通过调用 webView.configuration.userContentController 的 - addScriptMessageHandler:name: 方法注册回调,然后实现 WKScriptMessageHandler 协议中的 -userContentController:didReceiveScriptMessage: 方法,JavaScript SDK 通过 window.webkit.messageHandlers..postMessage()方式触发事件,我们就能在回调中接受到消息,然后从消息中解析事件信息,在调用 trackFromH5WithEvent:方法即可实现。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4