JSBridge——如何与原生打交道

导语

随着移动端领域的快速发展,业务场景愈发复杂,为减少开发量和投入成本,混合应用(Hybrid App)占据了不少市场。混合应用承载H5页面的容器就是webview,前端人员在开发过程中或多或少都需要与原生(Native App)之间交互,这个交互的桥梁就叫做JSBridge。同样也有一套通用的调试方法来确保JSB通信顺利。

什么是WebView

WebView是原生应用用来展示网页的view组件,本质上就是一款内置了webkit内核的无头浏览器(headless browser),提供了例如页面前进后退、放大缩小、音频控制等一般浏览器具备的功能。

可以将WebView想象成html里面的iframe标签,h5页面与原生应用的通信过程就像是iframe父子页面之间的通信,只不过iframe使用postMessage和message事件处理,而WebView使用的是JSBridge。

什么是JSBridge

JSBridge是支持原生应用与H5应用双向通信的“桥梁”。
不支持在 Docs 外粘贴 block

  • 原生->h5:向h5应用通知原生应用的相关状态,触发h5页面的内容更新、消息发送、音频播放等
  • h5->原生:向原生应用通知h5应用需要使用的功能,比如使用摄像头、使用gps、唤起app等
    因为原生应用与h5应用各自运行在独立的环境中,他们通信的方式就像前端JSONP跨域请求一样,通过原生暴露的一些特性让双端进行联系。
    不支持在 Docs 外粘贴 block

Android WebView

Android WebView以4.4为分界点,采用了不同的内核:

  • Android 4.4前:基于Webkit内核,H5的很多新特性不支持,且存在适配成本高、不安全、不稳定、耗流量、速度慢、视频播放差、文件能力差等问题。
  • Android 4.4后:基于Chromium内核(google在webkit上的fork出来的项目),很多新的规范被支持,例如WebGL,Canvas2D,CSS3以及其他很多的HTML5特性,同时大大提升了WebView组件的性能。

js调用java

Android Webview共提供过三种JS调用java的接口:

  • JavascriptInterface
  • WebViewClient.shouldOverrideUrlLoading() 【官方已废弃
  • WebChromeClient.onXXX()

JavascriptInterface

这是 Android 提供的 JS 与 Native 通信的官方解决方案。

  • 首先Android RD需要实现一个类给 JavaScript 调用。
public class WebAppInterface {
    
    
    @JavascriptInterface
    public void foo(String str) {
    
    
        // doing something
    }
}
  • 将这个WebAppInterface类添加到 WebView 的 JavaScriptInterface 中。
WebView webView = (WebView) findViewById(R.id.webview); 
// 这里的Android会被当做一个变量,注入到页面的window中。
webView.addJavascriptInterface(new WebAppInterface(this), "Android"); 
  • 最后就可以在 JS 中调用 Native 的方法了。
function callFoo(str: string) {
    
    
    Android.foo(str);
}

WebViewClient.shouldOverrideUrlLoading()

拦截webview内的url变更,例如iframe.src或location.href,若设置WebViewClient且该方法返回true,则可由代码自定义逻辑。

一般采用URL Scheme的方式,它一种类似于URL的链接,原本用于唤起各类app而设计,主要特征是protocol和host一般是自定义的。

所以可以事先约定好特殊格式的 URL Scheme,触发shouldOverrideUrlLoading拦截后,判断其格式是否符合,然后 Native 执行其特定逻辑。

public class CustomWebViewClient extends WebViewClient {
    
    
    @Override
    public boolean shouldOverrideUrlLoading(
      WebView view,
      String url
     ) {
    
    
      if (isJsBridgeUrl(url)) {
    
    
        // JSbridge的处理逻辑
        return true;
      }
      return super.shouldOverrideUrlLoading(view, url);
    }
}

WebChromeClient.onXXX()

WebChromeClient支持监听浏览器的很多方法,比如:

  • Alert -> WebChromeClient.onJsAlert
  • Confirm -> WebChromeClient.onJsConfirm
  • Prompt -> WebChromeClient.onJsPrompt
  • console.xxx -> WebChromeClient.onConsoleMessage
