阅读 443

Hybrid基本原理

本文原创:wangtiegang

背景介绍

Hybrid App,俗称混合应用,即混合了 Native技术 与 Web技术 进行开发的移动应用。兼具" Native App良好用户交互体验的优势 "和" Web App跨平台开发的优势 "。

Hybrid APP底层依赖于Native提供的容器(UIWebview),上层使用Html&Css&JS做业务开发,底层透明化、上层多多样化,这种场景非常有利于前端介入,非常适合业务快速迭代。

相关方案

Hybrid App,俗称混合应用,即混合了 Native技术 与 Web技术 进行开发的移动应用。现在比较流行的混合方案主要有三种,主要是在UI渲染机制上的不同:

  1. 基于 WebView UI 的基础方案,通过JSBridge完成H5和Native的通信,赋予H5一定的端能力。是一种基于WebView UI的解决方案。
  2. 基于 Native UI 的方案,例如 React-Native、Weex,在赋予 H5 原生API能力的基础上,进一步通过JSbridge将js解析为虚拟DOM传递到Native,并使用原生进行渲染。

架构介绍

通过JSBridge,H5页面可以调用Native的api,Native也可调用H5页面的方法或者通知H5页面回调,从而实现双向通信,如下图

frame.png

技术原理

JavaScript 通知 Native

基于 WebView 的机制和开放的 API, 实现这个功能有三种常见的方案:

  1. API注入,原理其实就是 Native 获取 JavaScript环境上下文,并直接在上面挂载对象或者方法,使 js 可以直接调用,Android 与 IOS 分别拥有对应的挂载方式。具体方法如下:
class JsObject {
   @JavascriptInterface
   public String toString() { return "injectedObject"; }
}
webview.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
复制代码

但是此方法使用需要注意:

  • 在Android <=4.1.2 (API 16),WebView使用WebKit浏览器引擎,并未正确限制addJavascriptInterface的使用方法,在应用权限范围内,攻击者可以通过Java反射机制实现任意命令执行。
  • 在Android >=4.2 (API 17),WebView使用Chromium浏览器引擎,并且限制了Javascript对Java对象方法的调用权限,只有声明了@JavascriptInterace注解的方法才能被Web页面调用。
  1. WebView 中的 prompt/console/alert 拦截,通常使用 prompt,因为这个方法在前端中使用频率低,比较不会出现冲突;

  2. WebView URL Scheme 跳转拦截;

第二三种机制的原理是类似的,都是通过对 WebView 信息冒泡传递的拦截,从而达到通讯的,目前多使用后两种方法。

实现过程如下:

定制协议

通过特定的参数转换方法,将传入的数据,方法名一起,拼接成一个url scheme,如

// 基本有用信息就是后面的callbackId,handlerName与data
API_Name:callbackId/handlerName?param0=xxx&param1=yyy
复制代码

通过上面的定义来对应Native层不同的方法,并传递参数

消息发送

  1. 这里不要使用 location.href 发送,因为其自身机制有个问题是同时并发多次请求会被合并成为一次,导致协议被忽略,而并发协议其实是非常常见的功能。

我们会使用创建 iframe 发送请求的方式。如

//创建隐藏iframe过程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe);

//触发scheme
messagingIframe.src = uri;  
复制代码
  1. 通过调用 onJsAlert(),onJsConfirm(),onJsPrompt(),方法回调拦截对话框消息
var data = {
    action:'xxxx',
    params:'xxxx',
    callback:'xxxx',
};
var jsonData = JSON.stringify([data]);
//发起弹框
window.prompt(jsonData);
复制代码

通常考虑到安全性,需要在客户端中设置域名白名单或者限制,避免公司内部业务协议被第三方直接调用。

消息的拦截

对应上面消息的发送,消息的拦截方法如下:

  1. 客户端可以通过 API 对 WebView 发出的请求进行拦截:

    IOS上: shouldStartLoadWithRequest

    Android: shouldOverrideUrlLoading

当解析到请求 URL 头为制定的协议时,便不发起对应的资源请求,而是 解析参数,并进行相关功能或者方法的调用,完成协议功能的映射

  1. 以onJsPrompt为例,客户端可以通过 API 对 WebView 发出的请求进行拦截:

    IOS上: runJavaScriptTextInputPanelWithPrompt

    Android: WebChromeClient.onJsPrompt

    处理方式同上

参数传递

由于 WebView 对 URL 会有长度的限制,因此常规的通过 scheme参数 进行传递的方式便具有一个问题,即当需要传递的参数过长时,可能会导致被截断,例如传递base64或者传递大量数据时。由于 Native 可以直接调用 JS 方法并直接获取函数的返回值,因此我们只需要对每条协议标记一个唯一标识,并把参数存入参数池中,到时客户端再通过该唯一标识从参数池中获取对应的参数即可。

协议回调

当消息发送到Native 层后,我们便需要处理对应的回调,同样需要的处理过程:

  1. js 向业务层发送消息,并通过 window.addEventListener 注册唯一的自定义事件,并绑定到对应的事件上
  2. Native 层收到后执行相应的业务方法
  3. Native 层执行完相应的业务,调用Bridge的dispatch 方法,发送到js层
  4. js 层获取传回的数据,通过 window.dispatchEvent 触发相应的自定义事件,并执行对应的回调

Native 通知 JavaScript

由于 Native 可以算作 H5 的宿主,因此拥有更大的权限,上面也提到了 Native 可以通过 WebView API直接执行 Js 代码。

// IOS
webview.stringByEvaluatingJavaScriptFromString("alert('NativeCall')")

// Android 4.4-
webView.loadUrl("javascript:JSBridge.trigger('NativeCall')")

// Android 4.4+
mWebView.evaluateJavascript("javascript:JSBridge.trigger('NativeCall')", 	 new ValueCallback<String>() {
    @Override
    public void onReceiveValue(String value) {
        //此处为 js 返回的结果
    }
});
复制代码

android系统在低于4.4时,evaluateJavascript 是无法使用的,因此单纯的使用 loadUrl 无法获取 JS 返回值,这时我们需要使用前面提到的 prompt 的方法进行兼容,让 H5端 通过 prompt 进行数据的发送,客户端进行拦截并获取相应数据

离线资源

在线获取h5资源总会遇到网络问题,对于一些不会经常改变的资源放到本地缓存,可以提高访问速度和稳定性。

通常来说,H5资源分为两种,经常更新的业务代码不经常更新的框架、库代码和公用组件代码,为了实现离线资源的共享,在H5打包时可以采用分包的策略,将公用部分单独打包,在本地也是单独存放

为了实现同一资源的线上和离线访问, Native 层需要对 H5 请求的资源进行判断,到底是从线上获取还是本地获取,这里我们暂时称之为 LocalUrlRouter。

接下来就是如何获取离线资源了,我们上面提到了 LocalUrlRouter,它负责线上资源到本地资源映射,我们借鉴已有的映射规则:H5开发完成后会扫描H5项目然后生成一份线上资源和本地资源路径的映射表(source-router.json)。每次请求资源时,LocalUrlRouter 会检查映射表,如果本地有离线资源,则会返回本地资源,否则通过http请求获取线上资源。

还可以完善的地方资源包的更新,映射表对资源的缓存也可以动态的增加等

文章分类
前端
文章标签