JSBridge

  • Post author:
  • Post category:其他


JavaScript提供调用Native功能的接口。让混合开发中的前端部分可以方便地使用Native的功能(地址位置、打开摄像头,查看本地相册,指纹支付)

JSBridge是Native和非Native之间的桥梁,他的核心是构建Native和非Native间消息通信的通道(而且这个通信的通道是双向的)。

双向通信的通道:

  • JS向Native发送消息:调用相关的功能、通知Native当前js的相关状态等
  • Native向JS发送消息。回溯调用结果,消息推送。通知js当前Native的状态等

    在这里插入图片描述


H5与Native对比

在这里插入图片描述



JSbridge的实现原理

JavaScript是运行在一个单独的JS context中(例如:webView的Webkit引擎,JSCore),由于这些Context与原生运行环境的天然隔离,我们可以将这种情况与RPC(Remote Procedure Call,远程过程调用)通信进行类比。将Native与JavaScript的每次互相调用看做一次RPC调用

在JSBridge的设计中,可以把前端看做RPC的客户端。把Native端看做RPC的服务端。从而JSBridge要实现的主要逻辑就出现了;通信调用(Native与JS通信)和句柄解析调用。



JSbridge的双向通信原理


JS调用Native的方式


方式一:拦截URL Scheme
  • Android:Webview提供了shouldOverrideUrlLoding方法来提供给Native拦截H5发送的URL Scheme请求
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);
     }
}
  • 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);
}
  • js调用
qunarhy://hy/url?url=ymfe.tech  // 自定义的
protocol是qunarhy,host则是hy

window.webkit.messageHandlers.topicSelect.postMessage(`vdiscover://post_new_note?attach=${JSON.stringify(params)}`)
window.prompt(`vdiscover://post_new_note?attach=${encodeURIComponent(JSON.stringify(params))}`)
  • URL SCHEME是一种类似于url的链接,是为了方便app直接互相调用设计的,形式和普通的url近似,主要区别是protocol和host一般是自定义的。
  • 拦截URL SCHEME的主要流程是:Web端通过某种方式(例如:iframe.src)发送URL Scheme请求,之后Native拦截到请求并更具URL SCHEME(包括所带的参数)进行相关的操作
  • 优点

    • 不存在漏洞问题,使用灵活,可以实现H5和Native页面的无缝切换

      • 场景:适用于快速迭代,快速开发上线,某一链接直接填写H5链接,在对应的Native页面开发完成前先跳转至H5页面。等Native页面开发完成之后再进行拦截,跳转至Native页面,此时H5链接不用修改
  • 缺点:

    • 当使用iframe.src来发送URL Scheme需要对URL长度作控制,使用复杂。速度较慢

      • 有些方案为了避免url长度隐患的缺陷,在ios上采用了使用ajax发送同域请求的方式,并将参数放到head或body中,这样虽然避免了url长度的隐患,但WKWebView并不支持这种方式
      • 为什么选择iframe.src不选择location.herf?(因为如果通过location.href连续调用Native,很容易丢失一些调用)
    • 创建请求需要一定的耗时,比诸如API的方式调用同样的功能耗时更长。


方式二:注入API

基于WebView提供的能力,我们可以向Window上注入对象或方法,js可以直接使用window上的方法,这种方法js需要等到Native执行完对应的逻辑之后才能进行回调里的操作

  • Android的WebView提供了addJavascriptInterface方法,支持Android4.2及以上的系统
gpcWebView.addJavascriptInterface(new JavaScriptInterface(), 'nativeApiBridge'); 
public class JavaScriptInterface {
    Context mContext;

  JavaScriptInterface(Context c) {
    mContext = c;
  }

  public void share(String webMessage){            
    // Native 逻辑
  }
}
  • JS调用:
window.NativeApi.share(xxx);
  • iOS的UIWebView提供了JavascriptScore方法,支持iOS7.0以上的系统,WKWebview提供window.webkit.messageHandlers方法,支持iOS8.0及以上系统。UIWebview在几年前常用,目前不常见
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"];
}
  • js调用示例
    window.webkit.messageHandlers.share.postMessage(xxx);


方式三:重写prompt等原生js方法
  • Android4.2之前注入对象的接口是addJavascriptInterface,但是由于一些安全的原因不被使用,一般会通过修改浏览器的部分window对象的方法来完成操作,主要是拦截alert、confirm、prompt、console.log四个方法。分别被Webview的onJsAlert、OnJsConfirm、onConsoleMessage、onJsPrompt监听
// 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;
}
  • iOS由于安全限制,WKWebView对alert、confirm、prompt等方法都做了拦截,如果通过此方式进行Native与js交互,需要实现WKWebView的三个WKUIDelegate代理方法
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{

  UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];

  [alertController addAction:([UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

      completionHandler();

  }])];

  [self presentViewController:alertController animated:YES completion:nil];

}

使用这种方式时,可以与Android和iOS约定使用传参的格式,h5可以无需识别客户端,传入不同参数直接调用Native即可,剩下的交给客户端自己去拦截相同的方法,识别相同的参数,进行自己的处理逻辑即可实现多端表现一致。

js调用:

alert("确定xxx?", "取消", "确定", callback());

如果能与Native确定好方法名、传参等调用的协议规范,这样其他格式的prompt等方法是不会被识别的,能起到隔离的作用



Native调用JS的方式

Native调用js需要,JavaScript的方法必须在全局的window上即可

Android中主要有两种实现方法:

  • Android 4.4以前,通过loadUrl方法,执行一段js代码来实现

    • loadUrl方式使用起来方便简洁,但是效率低无法获得返回结果且调用的时候会刷新WebView
  • Android 4.4以后可以使用evaluateJavascript方法

    • evaluateJavascript方法效率高获取返回值方便。调用的时候不刷新WebView
webView.loadUrl("javascript:" + javaScriptString);
webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
  @Override
  public void onReceiveValue(String value){
    xxx
  }
});
  • ios在WKWebview中可以通过evaluateJavaScript:javaScriptString来实现,支持iOS8.0及以上系统
// swift
func evaluateJavaScript(_ javaScriptString: String, 
    completionHandler: ((Any?, Error?) -> Void)? = nil)
// javaScriptString 需要调用的 JS 代码
// completionHandler 执行后的回调

// objective-c
[jsContext evaluateJavaScript:@"ZcyJsBridge(ev, data)"]



版权声明:本文为sxm666666原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。