/**
 * 当网页调用alert()来弹出alert弹出框前回调,用以拦截alert()函数
 */
public boolean onJsAlert(WebView view, String url, String message,JsResult result)
/**
 * 当网页调用confirm()来弹出confirm弹出框前回调,用以拦截confirm()函数
 */
public boolean onJsConfirm(WebView view, String url, String message,JsResult result)
/**
 * 当网页调用prompt()来弹出prompt弹出框前回调,用以拦截prompt()函数
 */
 public boolean onJsPrompt(WebView view, String url, String message,String defaultValue, JsPromptResult result) 
 /**
 * 打印 console 信息
 */
 public boolean onConsoleMessage(ConsoleMessage consoleMessage)

以alert为例:

  • H5页面调用alert方法
window.alert(message); 
  • 触发执行WebChromeClient.onJsAlert(),alert方法的入参message就是onJsAlert中的message参数
public class CustomWebChromeClient extends WebChromeClient {
    
    
  @Override
  public boolean onJsAlert(
    WebView view,
    String url,
    String message,
    JsResult result
    ) {
    
    
        // doing something
        // 返回值true,表示拦截成功,false表示拦截失败,继续弹窗
        return true;
  }
}

Java 调用 js

根据android版本,有2种方式:

  • loadUrl:全部支持
  • evaluateJavascript:安卓4.4版本之后

loadUrl(String)

webView.loadUrl("javascript:" + javaScriptString); 

WebView.evaluateJavascript(String, IValueCallback)

webView.evaluateJavascript("javascript:" + javaScriptString, new ValueCallback<String>() {
    
    
    @Override
    public void onReceiveValue(String value) {
    
    
      // 执行完成后的回调
    }
});

IOS WebView

IOS8 以前使用的是UIWebview,但是它存在不少问题:加载速度慢、内存泄漏、内存占用高、超出内存限制会被kill掉或者强制触发gc。感兴趣可阅读《IOS混合应用切换app闪屏bug总结》

所以在WWDC 2014 大会上,推出了WKWebView。它代替了 UIKit 中的 UIWebView 和 AppKit 中的 WebView,提供了统一的跨双平台 API。拥有 60fps 滚动刷新率、内置手势、高效的 app 和 web 信息交换通道、和 Safari 相同的 JavaScript 引擎。

使用messageHandlers 调用swift

  • 设置messageHandler
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"showAlert"];

// 为了避免循环引用,导致控制器无法被释放,还需要移除
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"showAlert"];
  • webview会向window全局对象上注入webkit,调用messageHandlers即可实现调用swift
window.webkit.messageHandlers.showAlert.postMessage()

使用UserScript来注入 JavaScript

WKUserScript 允许在正文加载之前或之后注入到页面中,它以 JavaScript 源码形式初始化,初始化时还可以传入是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。举一个简单例子:

let source = "document.body.innerHTML = 'helloWorld'";
// 注入脚本 在文档加载完成后执行
let userScript = WKUserScript()
let userScript = WKUserScript(source: source, injectionTime: WKUserScriptInjectionTimeAtDocumentStart, forMainFrameOnly: true)
let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)

let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
self.webView =WKWebView(frame: self.view.bounds, configuration: configuration)
    // url scheme
    this.sendMessageQueue.push(msgJSON);

    this._dispatchUrlMsg("" + this.bridgeScheme + this.dispatchMsgPath);
  }
} catch (e) {
    
    
  console.error(e);
}

相关文档

前端工程师所需要了解的WebView
关于shouldOverrideUrlLoading方法的一些考证
理解WebKit和Chromium: WebKit, WebKit2, Chromium和Chrome
WebViewClient.ShouldOverrideUrlLoading Method
IOS混合应用切换app闪屏bug总结

猜你喜欢

转载自blog.csdn.net/sinat_36521655/article/details/114648717
今日推荐