iOS7之前,iOS SDK 并无原生提供 js 调用 native 代码的 API。可是 UIWebView 的一个 delegate 方法使咱们能够作到让 js 须要调用时,通知 native。在 native 执行完相应调用后,能够用stringByEvaluatingJavaScriptFromString 方法,将执行结果返回给 js。这样,就实现了 js 与 native 代码的相互调用。具体让 js 通知 native 的方法是让 js 发起一次特殊的网络请求。使用加载一个隐藏的 iframe 来实现的,经过将 iframe 的 src 指定为一个特殊的 URL,在Objective-C中经过UIWebView的webView:shouldStartLoadWithRequest:navigationType:方法拦截这个跳转,而后经过解析跳转的url获取js须要调用的方法名和参数。javascript
UIWebView有个方法是: - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script 能够
直接调用js。例如你想获取页面document的clientHeight属性,这样写: NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.documentElement.clientHeight"]];
html
若是想调用页面的一个叫xxx的函数,则只须要 [webview stringByEvaluatingJavaScriptFromString:@"xxx()"]
java
iOS里面加载一个网页用的是UIWebView,页面加载是经过UIWebView的一个Delegate:UIWebViewDelegate来通知对应的webview的。而每次点击页面上的连接(或者是加载本页面的地址时) 都会在加载前调用UIWebViewDelegate的一个方法: - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
若是这个方法的返回值是YES的话就继续加载这个请求,若是是NO的话就不加载了。 Javascript调用Objective C代码的秘诀就在这里面。android
第一步. 匹配url格式git
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType { if (request.URL.absoluteString match urlSchemePattern) { [self executeSomeObjectiveCCode]; return NO; } else { return YES; } }
request.URL.absoluteString match urlSchemePattern
这句的意思是: 若是页面的url格式知足某种特定格式, 就不加载那个请求,而是执行Objective-C代码。github
Javascript想要调用Objective-C代码时,Javascript代码就须要和Objective-C协商一个请求的协议,例如:凡是请求的url scheme 是"js-call://"
这样格式开头的就是Javascript须要调用Objective C的代码,再具体点,好比"js-call://user/get" 就是要调用Objective-C 代码中一个getUser的方法的。 若是Javascript须要传递参数给Objective-C, 最简单的方法是像http的query string同样传参数, 例如:"js-call://user/set?uid=1&name=jpx",而后在分析url的时候将query string提取出来传给Objective -C的方法便可。 代码以下:web
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if ([request.URL.absoluteString hasPrefix:@"js-call://user/set"]) { NSDictionary *parameters = [self parseQueryString:request.URL.absoluetString]; [self executeSomeObjectiveCCodeWithParameters:parameters]; return NO; } else if ([request.URL.absoluteString hasPrefix:@"js-call://user/get"]) { NSDictionary *parameters = [self parseQueryString:request.URL.absoluetString]; [self executeSomeObjectiveCCodeWithParameters:parameters]; return NO; } return YES; }
若是Javascript须要调用好几个 Objective C的接口,那么在shouldStartLoadWithRequest的delegate方法里面就会有不少if ... else if分支代码, 此外,解析query string的那部分代码也是重复的,最好的办法是将这一切封装起来,能够定义一个JPXUIWebViewJSBridge方法。objective-c
self.bridge = [[JPXUIWebViewJSBridge alloc] initWithHandler:self]; self.bridge.routines = @[@[@"^js-call://user/set.*$", @"setUser"], @[@"^js-call://user/get.*$", @"getUser"] ];
定义了这套规则以后,只须要好比说在ViewController里面实现一个叫setUser的方法便可: - (void)setUser:(NSDictionary *)parametersFromWeb
, 其中parametersFromWeb就是query string对应的字典!json
而后在 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
只须要这样写就能够了:安全
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSError *error; BOOL canHandleRequest = [self.bridge canHandleRequest:request error:&error]; if (canHandleRequest) { [self.bridge handleRequest:request error:&error]; NSLog(@"error1:%@", [error localizedDescription]); return NO; } else { NSLog(@"error2:%@", [error localizedDescription]); } return YES; }
Javascript调用Objective C时,不少人第一反应就是在a标签里面的href写url调用,例如: <a href="js-call://user/set?uid=1&name=jpx" >测试</a>
, 可是这样的调用会以下的一些问题:
若是咱们连续 2 个 js 调 native,连续 2 次改 <a href>
的话,在 native 的 delegate 方法中,只能截获后面那次请求,前一次请求因为很快被替换掉,因此被忽略掉了。还有这种改url的方式也不太安全。
而合理的作法应该是经过加载一个iframe:
function execute(url) { var iframe = document.createElement("IFRAME"); iframe.setAttribute("src", url); document.documentElement.appendChild(iframe); iframe.parentNode.removeChild(iframe); iframe = null; }
从iOS7开始,咱们可使用JavaScriptCore框架来让咱们的Objective-C代码和JavaScript进行深度交互,简单的说咱们能够在Objective-C代码中访问JavaScript中的变量或调用JavaScript的函数,也能够JavaScript中使用Objective-C的对象和方法
由于 iOS SDK 没有天生支持 js 和 native 相互调用,你们的技术方案都是本身实现的一套调用机制,因此这里面有同步异步的问题。细心的同窗就能发现,js 调用 native 是经过插入一个 iframe,这个 iframe 插入完了就完了,执行的结果须要 native 另外用 stringByEvaluatingJavaScriptFromString 方法通知 js,因此这是一个异步的调用。
而 stringByEvaluatingJavaScriptFromString 方法自己会直接返回一个 NSString 类型的执行结果,因此这显然是一个同步调用。
因此 js call native 是异步,native call js 是同步。在处理一些逻辑的时候,不可避免须要考虑这个特色。
方法。
在Android 4.2以前可使用addJavascriptInterface
方式注入原生Java方法给JavaScript调用, 这种方案有必定的安全风险,在页面中执行一些不可信的Javascript代码便可能控制用户的手机,
所以在Android 4.2以后Android提供了@JavascriptInterface
对象注入的方式创建Javascript对象和android原生对象的绑定,提供给javascript调用的函数必须带有@JavascriptInterface
。本文以@JavascriptInterface为例,讲解一下Android:Java和JavaScript之间相互调用的方法。
有的时候咱们在使用webview开发的时候会使用本地的html文件,在这里为了方便咱们把html文件都放在assets
文件夹中,使用本地加载的方式,不须要server支持。
先定义一个html文件:
<!DOCTYPE html> <html> <body> <h1>this is html</h1> </body> </html>
使用file:///android_asset/index.html
加载到webview中:
private void initView() { webView = (WebView) findViewById(R.id.webView); webView.loadUrl("file:///android_asset/index.html"); }
以Android的Toast的为例,从Javascript代码中调用系统的Toast。
咱们定义一个AndroidToast的Java类,它有一个show的方法用来显示Toast:
public class AndroidToast { @JavascriptInterface public void show(String str) { Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show(); } }
须要对WebView设置一些参数,开启JavaScipt,注册JavascriptInterface:
private void initView() { webView = (WebView) findViewById(R.id.webView); WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); webSettings.setDefaultTextEncodingName("UTF-8"); webView.addJavascriptInterface(new AndroidToast(), "AndroidToast"); webView.loadUrl("file:///android_asset/index.html"); }
addJavascriptInterface
的做用是把AndroidToast类映射为Javascript中的AndroidToast对象。
在Javascript中调用Java代码:
function toastClick(){ window.AndroidToast.show('from js'); }
经过window的属性能够找到Java映射的对象AndroidToast,调用它的show方法。
注意这里传输的数据只能是基本数据类型和string,能够传输string意味着咱们可使用json
传输结构化数据。
若是想从Javascript调的方法里面获取到返回值,只须要定义一个带返回值的@JavascriptInterface
方法:
public class AndroidMessage { @JavascriptInterface public String getMsg() { return "form java"; } }
添加Javascript的映射Webview:
webView.addJavascriptInterface(new AndroidMessage(), "AndroidMessage");
Javascript直接调用Java方法:
function showAlert(){ var str=window.AndroidMessage.getMsg(); console.log(str); }
Java在调用js的时候,使用的是WebView.loadUrl()
方法,能够直接在HTML页面里面执行JavaScript方法,首先定义一个Javascript方法给Java调用:
function callFromJava(str){ console.log(str); }
Java端调用Javascript方法:
public void javaCallJS(){ webView.loadUrl("javascript:callFromJava('call from java')"); // 能够在loadUrl中直接给Javascript方法直接传值 }
Android在4.4以前并无提供直接调用js函数并获取值的方法,因此在此以前,经常使用的思路是 java调用js方法,js方法执行完毕,再次调用java代码将值返回。
1.Java调用js代码
String call = "javascript:sumToJava(1,2)";
webView.loadUrl(call);
2.js函数处理,并将结果经过调用java方法返回
function sumToJava(number1, number2){ window.control.onSumResult(number1 + number2) }
3.Java在回调方法中获取js函数返回值
@JavascriptInterface public void onSumResult(int result) { Log.i(LOGTAG, "onSumResult result=" + result); }
Android 4.4处理
Android 4.4以后使用evaluateJavascript便可。这里展现一个简单的 具备返回值的js方法
function getGreetings() { return 1; }
java代码时用evaluateJavascript方法调用
private void testEvaluateJavascript(WebView webView) { webView.evaluateJavascript("getGreetings()", new ValueCallback<String>() { @Override public void onReceiveValue(String value) { Log.i(LOGTAG, "onReceiveValue value=" + value); }}); }
注意事项: