下文中所有 H5 均指 App 内嵌 H5

打通 App 与 App 内嵌 H5 的行为降维的说其实就是需要统一 App 和 H5 标识用户的 ID。因为 H5 在用户匿名状态下没有一个可靠的用户标识,所以这个需求可以被进一步明确成:使用 App 的用户 ID 去标记 H5 的行为。这样的话我们很自然就可以想到两种思路:

  1. App 将用户 ID 暴露给 H5,H5 使用该 ID 来标记行为
  2. H5 将行为数据转交给 App,App 来修正数据内的用户 ID

1. 思路一的实现

  1. App 一般通过 WebView 的 loadUrl 去加载一个 H5 页面,此时可以把 App 的用户 ID 当作 parameter 拼接在 URL 中,H5 解析 parameter 的值就可以获取到当前 App 的用户 ID。

    // 原先只是简单的 load 目标地址
    //webView.loadUrl("https://www.sensorsdata.cn")
    // 和 H5 的小伙伴约定好 parameter 的 key 之后,在 load 目标地址时拼接上此时 App 的用户 ID
    webView.loadUrl("https://www.sensorsdata.cn" "?distinct_id=" + Utils.getID())
  2. WebView 有一个强大的功能,通过 addJavascriptInterface 可以把 Java 对象的方法暴露给 JavaScript。这样我们封装一个获取用户 ID 的方法暴露出去,H5 就可以获取到。

    // 在 Android 声明需要暴露给 JavaScript 的类与方法,注意这里的 @JavascriptInterface 注释
    class AppWebViewInterface {
        @JavascriptInterface
        public String callApp() { return "this is a HelloWorld from App"; }
    }
    // 初始化 WebView 并且在 loadUrl 之前向页面注入我们的 Java 对象
     webview.getSettings().setJavaScriptEnabled(true);
     webView.addJavascriptInterface(new AppWebViewInterface(), "APP_JS_Bridge");
     webView.loadUrl("https://www.sensorsdata.cn");
    // 在 H5 中,可以通过 JavaScript 使用该对象
    alert(APP_JS_Bridge.callApp)

2. 思路二的实现

实现思路二同样需要 addJavascriptInterface ,只不过把方法的逻辑从返回一个 ID 改成接收行为数据。

class AppWebViewInterface {
    @JavascriptInterface
    public boolean callApp(String data) {
        // 处理 data,这里可以返回一个 boolean 给 JS 以应对更复杂的场景,例如 App 能正常处理数据时返回 true,某些异常情况时返回 false
        return handleData(data);
    }
}
// AppWebViewInterface 类的用法同思路一的第二种实现。

3. 安全策略

Google 官方形容 addJavascriptInterface 为 a power feature,同时指出它所附带的极大潜在风险,提醒我们 “Use extreme care when using this method in a WebView which could contain untrusted content “,毕竟 App 端内的接口暴露出去允许 JavaScript 随意调用这方便的不仅仅是开发者…

一般可以从两方面考虑如何做到官方所说的 “extreme care”:

  1. 恶意的 App 加载我的 H5,模仿正常 App 所暴露的对象和方法,这里需要识别出恶意伪造的 App
  2. 我的 App 加载了恶意的 H5,恶意 H5 模仿正常 H5 获取到 App 数据,这里需要识别出恶意伪造的 H5

处理第一种情况我们可以在正常 App 加载 H5 时给一个特殊的 UA,H5 只有在识别到正确规则的 UA 时再和 App 通信,这样可以在一定程度上达到过滤的效果。

处理第二种情况我们可以只在加载信任的 URL 对应的 WebView 时注入 Java 对象,WebView 加载不被信任的 URL 可以通过 removeJavascriptInterface 去掉之前注入的 Java 对象。并且做好 H5 的安全防护,防止被劫持。

这里的安全策略只列举了一些方便实现、通用的方法,如果您有更好的想法欢迎在神策数据开源社区交流群中讨论。

4. 神策是如何做的

神策目前的打通分为方案一与方案二,其中方案一实现机制对应本文的思路二的实现,方案二实现机制对应本文思路一的第二种实现。并且神策还在打通的接口中加入了跟自身业务相关的数据接收地址校验,以适应更复杂的场景。如果您对源码有兴趣 Android 部分可以参考 SensorsDataAPI 类的 showUpWebView 方法,iOS 部分参考 SensorsAnalyticsSDK 的 showUpWebView 方法,JavaScript 部分在 sensorsdata.full.js 搜索 SensorsData_APP_JS_Bridge 即可看到对应内容。

5. 一些坑

@JavascriptInterface 虽然好用,但需要注意它是 android.webkit 包下类,也就是说在某些浏览器内核下该注解并不被承认。例如 UC 内核的 WebView 不会识别该注解,需要使用 @com.uc.webview.export.JavascriptInterface 替代,而腾讯 x5 内核的 WebView 是支持该注解的。

 

每日一问的答案中可能无法全完解读这个问题,如果您是相关技术专家或者是对本问题有自己的见解,欢迎带着「批判性」的态度阅读,指正其中的不足。