Android與JS交互

現(xiàn)在越來越多的App是混合開發(fā),很需要原生與網(wǎng)頁交互,下面介紹如何交互以及有可能出現(xiàn)的坑。

Java調(diào)用JS方法

對于Android調(diào)用JS方法的方式有2種:

  • 通過WebViewloadUrl()
  • 通過WebViewevaluateJavascript()

方式一: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)(也就是坑)

  1. 調(diào)用JS方法時(shí)一定要在onPageFinished()回調(diào)之后才能調(diào)用,否則不會調(diào)用。

onPageFinished()屬于WebViewClient類的方法,主要在頁面加載結(jié)束時(shí)調(diào)用

  1. 調(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()");
                    }
 });
  1. 調(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的方式有三種

  • 通過WebViewaddJavascriptInterface()
  • 通過WebViewClientshouldOverrideUrlLoading()方法回調(diào)攔截 url
  • 通過WebChromeClientonJsAlert()、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)行線程切換操作。

方式二:WebViewClientshouldOverrideUrlLoading()方法

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();。

方式三:通過WebChromeClientonJsAlert()、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++

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容