摘要:中和的交互方式在進行交互之前需要我們對進行設置開啟對的支持。定義和相關的交互類和方法,對于方法通過注解進行標注。向添加該,同時為其指定一個名稱,該名稱將會在文件中使用。傳遞的數據中有一個端口號,通過這個端口號作為標示,來調用相應的方法。
隨著H5性能的提升,在我們移動應用開發的過程中,我們會越來越多的在我們的App頁面內嵌入H5頁面,使得App變的更加動態靈活。而H5頁面往往并不是獨立,很多時候需要和native進行交互,調用native的一些方法,或者Web中的一些方法被native所調用。
現在有很多開源的解決方案,比如JSBridge,可以很方便的讓我們進行web頁面和native的交互,其實現是在WebView原有提供的Web和Native通信基礎上做了封裝,由于最近接手工作中用到了JSBridge,借此機會學習了一下,本文先從系統提供的一些WebView和Native交互接口講起,然后對于一個簡單的開源JSBridge 的剖析。
WebView的使用WebView webView= (WebView) findViewById(R.id.webview); webView.loadUrl("file:///android_asset/index.html");
我們可以通過將WebView內嵌在App界面中,來裝載網頁,通過loadUrl,給予一個本地或者遠程的地址,程序執行即可裝載出我們的界面。這里代碼演示的是在assets文件下一個index.html文件,然后通過我們的Webview裝載的。
Android中 JS和Java的交互方式在進行交互之前需要我們對WebView進行設置開啟對JS的支持。
WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true);
Java調用JS
通過WebView的loadUrl()
通過WebView的evaluateJavascript()
JS調用Java
通過WebView的JavascriptInterface
通過WebViewClient.shouldOverrideUrlLoading(),攔截加載信息
通過WebChromeClient.onConsoleMessage(),攔截控制臺信息
通過WebChromeClient.onJsPrompt(),onJsAlert()、onJsConfirm()攔截Web相應彈框的事件
Java調用JS在Java中調用JS的代碼有兩種方式,分別為通過loadurl和通過evaluateJavascript.
首先定義了一個html文件,然后將其放置在asset目錄下。通過WebView loadUrl裝載。
我的頁面 Android Java JS 交互測試
通過loadUrl來調用JS方法
mWebView.post(new Runnable() { @Override public void run() { mWebView.loadUrl("javascript:alertTest()"); } });
通過evaluateJavascript來調用JS方法
通過該方法,我們還可以得到JS方法的返回值,來進行值的展示。
mWebView.evaluateJavascript("javascript:alertTest()", new ValueCallback() { @Override public void onReceiveValue(String value) { Toast.makeText(WebViewActivity.this, value, Toast.LENGTH_SHORT).show(); } });
這兩個方法在開始調用的時候,出現的問題是報出錯誤信息,錯誤信息表示調用的JS方法未被定義,問題原因是因為在oncreate或者onResume方法中調用的時候,其JavaScript文件未被完全加載完成,因此出現了該問題,可以通過監聽WebView的裝載事件延遲調用來解決該問題。
JS調用JavaJavascriptInterface
該種方式由于存在著缺陷,后來被棄用。具體問題將在下面介紹。這里先講一下其使用的方式。
1.定義和JS相關的交互類和方法,對于方法通過注解進行標注。
public class JSTest { private Context mContext; public JSTest(Context context) { mContext = context; } @JavascriptInterface public void showToast(String str) { Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show(); } }
2.向WebView添加該JavaScriptInterface,同時為其指定一個名稱,該名稱將會在JS文件中使用。
mWebView.addJavascriptInterface(new JsTest(context), "JsTest");
JS文件
function showToast() { JsTest.showToast("來自Web調用"); }
shouldOverrideUrlLoading
在WebViewClient中有一個方法shouldOverrideUrlLoading,該方法在每次有新的鏈接跳轉的時候,該函數都會被回調,同時傳遞該次跳轉的url,所以我們可以根據自己的需求制定一個url的規則,在這里對于url進行判斷,如果是我們協議內的,則進行攔截,解析我們的協議,然后進行相應的方法調用。
onConsoleMessage()
在WebChromeClient中,有一個函數回調,當我們有console消息的時候,該函數就會被回調到。因此,我們可以自己制定規則,然后觸發console消息,這個時候該函數就會被回調,回調之后,根據我們的規則進行解析,然后調用我們本地相應的方法。
1.在WebChromeClient中定義相應回調方法的攔截處理
@Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { String msg = consoleMessage.message(); if ("showToast".equals(msg)) { Toast.makeText(mContext, "來自Web Toast測試", Toast.LENGTH_SHORT).show(); } return super.onConsoleMessage(consoleMessage); }
JS文件,向控制臺輸出信息。
function consoleTest() { console.log("showToast"); }
onJsPrompt,onJsConfirm, onJsAlert
除了onConsoleMessage的回調之外,WebChromeClient還提供了onJsConfirm,onJsAlert,onJSPrompt等回調,這些在web端有相應的操作的時候,都會被回調到。對于其攔截,要對其中的result做判斷和處理,返回值為true,則表示不再執行,如果返回值不是true,則會網頁上的操作繼續被執行。我們可以通過該種消息的回調來傳遞一些信息,通過這個信息來實現JS和Java的交互。
對于三種回調的方式,onJsPrompt可以傳遞一個任意的值給web,而JsConfirm只能傳遞是否,onJsAlert則不能夠傳遞值,因此為了實現JS和Java的互相調用,onJsPrompt使用是最方便的。
1.JS文件
function promptTest { var result = prompt("js://test?arg1=device"); alert("device: " + result); }
2.WebChromeClient中做相應的攔截,這里直接按照傳遞的數據做比對處理,沒有做協議的約束和解析,然后返回一個當前的設備類型。
@Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { if ("js://test?arg1=device".equals(message)) { result.confirm("模擬器"); } return true; }JSBridge的實現
上面分析了JS和Java的交互的方式,但是如果只是通過上述的方式進行通信,必然會使得代碼比較臃腫,也難以維護,因此就出現了各種框架來對其進行封裝。這里給出一個簡單地通信包裝。
我們的需求是實現JS和Java的互相調用,比如JS調用了Java的方法,執行完成之后,能夠將結果返回,同時使用返回的結果作為JS方法的參數,執行相應的JS方法。這里采取的通信方式是通過對onJsPrompt的攔截解析,然后通過loadUrl的方式執行JS方法。這里分析的是一個簡單開源JSBridge的實現。
Java方法處理
對于JS可能會調用到的Java方法,進行集中管理,對于每一個類,可以自定義名稱,方便在JS中的調用。可能會被調用到的Java方法,都要進行注冊。
public class JSBridge { private static Map> exposedMethods = new HashMap<>(); public static void register(String exposedName, Class extends IBridge> clazz) { if (!exposedMethods.containsKey(exposedName)) { try { exposedMethods.put(exposedName, getAllMethod(clazz)); } catch (Exception e) { e.printStackTrace(); } } } .... }
除此之外,還提供了一個調用函數,這個函數主要是對傳遞的數據根據我們制定的協議進行解析,然后從注冊的函數中找到所要調用的函數,執行Java函數。
public static String invokeNative(WebView webView, String uriString) { //協議解析 String methodName = ""; String className = ""; String param = "{}"; String port = ""; if (!TextUtils.isEmpty(uriString) && uriString.startsWith("JSBridge")) { Uri uri = Uri.parse(uriString); className = uri.getHost(); param = uri.getQuery(); port = uri.getPort() + ""; String path = uri.getPath(); if (!TextUtils.isEmpty(path)) { methodName = path.replace("/", ""); } } //查找方法,執行相應函數 if (exposedMethods.containsKey(className)) { HashMapmethodHashMap = exposedMethods.get(className); if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) { Method method = methodHashMap.get(methodName); if (method != null) { try { method.invoke(null, webView, new JSONObject(param), new Callback(webView, port)); } catch (Exception e) { e.printStackTrace(); } } } } return null; }
從上面函數執行的語句中,可以看到起傳遞的參數有WebView,JSonObject和一個Callback。
method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
這里用來給JS調用的Java方法,傳遞的值都是JsonObject的形式,Callback則是回調相應的JS方法,當我們的Java方法執行完成之后,如果我們需要調用相應的JS方法,我們可以通過callback提供的apply方法來傳遞一些數據。
public static void showToast(WebView webView, JSONObject param, final Callback callback) { String message = param.optString("msg"); Toast.makeText(webView.getContext(), message, Toast.LENGTH_SHORT).show(); if (null != callback) { try { JSONObject object = new JSONObject(); object.put("key", "value"); object.put("key1", "value1"); callback.apply(getJSONObject(0, "ok", object)); } catch (Exception e) { e.printStackTrace(); } } }
這里callback的apply方法的實現。
private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge.onFinish("%s", %s);"; public void apply(JSONObject jsonObject) { final String execJs = String.format(CALLBACK_JS_FORMAT, mPort, String.valueOf(jsonObject)); if (mWebViewRef != null && mWebViewRef.get() != null) { mHandler.post(new Runnable() { @Override public void run() { mWebViewRef.get().loadUrl(execJs); } }); } }
這里將需要傳回的數據,傳遞給JSBridge中的onFinish方法。傳遞的數據中有一個端口號,通過這個端口號作為標示,來調用相應的方法。
JS方法處理
callbacks: {}, call: function (obj, method, params, callback) { var port = Util.getPort(); this.callbacks[port] = callback; var uri=Util.getUri(obj,method,params,port); window.prompt(uri, ""); }, onFinish: function (port, jsonObj){ var callback = this.callbacks[port]; callback && callback(jsonObj); delete this.callbacks[port]; },
JS文件提供了兩個方法,一個是call一個是finish,分別是web中被調用,另一個是在native中將會被調用,每一個web中調用我們native方法的時候,都會調用js文件中的oncall方法,同時也會傳遞一個函數作為回調,js文件中會為該次調用隨機生成一個端口號,同時將其回調保存在一個內部列表callbacks中,根據協議規則,通過window.promt的方式將相應的調用傳遞下去。
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { result.confirm(JSBridge.invokeNative(view, message)); return true; }
在WebChromeClient的onJsPrompt方法中便可以得到相應的信息,通過JSBridge方法對回傳數據進行相應的調用。
JS中調用Java方法的方式
JSBridge.call("bridge","showToast",{"msg":"Hello JSBridge"},function(res){alert(JSON.stringify(res))})
第一,二個參數表示調用的Java的方法,第三個參數為傳遞的參數。第四個參數為設置的回調函數。
至此,一個簡單的JSBridge實現了,JS只需要通過call方法傳遞相應的參數即可調用Java方法,Java只需要將待調用方法進行注冊即可。
交互中的安全漏洞問題進幾年和WebView遠程代碼執行相關的漏洞主要有CVE-2012-6336,CVE-2014-1939,CVE-2014-7224, 這些漏洞中最核心的漏洞是CVE-2012-6336,另外兩個CVE只是發現了幾個默認存在的接口。
CVE-2012-6636
Android API 16.0及之前的版本中存在安全漏洞,該漏洞源于程序沒有正確限制使用WebView.addJavascriptInterface方法。遠程攻擊者可通過使用Java Reflection API利用該漏洞執行任意Java對象的方法
Google Android <= 4.1.2 (API level 16) 受到此漏洞的影響。
CVE-2014-1939
java/android/webkit/BrowserFrame.java 使用addJavascriptInterface API并創建了SearchBoxImpl類的對象。攻擊者可通過訪問searchBoxJavaBridge_接口利用該漏洞執行任意Java代碼。
Google Android <= 4.3.1 受到此漏洞的影響
CVE-2014-7224
香港理工大學的研究人員發現當系統輔助功能中的任意一項服務被開啟后,所有由系統提供的WebView都會被加入兩個JS objects,分別為是accessibility和accessibilityTraversal。惡意攻擊者就可以使用accessibility和accessibilityTraversal這兩個Java Bridge來執行遠程攻擊代碼.
Google Android < 4.4 受到此漏洞的影響。
對于上述漏洞,其攻擊原理為得到了Java對象,通過反射的方式來執行自己的惡意代碼。
解決方案
1.移除掉原有提供的JavaScript接口
private static final void removeJavascriptInterfaces11(WebView webView) { try { webView.removeJavascriptInterface("searchBoxJavaBridge_"); webView.removeJavascriptInterface("accessibility"); webView.removeJavascriptInterface("accessibilityTraversal"); } catch (Throwable tr) { tr.printStackTrace(); } }
2.升級系統API level 17后,只有顯示添加 @JavascriptInterface的方法才能被JavaScript調用,這樣反射就失去作用了。但對于更低版本則還是會存在,考慮采用其它方案,例如JSBridge實現交互。
參考資料JsBridge 實現 JavaScript 和 Java 的互相調用
Android:你要的WebView與 JS 交互方式 都在這里
Android WebView遠程執行代碼漏洞淺析
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/88944.html
摘要:若是自定義類型,則接收不到正確數據可以通過原生方法的返回值獲取內置類型的參數,如若需要互傳復雜類型的對象,雙方須以對媒介,以各自語言的工具類進行相互轉化參見 showImg(https://segmentfault.com/img/remote/1460000018767532); 在Android混合開發中,WebView是中間件,承擔著渲染網頁的職責。 WebView的使用教程參見...
摘要:若是自定義類型,則接收不到正確數據可以通過原生方法的返回值獲取內置類型的參數,如若需要互傳復雜類型的對象,雙方須以對媒介,以各自語言的工具類進行相互轉化參見 showImg(https://segmentfault.com/img/remote/1460000018767532); 在Android混合開發中,WebView是中間件,承擔著渲染網頁的職責。 WebView的使用教程參見...
摘要:那么本身是代碼,它是怎么調用到的實現的本文就會介紹這個細節。下圖是離線存儲插件的實現代碼的一部分。待執行的的實現類名稱。傳遞給的參數數組。 在Cordova官網中有這么一張架構圖:大家看右下角藍色的矩形框Custom Plugin——自定義插件。意思就是如果您用Cordova打包Mobile應用時,發現您的移動應用里需要使用一些功能,這些功能用普通的JavaScript無法實現,而是需...
閱讀 2321·2021-11-24 10:18
閱讀 3385·2021-09-22 15:35
閱讀 3340·2021-09-13 10:37
閱讀 3766·2021-09-06 15:14
閱讀 2071·2021-09-06 15:02
閱讀 2212·2021-09-02 15:11
閱讀 547·2019-08-30 15:53
閱讀 3075·2019-08-29 16:15