转转Hybrid-SDK重构和实践

转转Hybrid-SDK重构和实践
大前端 @ 转转

转转的移动端开发体系主要是基于Hybrid方案,但长久以来Webview容器和SDK管理等存在标准不统一、更新不及时的问题。随着转转/找靓机/采货侠等多环境开发场景越来越多,适配不同场景极大的影响了业务迭代效率。所有我们决定重新规划SDK的建设。在介绍方案之前。先了解一下基础知识。

JSBridge 的双向通信原理

JSBridge 是一种 JS 实现的 Bridge,连接着桥两端的 Native 和 H5。它在 APP 内方便地让 Native 调用 JS,JS 调用 Native ,是双向通信的通道。JSBridge 主要提供了 JS 调用 Native 代码的能力,实现原生功能如查看本地相册、打开摄像头、指纹支付等

一、JS调用Native

JS 调用 Native 的实现方式较多,目前主流采用是拦截URL Scheme、MessageHandler。

1、拦截 URL Scheme

Web端采用创建隐藏的iframe进行Scheme请求, Android 和 iOS 可以通过拦截 URL Scheme 并解析 Scheme 来是否进行对应的 Native 代码逻辑处理。

Android端,Webview 提供了 shouldOverrideUrlLoading 方法来进行拦截 H5 发送的 URL Scheme 请求。代码如下:

public boolean shouldOverrideUrlLoading(WebView view, String url){
	//读取到url后自行进行分析处理
	
	//如果返回false,则WebView处理链接url,如果返回true,代表WebView根据程序来执行url
	return true;
}
复制代码

iOS 的 WKWebview 可以根据拦截到的 URL Scheme 和对应的参数执行相关的操作。代码如下:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    if ([navigationAction.request.URL.absoluteString hasPrefix:@"xxx"]) {
        [[UIApplication sharedApplication] openURL:navigationAction.request.URL];
    }
    decisionHandler(WKNavigationActionPolicyAllow);
}
复制代码

Web端通过动态的创建iframe和客户端通讯

function iosExecute (action, param) {
  param['methodName'] = action
  let iframe = env.createIframe()
  let paramStr = JSON.stringify(param)
  iframe.src = `zznative://zhuanzhuan.hybrid.ios/?infos=${encodeURIComponent(paramStr)}`
  document.body.appendChild(iframe)
  setTimeout(() => iframe.remove(), 300)
}
复制代码

2、MessageHandler

基于 Webview 提供的能力,我们可以向 Window 上注入对象或方法。JS 通过这个对象或方法进行调用时,执行对应的逻辑操作,可以直接调用 Native 的方法。使用该方式时,JS 需要等到 Native 执行完对应的逻辑后才能进行回调里面的操作。

Android 的 Webview 提供了 addJavascriptInterface 方法,支持 Android 4.2 及以上系统。

gpcWebView.addJavascriptInterface(new JavaScriptInterface(), 'nativeApiBridge'); 
public class JavaScriptInterface {
	Context mContext;

  JavaScriptInterface(Context c) {
    mContext = c;
  }

  public void share(String webMessage){	    	
    // Native 逻辑
  }
}

复制代码

iOS 的 UIWebview 提供了 JavaScriptScore 方法,支持 iOS 7.0 及以上系统。WKWebview 提供了 window.webkit.messageHandlers 方法,支持 iOS 8.0 及以上系统。UIWebview 在几年前常用,目前已不常见。以下为创建 WKWebViewConfiguration 和 创建 WKWebView 示例:

WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKPreferences *preferences = [WKPreferences new];
preferences.javaScriptCanOpenWindowsAutomatically = YES;
preferences.minimumFontSize = 40.0;
configuration.preferences = preferences;
    
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"share"];
  	[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"pickImage"];
}
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.webView.configuration.userContentController 	removeScriptMessageHandlerForName:@"share"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"pickImage"];
}
复制代码

Web端通过调用客户端注入的全局变量进行通讯

window.zhuanzhuanMApplication.executeCmd(action, oriParam)
复制代码

二、Native 调用 JS

Native 调用 JS 比较简单,只要 H5 将 JS 方法暴露在 Window 上给 Native 调用即可

