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); }}); }
注意事項: