Hybrid App開發模式中, IOS/Android 和 JavaScript相互調用方式

IOS:Objective-C 和 JavaScript 的相互調用

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

Objective-C調用JavaScript

UIWebView有個方法是: - (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script 能夠直接調用js。例如你想獲取頁面document的clientHeight屬性,這樣寫: NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.documentElement.clientHeight"]];html

若是想調用頁面的一個叫xxx的函數,則只須要 [webview stringByEvaluatingJavaScriptFromString:@"xxx()"]java

這種調用有一個限制條件: JS代碼佔用的內存 < 10M。

 

Javascript調用Objective-C

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

第2步 協商url格式以及參數傳遞方式

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:Java 和 JavaScript 相互調用

在Android 4.2以前可使用addJavascriptInterface方式注入原生Java方法給JavaScript調用, 這種方案有必定的安全風險,在頁面中執行一些不可信的Javascript代碼便可能控制用戶的手機,
所以在Android 4.2以後Android提供了@JavascriptInterface對象注入的方式創建Javascript對象和android原生對象的綁定,提供給javascript調用的函數必須帶有@JavascriptInterface。本文以@JavascriptInterface爲例,講解一下Android:Java和JavaScript之間相互調用的方法。

 

第一步: 加載本地html文件

有的時候咱們在使用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"); }

Javascript調用Java方法

以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調用有返回值Java函數

若是想從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調用Javascript方法

Java在調用js的時候,使用的是WebView.loadUrl()方法,能夠直接在HTML頁面裏面執行JavaScript方法,首先定義一個Javascript方法給Java調用:

 Java調用有參數無返回值的js函數

function callFromJava(str){ console.log(str); }

Java端調用Javascript方法:

public void javaCallJS(){ webView.loadUrl("javascript:callFromJava('call from java')"); // 能夠在loadUrl中直接給Javascript方法直接傳值 }

 

調用js有參數有返回值的函數

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);
  }});
}

注意事項:

  • 上面限定告終果返回結果爲String,對於簡單的類型會嘗試轉換成字符串返回,對於複雜的數據類型,建議以字符串形式的json返回。
  • evaluateJavascript方法必須在UI線程(主線程)調用,所以onReceiveValue也執行在主線程。
相關文章
相關標籤/搜索