現(xiàn)在越來越多的App是混合開發(fā),很需要原生與網(wǎng)頁交互,下面介紹如何交互以及有可能出現(xiàn)的坑。
Java調(diào)用JS方法
對于Android調(diào)用JS方法的方式有2種:
- 通過
WebView的loadUrl() - 通過
WebView的evaluateJavascript()
方式一:loadUrl()
- 第一步:設(shè)置與Js交互的權(quán)限
WebSettings webSettings = mWebView.getSettings();
// 設(shè)置與Js交互的權(quán)限
webSettings.setJavaScriptEnabled(true);
- 第二步:設(shè)置加載網(wǎng)頁
mWebview.loadUrl("加載的網(wǎng)頁");
- 第三步:調(diào)用JS方法
通過使用mWebview.loadUrl 調(diào)用“javascript:" + 方法
如果JS這樣定義的
<script>
function callJS(){
alert("Android調(diào)用了JS的無參callJS方法");
}
function callJSParameter(message){
alert("Android調(diào)用JS的有參callJS方法,參數(shù)為"+message);
}
</script>
在Java代碼中
//調(diào)用無參
mWebView.loadUrl("javascript:callJS()");
//調(diào)用有參
mWebView.loadUrl("javascript:callJSParameter('測試')");
方式一注意事項(xiàng)(也就是坑)
- 調(diào)用JS方法時(shí)一定要在
onPageFinished()回調(diào)之后才能調(diào)用,否則不會調(diào)用。
onPageFinished()屬于WebViewClient類的方法,主要在頁面加載結(jié)束時(shí)調(diào)用
- 調(diào)用JS方法時(shí)一定要在主線程,否則會崩潰異常。
java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'Thread-18022'. All WebView methods must be called on the same thread.
如果非要在子線程調(diào)用,也要將其轉(zhuǎn)換到主線程中去:
mWebView.post(new Runnable() {
@Override
public void run() {
mWebView.loadUrl("javascript:callJS()");
}
});
- 調(diào)用的帶參數(shù)的JS方法時(shí),參數(shù)類型如果為String ,切記使用單引號( ') 包裹;如果為數(shù)組類型則不用,如:javascript:callJs([01, 02, 03]);如果為其他復(fù)雜類型則可以轉(zhuǎn)換為 Json 字符串的形式傳遞。
方式二:evaluateJavascript()
*執(zhí)行該方法不會使頁面刷新,而第一種方法(loadUrl )的執(zhí)行則會,但是Android 4.4 后才可使用,兼容性要求偏高。
JS代碼
function callJSReturn(message){
return message;
}
Java代碼
mWebView.evaluateJavascript("javascript:callJSReturn('方式二調(diào)用JS')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此處為 js 返回的結(jié)果
Toast.makeText(MainActivity.this, value, Toast.LENGTH_SHORT).show();
}
});
兩種方式對比
| 調(diào)用方式 | 優(yōu)點(diǎn) | 缺點(diǎn) | 使用場景 |
|---|---|---|---|
loadUrl() |
兼容性好 | 獲取返回值麻煩 | 不需要獲取返回值 |
evaluateJavascript() |
只能從4.4(18)以上使用 | 獲取返回值簡單 | 4.4(18)以上 |
JS調(diào)用Java
JS調(diào)用Java的方式有三種
- 通過
WebView的addJavascriptInterface() - 通過
WebViewClient的shouldOverrideUrlLoading()方法回調(diào)攔截url - 通過
WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調(diào)攔截JS對話框alert()、confirm()、prompt()消息
方式一:addJavascriptInterface()
JS代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>LYJ</title>
<script>
function testCallAndroid(){
test.callAndroid("JS調(diào)用了Android方法");
}
</script>
</head>
<body>
<button type="button" id="button1" onclick="testCallAndroid()">點(diǎn)擊按鈕通過方式一調(diào)用Android方法</button>
</body>
</html>
- 第一步:定義一個(gè)JS調(diào)用的方法
@JavascriptInterface
public void callAndroid(String message){
Toast.makeText(CallAndroidActivity.this,message,Toast.LENGTH_SHORT).show();
}
1.必須要有@JavascriptInterface,在 Android4.2(17) 及更高版本的系統(tǒng)中,任何暴露給 Js 訪問的 Java 接口都需要添加這個(gè)注解,否則會報(bào)異常:Uncaught TypeError: Object [object Object] has no method ‘XXX’。系統(tǒng)在之前的版本中有漏洞,Js 可以通過反射的方式訪問注入 WebView 中的 Java 對象的 public 類型 field 和 method,從而隨意修改宿主程序,所以為了安全增加了這個(gè)注解。想更深入的了解參考這篇文章你不知道的 Android WebView 使用漏洞
2.方法名切記要和JS調(diào)用的方法名一樣
- 第二步將Java對象映射到JS對象
mWebView.addJavascriptInterface(this, "test");
第一個(gè)參數(shù)是寫有JS調(diào)用方法的Android對象,第二個(gè)參數(shù)是JS調(diào)用的對象名,切記跟網(wǎng)頁上的一致。
方式一注意事項(xiàng)
- Js 調(diào)用 Java 方法時(shí),不是在主線程 (Thread Name:main) 中運(yùn)行的,而是在一個(gè)名為 JavaBridge 的線程中執(zhí)行的,如果需要 Java 繼續(xù)回調(diào) Js,千萬別在 JavascriptInterface 方法體中直接執(zhí)行 loadUrl() 方法,而是像前面一樣進(jìn)行線程切換操作。
方式二:WebViewClient 的shouldOverrideUrlLoading()方法
JS代碼
<script>
function testTwoCallAndroid(){
document.location = "js://webview?key1=value1&key2=value2";
}
</script>
<body>
<button type="button" id="button2" onclick="testTwoCallAndroid()">點(diǎn)擊按鈕通過方式二調(diào)用Android方法</button>
</body>
Java代碼
// 復(fù)寫WebViewClient類的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(
new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 第一步:根據(jù)協(xié)議的參數(shù),判斷是否是所需要的url
// 一般根據(jù)scheme(協(xié)議格式) & authority(協(xié)議名)判斷
//假定傳入進(jìn)來的 url = "js://webview?key1=value1&key2=value2"(同時(shí)也是約定好的需要攔截的)
Uri uri = Uri.parse(url);
// 第二步:如果url的協(xié)議 = 預(yù)先約定的 js 協(xié)議,就解析往下解析參數(shù)
if (uri.getScheme().equals("js")) {
//第三步:再判斷,如果 authority = 預(yù)先約定協(xié)議里的 webview,即代表都符合約定的協(xié)議
// 所以攔截url,下面JS開始調(diào)用Android需要的方法
if (uri.getAuthority().equals("webview")) {
// 第四步:
// 執(zhí)行JS所需要調(diào)用的邏輯
// 獲取協(xié)議執(zhí)行的參數(shù)
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
for (String key : collection){
String value = uri.getQueryParameter(key);
Log.d("==","key = " + key + ",value = " + value);
params.put(key,value);
}
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
);
}
注釋比較清楚,就按注釋的步驟走。
shouldOverrideUrlLoading(WebView view, String url)在Android 7.0(24)過時(shí),官方改為shouldOverrideUrlLoading (WebView view,
WebResourceRequest request)。然后獲取Uri改為Uri uri = request.getUrl();。
方式三:通過WebChromeClient的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調(diào)攔截JS對話框alert()、confirm()、prompt()消息
| 方法 | 作用 | 返回值 | 備注 |
|---|---|---|---|
| alert() | 彈出警告框 | 沒有 | 在文本加入/n可換行 |
| confirm() | 彈出確認(rèn)框 | 兩個(gè) | 返回boolean值,true代表確認(rèn),false代表取消 |
| prompt() | 彈出輸入框 | 任意 | 點(diǎn)擊確認(rèn)返回輸入框的值,點(diǎn)擊取消返回null |
下面的例子將用攔截 JS的輸入框(即prompt()方法)說明,其他兩種與其類似 :
JS代碼
<script>
function testPromptCallAndroid(){
var result=prompt("js://prompt?key1=value1&key2=value2");
alert("方式三 " + result);
}
</script>
<body>
<button type="button" id="button3" onclick="testPromptCallAndroid()">點(diǎn)擊按鈕通過方式三調(diào)用Android方法</button>
</body>
Java代碼
mWebView.setWebChromeClient(
new WebChromeClient() {
// 攔截輸入框(原理同方式2)
// 參數(shù)message:代表promt()的內(nèi)容(不是url)
// 參數(shù)result:代表輸入框的返回值
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根據(jù)協(xié)議的參數(shù),判斷是否是所需要的url(原理同方式2)
// 一般根據(jù)scheme(協(xié)議格式) & authority(協(xié)議名)判斷(前兩個(gè)參數(shù))
//假定傳入進(jìn)來的 url = "js://webview?arg1=111&arg2=222"(同時(shí)也是約定好的需要攔截的)
Uri uri = Uri.parse(message);
// 如果url的協(xié)議 = 預(yù)先約定的 js 協(xié)議
// 就解析往下解析參數(shù)
if (uri.getScheme().equals("js")) {
// 如果 authority = 預(yù)先約定協(xié)議里的 webview,即代表都符合約定的協(xié)議
// 所以攔截url,下面JS開始調(diào)用Android需要的方法
if (uri.getAuthority().equals("prompt")) {
// 執(zhí)行JS所需要調(diào)用的邏輯
// 可以在協(xié)議上帶有參數(shù)并傳遞到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
for (String key : collection) {
String value = uri.getQueryParameter(key);
Log.d("==", "key = " + key + ",value = " + value);
params.put(key, value);
}
//參數(shù)result:代表消息框的返回值(輸入值)
result.confirm("js通過方式三調(diào)用了Android的方法成功");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
}
);
整體上跟上一個(gè)方式相差不多。
其他
- 如果需要回調(diào)這種需求,你只要android的異步回調(diào)中,使用loadUrl調(diào)用js的相關(guān)方法。
- 如果需要給JS返回值,除了方式三以外,還可以這么做
JS代碼
function returnResult(result){
alert("result is" + result);
}
Java代碼
mWebView.loadUrl("javascript:returnResult(" + result + ")");
參考文章
Android:你要的WebView與 JS 交互方式 都在這里了
Android WebView —— Java 與 JavaScript 交互總結(jié)
Android_其他語言交互篇——Js、C#、C、C++