Android 中主要有两种方式实现。在 4.4 以前,通过 loadUrl 方法,执行一段 JS 代码来实现。在 4.4 以后,可以使用 evaluateJavascript 方法实现。loadUrl 方法使用起来方便简洁,但是效率低无法获得返回结果且调用的时候会刷新 WebView。evaluateJavascript 方法效率高获取返回值方便,调用时候不刷新WebView,但是只支持 Android 4.4+。相关代码如下:

webView.loadUrl("javascript:" + javaScriptString);
webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value){
    xxx
  }
});
复制代码

iOS 在 WKWebview 中可以通过 evaluateJavaScript:javaScriptString 来实现,支持 iOS 8.0 及以上系统。

[jsContext evaluateJavaScript:@"zhuanzhuanMApplication(ev, data)"]
复制代码

开源通讯方案介绍

上面讲完了原理。但是自己开发一套完整和好用的还是比较麻烦,现在我们就来介绍一下现在比较好的开源解决方案DSBridge。

DSBridge的主要特点:

Android、IOS、Javascript 三端易用,轻量且强大、安全且健壮。

  1. 同时支持同步调用和异步调用
  2. 支持以类的方式集中统一管理API
  3. 支持API命名空间
  4. 支持调试模式
  5. 支持API存在性检测
  6. 支持进度回调:一次调用,多次返回
  7. 支持Javascript关闭页面事件回调
  8. 支持Javascript 模态/非模态对话框
  9. 支持腾讯X5内核

还有一点可以介绍一下。dsBridge如果和Fly.js一起使用。可以直接使用客户端的通讯能力。正如我们所知,在浏览器中,ajax请求受同源策略限制,不能跨域请求资源。然而, Fly.js 有一个强大的功能就是支持请求重定向:将ajax请求通过任何Javascript bridge重定向到端上,并且 Fly.js 官方已经提供的 dsBridge 的 adapter, 可以非常方便的协同dsBridge一起使用。由于端上没有同源策略的限制,所以 fly.js可以请求任何域的资源。另一个典型的使用场景是在混合APP中,由于Fly.js 可以将所有ajax请求转发到端上,所以,开发者就可以在端上进行统一的请求管理、证书校验、cookie管理、访问控制等。

转转面临的问题

介绍完基础原理和优秀的开源方案,现在来看看转转面临的问题。

1、通信协议不统一

转转使用自定义的通讯方案,有200个API,找靓机采用了dsbrige通讯方案有100个API

2、URL参数含义和UA不统一

两端都有很多的URL参数和UA参数,没有一个统一的规范

3、找靓机Webiview分新老两个版本

在找靓机合并到转转时,我们做了技术栈统一。把转转的Webview迁移到找靓机,但是找靓机业务大量使用的是老的Webview,所以为了保证老的业务能正常的运行,只能保持两套Webview并行使用

4、存在两套文档,不是很好的找到API

5、SK文档维护问题

转转的SDK文档为了保证准确性和有人维护。把SDK设计成一个对象。如果需要添加新的SDK需要通知维护人员添加。然后发版还能使用,文档和使用的测试Demo由维护人员编写。但是这样就造成SDK的体积越来越大。而且当客户端的同学发现一个文档错误,他们不能直接的修改文档,需要把问题反馈,还能修改。综上,导致SDK文档维护成本巨大。

基于此,我们和客户端团队成立了标准Webview小组,打造转转标准Webview容器。

转转解决方案

1、标准化相关: 客户端Cookie管理、Url Query管理 、UA规范、通讯格式规范

我们整理了所有的cookie,url, ua, 通讯方案,并制定了规范 image

UA的标准

App 的 UA 分为两个层面:

公共 UA:使用 zz{Key}/{Value} 的形式添加一个键值信息,Key首字母大写,中间有空格分隔,末尾不包含空格。

自定义 UA: APP 内自定义格式。

公共 UA 与 自定义 UA 不做顺序要求,但公共 UA 中的 zzApp 字段一定是最后一个。

zzVersion/客户端版本号 zzT/客户端终端值 zzDevice/是否支持顶栏穿透_状态栏高度_设备像素比例 zzApp/App标识符

举例:zzVersion/8.18.20 zzT/16 zzDevice/1_44.0_3.0 zzApp/58ZhuanZhuan
复制代码

Cookie的标准

Cookie的诞生背景:由于 HTTP 协议是无状态的,网站为了辨别用户身份向用户本地终端上储存数据,这就是Cookie。

