AndroidX技术栈之H5容器

Hybrid应用中JS和Native互相调用

1 Native调用JS

这个大家都懂,Android上可以用loadUrl("javascript:xxx"), API level19以后可以用evaluateJavascript,iOS可以用stringByEvaluatingJavaScriptFromString来执行一段js脚本
坑爹的是Android api level19以前,native层是没法拿到js执行结果的。

2 JS调用Native

2.1 addJavascriptInterface

这个大家都懂,将一个Java对象暴露给JS。可惜只有Android有,iOS没有。
用这个方法需要注意恶意页面用反射攻击,比如

jsobj.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs)

android 4.2以后可以用@JavascriptInterface指定要暴露的方法,解决以上问题。

2.1) window.location

JS里设置window.location时,会被shouldOverrideUrlLoading(iOS是delegate的shouldStartLoadWithRequest)拦截到,可以处理后告诉WebView是不是真的变更url。
这个可以和evaluateJavascript/stringByEvaluatingJavaScriptFromString配合,让JS调用Native方法。当JS需要调用一个Native方法的具体步骤如下:
a) js先把回调function挂在window下
b) js把window.location(或者window.location.hash)设置成一个和native层约定好的url,这个url包含本次调用的所有信息,比如方法名字,参数,挂在windows下的回调名字
c) native层在shouldOverrideUrlLoading/shouldStartLoadWithRequest会得到上一步js传过来的url,按照约定解析后执行,让WebView不要真的去加载这个url
d) native执行完后用1)里的方法,调用挂在window下的回调,并将返回值作为参数带给这个回调方法
e) 从window下摘掉回调function,释放资源
PhoneGap是类似这种方式实现的。这种方式暴露给JS的方法都是异步的,如果想要同步调用,比如只是取个状态之类瞬间就能完成的调用,还要写回调,就太麻烦了。

2.2) iframe

这个方式和2.1)有点像,只是JS设置的location不是主frame的,而是在一个子frame上。WebView有个undocumented的特性,它会保证在拦截方法结束前,阻塞js执行,这样就可以实现同步调用。
具体步骤如下:
a) js创建一个隐藏的iframe
b) js将iframe的src设置成约定好的调用信息
c) native层在shouldOverrideUrlLoading/shouldStartLoadWithRequest会得到上一步js传过来的url,按照约定解析后执行,执行完成后用1)里的方法,把返回值挂在window下一个特定的变量上再返回
d) js从window下得这个特定变量拿到返回值
将a-d步骤封装到一个js方法里,就会类似这样:

Native = {
    call:function callNative(method, args){
        var i = document.createElement('iframe');
        i.style.display = 'none';
        i.src = 'native://' + method + '/' + args;
        document.documentElement.appendChild(i); // 注意,不同于主frame的location变化,iframe的location变化后会在shouldOverrideUrlLoading/shouldStartLoadWithRequest返回前阻塞
        i.parentNode.removeChild(i);
        return window.nativereturn;
    }
}

这种方式可以实现同步调用,也可以做成伪异步(实际是同步,只是使用方式异步而已)

2.3) ContentProvider/NSURLProtocol

Android下app可以定义一个ContentProvider(iOS下app可以继承NSURLProtocol来自定义一个协议处理类),JS可以用一个约定好的URL schema来从ContentProvider/NSURLProtocol获取数据或调用方法。
这种方式会造成页面访问跨域,所以没法ajax,只能使用jsonp,所以也不能实现同步调用。如果ContentProvider是公开的,这种方式甚至可以为其他APP提供服务。

2.4) shouldInterceptRequest/NSURLCache

Android下app可以用shouldInterceptRequest拦截WebView的网络资源请求,给WebView任意数据。ios下可以用NSURLCache来覆盖系统的缓存策略。这两个系统API本来是用来订制webview缓存的,也可用来让JS调用Native方法
具体步骤如下:
a) 页面发送一个AJAX/JSONP请求,请求的url是与native约定好的,包含调用信息
b) shouldInterceptRequest拿到url,按照约定解析后执行,并将返回值当做数据回给WebView
c) 页面拿到调用结果
这种方式可以ajax也可以jsonp,所以可以同步,也可以异步

2.5) prompt

WebView/UIWebView允许我们重新定义prompt/alert/confirm的行为,借助这个特性,我们也可以实现JS调用Native方法。
android上重载WebChromeClient的onJsPrompt即可,代码示例如下:

  webview.setWebChromeClient(new WebChromeClient(){
    // 用message传递调用方法和参数
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
      // 这里解析message,调用相应native方法
      result = ... 
      result.confirm(result);
      return true;
    }
  });

iOS上需要扩展UIWebView类,加个Category,定义如下,实现类似上面的android例子:

@interface UIWebView (JavaScriptAlert)
- (NSString *) webView:(UIWebView *)view runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)text initiatedByFrame:(id)frame;
@end

Javascript里可以类似这么调用:

  var value  = promot("native://<method>/<arg1>/<arg2>/...")

这种方式调用也是同步的。
windvane android上是用的这种方法,但是做成了异步方式。

2.6) Build-in http server

这个大家都懂,我们可以在APP里开一个web服务器,用jsonp(如果H5页面也通过这个web服务器下载,也可以ajax)方式调用Native方法。当然在android上也可以为其他app提供数据。
如果没有什么保护措施,这种方法容易被恶意的其他app获取应用数据,因为无法区分是否是当前app发起的调用。解决办法也是有的,比如在页面加载完成时,Native层给它一个token,每个请求都要带上,否则就拒绝服务。恶意app无法获取token,也就没法调用我们的服务了。

results matching ""

    No results matching ""