JSBridge混合开发的桥梁

我心飞翔 分类:javascript

简介

  1. 近些年,移动端普及化越来越高,开发过程中选用 Native 还是 H5 一直是热门话题。Native 和 H5 都有着各自的优缺点,为了满足业务的需要,公司实际项目的开发过程中往往会融合两者进行 Hybrid 开发。Native 和 H5 分处两地,看起来无法联系,那么如何才能让双方协同实现功能呢?

  2. 所以出现了 JSBridge ,顾名思义,JS 桥梁,它是用 JS 实现 H5 和 Native 之间双向通行的方法统称。

  3. JSBridge 主要提供了 JS 调用 Native 代码的能力,实现原生功能如查看本地相册、打开摄像头、指纹支付等。

H5 和 Native 对比

功能 H5 Native
稳定性 调用系统浏览器内核,稳定性较差 使用原生内核,更加稳定
受网速 影响 较大 较小
用户体验 功能受浏览器限制,体验有时较差 原生系统 api 丰富,能实现的功能较多,体验较好
流畅度 有时加载慢,给用户“卡顿”的感觉 加载速度快,更加流畅

| 可移植性 | 兼容跨平台跨系统,如 PC 与 移动端,iOS 与 Android | 可移植性较低,对于 iOS 和 Android 需要维护两套代码

|

总结:原生用户体验方面比 H5好, 但是 H5更加灵活,所以我们通过 JSBridge 可以结合两者的优点。

手机上展示 H5 页面

我们要在手机上展示 H5 页面,那么会需要一个容器,一个载体,根据我们的尝试,我们会想到 iframe 和 webview,那么应该选择 iframe 还是 webview 呢。
**iframe **是一个 HTML 标签,浏览器识别到这个标签,会为它创建一个视图区域,来展示其中链接的内容。

**webview **以Android为例。在Android手机中内置了一款高性能webkit内核浏览器,在SDK中封装为一个叫做WebView组件。他可以直接用来加载 HTML 。

小结: 我们可知,iframe 适用于 PC Web 端,他需要浏览器的支持。而 webview 是手机内置的一个控件,可以很好的支持 HTML 的渲染,而且有丰富的 API 供我们使用,所以我们一般 JSBridge 中都是使用 webview 作为容器。

JSBridge 的双向通信

既然是双向通行,那么就会分为 H5 向 Native 发送请求,和 Native 向 H5 发送请求。

H5 向 Native 发送请求

1. 拦截 URL Scheme

url scheme 是一种类似于 url 的链接,是为了方便 app 直接互相调用设计的。
      具体为,可以用系统的OpenURI打开一个类似于url的链接(可拼入参数),然后系统会进行判断,如果是系统的url scheme,则打开系统应用, 否则找看是否有app注册这种scheme,打开对应app
       需要注意的是,这种scheme必须原生app注册后才会生效,如微信的scheme为(weixin://)
格式如下

jsbridge://showToast?text=hello
 

Android 和 iOS 都可以通过拦截 URL Scheme 并解析 scheme 来决定是否进行对应的 Native 代码逻辑处理。我们对请求的格式进行判断:如果符合我们自定义的 URL Schema ,对 URL 进行解析,拿到相关操作、操作,进而调用原生 Native 的方法如果不符合我们自定义的 URL Schema ,我们直接转发,请求真正的服务

Web发送 URL 请求的方法

  1. a 标签
  2. location.href
  3. 使用 iframe.src
  4. 发送 ajax 请求

这些方法, a 标签需要用户操作 location.href 可能会引起页面的跳转丢失调用,发送 ajax 请求 Android 没有相应的拦截方法,所以使用 iframe.src 是经常会使用的方案

拦截方法

Android Webview提供了shouldOverrideUrlLoading Api,来提供给 Native 拦截 H5 发送的 URL Scheme 请求。

这种方式从早期就存在,兼容性很好,但是由于是基于URL的方式,长度受到限制而且不太直观,数据格式有限制,而且建立请求有时间耗时。
代码如下

public class CustomWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
      ......
      // 场景一: 拦截请求、接收 scheme
        if (url.equals("xxx")) {

            // handle
            ...
            // callback
            view.loadUrl("javascript:setAllContent(" + json + ");")
            return true;
        }
        return super.shouldOverrideUrlLoading(url);
     }
}
 

小结:

  1. 首先要确定通信格式,形成一个映射,通过拦截你的 Url ,触发不同的 Api。

2.向 Webview 注入 JS API

这个方法会通过 webView 提供的接口,App 将 Native 的相关接口注入到 JS 的 Context(window)的对象中,一般来说这个对象内的方法名与 Native 相关方法名是相同的,Web 端就可以直接在全局 window下使用这个全局 JS 对象,进而调用原生端的方法。

Android(4.2+)提供了 addJavascriptInterface 注入addJavascriptInterface

// 注入全局JS对象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge {
  private Context ctx;
  NativeBridge(Context ctx) {
    this.ctx = ctx;
  }

  // 增加JS调用接口
  @JavascriptInterface
  public void showNativeDialog(String text) {
    new AlertDialog.Builder(ctx).setMessage(text).create().show();
  }
}
 

在 Web 端调用。

window.NativeBridge.showNativeDialog('hello');
 

小结:addJavascriptInterface方式有安全漏洞,所以渐渐被抛弃。

3.重写 prompt 等原生方法

Android 4.2 之前注入对象的接口是 addJavascriptInterface ,但是由于安全原因慢慢不被使用。一般会通过修改浏览器的部分 Window 对象的方法来完成操作。主要是拦截 alert、confirm、prompt、console.log 四个方法,分别被Webview的 onJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt 监听。

public boolean onJsPrompt(WebView view, String origin, String message, String         defaultValue, final JsPromptResult result) {
  String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message,             defaultValue);
  xxx;
  return true;
}
 

使用该方式时,可以与 Android 和 iOS 约定好使用传参的格式,这样 H5 可以无需识别客户端,传入不同参数直接调用 Native 即可。剩下的交给客户端自己去拦截相同的方法,识别相同的参数,进行自己的处理逻辑即可实现多端表现一致,另外,如果能与 Native 确定好方法名、传参等调用的协议规范,这样其它格式的 prompt 等方法是不会被识别的,能起到隔离的作用。

Native 向 H5 发送请求

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

Android 实现

  1. loadUrl 方法使用起来方便简洁,但是效率低无法获得返回结果且调用的时候会刷新 WebView 。
webView.loadUrl("javascript:" + javaScriptString);
 
  1. evaluateJavascript 方法效率高获取返回值方便,调用时候不刷新 WebView,但是只支持 Android 4.4+。
webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value){
    xxx
  }
});
 

IOS 实现

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

// swift
func evaluateJavaScript(_ javaScriptString: String, 
    completionHandler: ((Any?, Error?) -> Void)? = nil)
// javaScriptString 需要调用的 JS 代码
// completionHandler 执行后的回调
 

小结:实际上是用来一个类似 JSONP 的方式。

回复

我来回复
  • 暂无回复内容