JS與Native之間的通信
JS與Native之間的通信方式有以下 5種方式:
- 通過js對話框執行語句從JS到NATIVE通信,以及回調NATIVE對話框(JS->Native->JS)
- 通過對頁面的導向document.location重新指定URL(JS->Native)
- 通過Android的javascriptInterface接口(JS->Native)
- 通過loadurl加載網頁或執行html/javascript的語句(Native->JS)
- 通過XHR的方式訪問NATIVE的ServerSocket,(JS->Native)
通過js對話框執行語句從JS到NATIVE通信,以及回調NATIVE對話框
JS的alert,confirm,promt的JS代碼呼出後,android的webview的代理叫做WebChromeClient類,裏面就有響應的HOOKalert confirm promt對話框的方法,分別是 onJsAlert,onJsConfirm,onJsPromt。參數message是傳入的數據,defaultValue則可以定義成內部的約定的關鍵字,根據關鍵字就可以判斷是否需要獨特處理的數據
下面這個是Android WebChromeClient#onJsPromt方法
@Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { boolean result = true; if(defaultValue.equals("customize:")) { result = false; } return result; }
document.location重新指定URL
頁面document.location被重新設置後,android的webview的代理叫做WebViewClient類,裏面將有響應location被重置後的方法是shouldOverrideUrlLoading。
其中url就是重置後的新的URL數據。URL的shema可以是file:///,https:// http:// ftp等,也可以是自己定義的shema,通過解析這個shema,也可以判斷是否需要獨特處理的數據。
下面這個是Android WebViewClient#shouldOverrideUrlLoading的方法
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { boolean result = true; URI u = null; try { u = new URI(url); } catch (URISyntaxException e) { return result; } if(u.getScheme().compareTo("customize:") == 0) { } else if (u.getScheme().compareTo("http://") == 0) { } else if(u.getScheme().compareTo("file://") == 0) { } return result; }
javascriptInterface接口
Android的webview類,設置javascript訪問允許設置後,提供一個java的類給javascript直接調用。
Android的Webview的代碼
WebSettings webSettings = mWebView.getSettings(); webSettings.setJavaScriptEnabled(true); mWebView.addJavascriptInterface(new javascriptInterface(), "interface"); class javascriptInterface { public void calledByJavascript(){ } }
Javascript端:window.interface.calledByJavascript();
不過,JavascriptInterface接口因爲直接調用android的本地代碼,因此可以被惡意的js隨意注入,存在很大的安全問題,Android4.2以後有對所暴露的接口必須帶有@JavascriptInterface註釋語句,沒有這個註釋的區域則不能被js訪問。通過loadurl加載網頁或執行html/javascript的語句
Android的組件webview有個loadurl的方法,該方法可以實現2個操作
- url爲http file 的sheme的時候,加載整個網頁
- url爲javascript:開頭的時候,運行Webview裏面加載的網頁運行,javascript:後面的內容。
這裏所提及的就是第二種方式,用loadurl將java的信息傳遞給js,因此,javascript:後面可以加上function,也可以加上屬性賦值。
loadurl爲異步方法。
通過XHR的方式訪問NATIVE的ServerSocket
前面一篇文章提到XHR是可以向服務器,或ASP等網頁進行訪問加載的,因此,當NATIVE裏面使用了ServerSocket,則可以相互通信。
PhoneGap對於本地文件的通信就是用XHR的方式,讀取回調函數列表。
借用PhoneGap裏面JS端和ServerSocket的方法,來舉例說明
JS端:
JSCallback = function() { var xmlhttp = new XMLHttpRequest(); // Callback function when XMLHttpRequest is ready xmlhttp.onreadystatechange=function(){ if(xmlhttp.readyState === 4){ // If callback has JavaScript statement to execute if (xmlhttp.status === 200) { // Need to url decode the response var msg = decodeURIComponent(xmlhttp.responseText); setTimeout(function() { try { var t = eval(msg); } catch (e) { // If we're getting an error here, seeing the message will help in debugging console.log("JSCallback: Message from Server: " + msg); console.log("JSCallback Error: "+e); } }, 1); } // If callback ping (used to keep XHR request from timing out) else if (xmlhttp.status === 404) { } // If security error else if (xmlhttp.status === 403) { console.log("JSCallback Error: Invalid token. Stopping callbacks."); } // If server is stopping else if (xmlhttp.status === 503) { console.log("JSCallback Error: Service unavailable. Stopping callbacks."); } // If request wasn't GET else if (xmlhttp.status === 400) { console.log("JSCallback Error: Bad request. Stopping callbacks."); } // If error, revert to polling else { console.log("JSCallback Error: Request failed."); } } }; //PhoneGap.JSCallbackPort PhoneGap.JSCallbackToken 都是從native端獲取過來 xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken/ , true); xmlhttp.send(); };
Native的ServerSocket端,
try { this.active = true; String request; ServerSocket waitSocket = new ServerSocket(0); this.port = waitSocket.getLocalPort(); this.token = java.util.UUID.randomUUID().toString(); while (this.active) { Socket connection = waitSocket.accept(); BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()),40); DataOutputStream output = new DataOutputStream(connection.getOutputStream()); request = xhrReader.readLine(); String response = ""; if (this.active && (request != null)) { if (request.contains("GET")) { // Get requested file String[] requestParts = request.split(" "); // Must have security token if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) { // Wait until there is some data to send, or send empty data every 10 sec // to prevent XHR timeout on the client synchronized (this) { while (this.empty) { try { this.wait(10000); // prevent timeout from happening break; } catch (Exception e) { } } } // If server is still running if (this.active) { // If no data, then send 404 back to client before it times out if (this.empty) { //System.out.println("CallbackServer -- sending data 0"); response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space } else { //System.out.println("CallbackServer -- sending item"); response = "HTTP/1.1 200 OK\r\n\r\n"; // 回調的內容在getJavascript的返回值裏添加 String js = this.getJavascript(); if (js != null) { response += encode(js, "UTF-8"); } } } else { response = "HTTP/1.1 503 Service Unavailable\r\n\r\n "; } } else { response = "HTTP/1.1 403 Forbidden\r\n\r\n "; } } else { response = "HTTP/1.1 400 Bad Request\r\n\r\n "; } //System.out.println("CallbackServer: response="+response); //System.out.println("CallbackServer: closing output"); output.writeBytes(response); output.flush(); } output.close(); xhrReader.close(); } } catch (IOException e) { e.printStackTrace(); } this.active = false;
Native端創建ServerSocket,當JS通過XHR請求的時候,ServerSocket則將返回內容設置到response裏面去。當XHR的onreadystatechange的狀態爲4並且狀態碼爲200的時候,就可以通過responseText獲取到Native傳給JS的內容了。