所以从Cookie设计上来讲,它是为了解决服务端在客户端保存数据问题的,这个存储容器的主动权应当在服务端,客户端不应过多干预,即使客户端拥有修改Cookie的 api,但除非用户清除缓存时,可以使用 cookie.clear() 方法之外,其余情况不建议对 Cookie进行修改。

其次,cookie 会被附加在每个可以被携带的 HTTP 请求中。

所以如果客户端想要给 H5 传值,不建议采用通过Cookie的方式,因为这些 Cookie 会原封不动的携带给服务端,占用请求头部大小,造成流量浪费。

基于上面的考虑和调研,我们制定了Cookie的标准

  • 客户端理论上不再向 WebView 增加新的 Cookie

  • 只有通过了 Cookie 白名单的 Url 才会写入 Cookie

  • Cookie 某一条目不存在、条目的 value 为空或空串时,该条目应视为无效,客户端的无效条目可能为以上三种的任一形式

  • 客户端启动一个 WebView 页面时,在请求首个 Url 之前,会写入 Cookie ,如果该 Cookie 条目已存在则覆盖

Url Query 标准

Url Query 主要为了解决H5在执行 JS 方法之前的这段时间里,页面的样式、能力问题。例:加载 H5 页面时,希望页面背景为黑色,如果依赖 Api 实现,则执行 Api 之前页面为非黑色,效果不理想。

H5 加载失败后,容器层也能够进行设置的一些配置项(不建议在失败页面上支持过于丰富的配置项,失败页面仅支持少部分配置项即可)。例: H5 即使加载失败了,页面也可以侧滑关闭。

基于上面的考虑和调研,我们制定了Url Query的标准

  • 其余能依赖 Api 解决的问题,不建议放到 Url Query 中。

  • Url Query Name 规范,定义时按照驼峰式,但客户端读取时按照大小写不敏感方式读取。

  • Url Query 匹配规范 遵守 Url 标准规范,如 # 后边的参数不应进行识别。

  • 不使用字符串匹配的方式,如 key = 13 使用 key = 1 会误匹配。

通讯格式规范

之前的通讯协议不太规范,所以我们把之前的Api作为V1,之后新增的Api使用V2。通讯格式分为 V1 版本和 V2 版本,V1 V2 不兼容,当前已经按照 V1 标准开发的 Api 不升级至 V2 版本,V1 V2 标准会长期共存。

协议分为两层:框架层和业务层。普通的业务开发不需要关注框架层state部分,state是SDK和客户端沟通的字段,用来展示一些异常,比如客户端没有这个方法等,框架层字段等其他逻辑对于业务层来说是透明的 使用code来判断状态,msg是提示信息,data是返回的数据。

callback('state', '{code: "", msg: "", data: {}}')
复制代码

框架层字段(v1 版本使用自然数风格,v2 版本采用 http 状态码风格)

image

业务层通用字段

值(String)说明
0业务成功
-1业务失败
-1001参数不合法(如缺少必填参数)

2、SDK重构,实现通讯层,告别频繁的发版

只保留通讯层比较简单,但是如果兼容历史的300个Api是比较难的问题,我们的解决方案是老的还是保留,所有的新的使用新的Api通讯方案,慢慢的把老的不标准的Api迁移到新的通讯方案

3、SDK文档管理后台,保证了文档的及时更新

image

文档使用VuePress生成。通过管理后台编辑数据,然后动态的生成md文档。执行VuePress的命令。通过脚手架命令把文档上传到线上。

image

基于上面的优化我们搭建转转移动开发平台网站,方便FE快速找到相关资料文档 image

展望

做完上面的事情,我们还是有很多的挑战没有做。

1、SDK进一步标准化

老的Api能力不标准。比如分享就有5个不同的Api,我们需要整合成一个完整的share。把所有的Api都标准化是一个工作量巨大的事情,所有只能依靠后面的规范来一点一点做。

2、用户全链路监控完整监控

现在的SDK的监控是前端和客户端分开做了。后面准备做全链路监控

3、沉淀Webview性能优化方案,降低接入成本,大范围业务覆盖

现在的性能优化手段比较多。但是接入成本还是比较好。需要改动代码。准备在后面降低接入成本。

分类:
前端
分类:
前端