Android開發之WebView和JS交互詳解

Android提供了一個很強大的WebView控件用來處理Web網頁,而在網頁中,JavaScript又是一個很舉足輕重的腳本。本文將介紹如何實現Java代碼和Javascript代碼的相互調用。


如何實現

實現Java和js交互十分便捷。通常只需要以下幾步。

  • WebView開啓JavaScript腳本執行
  • WebView設置供JavaScript調用的交互接口。
  • 客戶端和網頁端編寫調用對方的代碼。

本例代碼

爲了便於講解,先貼出全部代碼

Android代碼

package com.example.javajsinteractiondemo;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class MainActivity extends Activity {
  private static final String LOGTAG = "MainActivity";
  @SuppressLint("JavascriptInterface")
  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      final WebView myWebView = (WebView) findViewById(R.id.myWebView);
      WebSettings settings = myWebView.getSettings();
      settings.setJavaScriptEnabled(true);
      myWebView.addJavascriptInterface(new JsInteration(), "control");
      myWebView.setWebChromeClient(new WebChromeClient() {});
      myWebView.setWebViewClient(new WebViewClient() {

          @Override
          public void onPageFinished(WebView view, String url) {
              super.onPageFinished(view, url);
              testMethod(myWebView);
          }
      });
      myWebView.loadUrl("file:///android_asset/js_java_interaction.html");
  }
  private void testMethod(WebView webView) {
      String call = "javascript:sayHello()";
      call = "javascript:alertMessage(\"" + "content" + "\")";
      call = "javascript:toastMessage(\"" + "content" + "\")";
      call = "javascript:sumToJava(1,2)";
      webView.loadUrl(call);
  }

  public class JsInteration {

      @JavascriptInterface
      public void toastMessage(String message) {
          Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
      }

      @JavascriptInterface
      public void onSumResult(int result) {
          Log.i(LOGTAG, "onSumResult result=" + result);
      }
  }
}

前端網頁代碼

<html>
<script type="text/javascript">
    function sayHello() {
        alert("Hello")
    }

    function alertMessage(message) {
        alert(message)
    }

    function toastMessage(message) {
        window.control.toastMessage(message)
    }

    function sumToJava(number1, number2){
       window.control.onSumResult(number1 + number2)
    }
</script>
Java-Javascript Interaction In Android
</html>

調用示例

JS調用Java

調用格式爲window.jsInterfaceName.methodName(parameterValues) 此例中我們使用的是control作爲注入接口名稱。

function toastMessage(message) {
  window.control.toastMessage(message)
}
function sumToJava(number1, number2){
   window.control.onSumResult(number1 + number2)
}

Java調用JS

webView調用js的基本格式爲:

webView.loadUrl(“javascript:methodName(parameterValues)”)

webView調用js有參無返回值函數:

注意對於字符串作爲參數值需要進行轉義雙引號。

String call = "javascript:alertMessage(\"" + "content" + "\")";
webView.loadUrl(call);

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

Android在4.4之前並沒有提供直接調用js函數並獲取值的方法,所以在此之前,常用的思路是 java調用js方法,js方法執行完畢,再次調用java代碼將值返回。

  • Java調用js代碼
String call = "javascript:sumToJava(1,2)";
webView.loadUrl(call);
  • js函數處理,並將結果通過調用java方法返回
function sumToJava(number1, number2){
       window.control.onSumResult(number1 + number2)
}
  • Java在回調方法中獲取js函數返回值
@JavascriptInterface
public void onSumResult(int result) {
  Log.i(LOGTAG, "onSumResult result=" + result);
}

4.4版本以上處理:

Android 4.4之後使用evaluateJavascript即可。這裏展示一個簡單的交互示例 具有返回值的js方法

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);
  }});
}
輸出結果:
I/MainActivity( 1432): onReceiveValue value=1

注意:

  • 上面限定了結果返回結果爲String,對於簡單的類型會嘗試轉換成字符串返回,對於複雜的數據類型,建議以字符串形式的json返回。
  • evaluateJavascript方法必須在UI線程(主線程)調用,因此onReceiveValue也執行在主線程。

疑問解答

1、 Alert無法彈出

應該是沒有設置WebChromeClient,按照以下代碼設置

myWebView.setWebChromeClient(new WebChromeClient() {});

2、 Uncaught ReferenceError: functionName is not defined

問題出現原因,網頁的js代碼沒有加載完成,就調用了js方法。解決方法是在網頁加載完成之後調用js方法

myWebView.setWebViewClient(new WebViewClient() {

  @Override
  public void onPageFinished(WebView view, String url) {
      super.onPageFinished(view, url);
      //在這裏執行你想調用的js函數
  }
});

3、Uncaught TypeError: Object [object Object] has no method

安全限制問題

如果只在4.2版本以上的機器出問題,那麼就是系統處於安全限制的問題了。Android文檔這樣說的

Caution: If you’ve set your targetSdkVersion to 17 or higher, you must add the @JavascriptInterface annotation to any method that you want available your web page code (the method must also be public). If you do not provide the annotation, then the method will not accessible by your web page when running on Android 4.2 or higher.

中文大意爲:

警告:如果你的程序目標平臺是17或者是更高,你必須要在暴露給網頁可調用的方法(這個方法必須是公開的)加上@JavascriptInterface註釋。如果你不這樣做的話,在4.2以以後的平臺上,網頁無法訪問到你的方法。

解決方法

  • 將targetSdkVersion設置成17或更高,引入@JavascriptInterface註釋
  • 自己創建一個註釋接口名字爲@JavascriptInterface,然後將其引入。注意這個接口不能混淆。這種方式不推薦,大概在4.4之後有問題。

注,創建@JavascriptInterface代碼

public @interface JavascriptInterface {

}

4、代碼混淆問題

如果在沒有混淆的版本運行正常,在混淆後的版本的代碼運行錯誤,並提示Uncaught TypeError: Object [object Object] has no method,那就是你沒有做混淆例外處理。 在混淆文件加入類似這樣的代碼

keepattributes *Annotation*
keepattributes JavascriptInterface
-keep class com.example.javajsinteractiondemo$JsInteration {
    *;
}

5、All WebView methods must be called on the same thread

過濾日誌曾發現過這個問題。

E/StrictMode( 1546): java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {528712d4} called on Looper (JavaBridge, tid 121) {52b6678c}, FYI main Looper is Looper (main, tid 1) {528712d4})
E/StrictMode( 1546):   at android.webkit.WebView.checkThread(WebView.java:2063)
E/StrictMode( 1546):   at android.webkit.WebView.loadUrl(WebView.java:794)
E/StrictMode( 1546):   at com.xxx.xxxx.xxxx.xxxx.xxxxxxx$JavaScriptInterface.onCanGoBackResult(xxxx.java:96)
E/StrictMode( 1546):   at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
E/StrictMode( 1546):   at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
E/StrictMode( 1546):   at android.os.Handler.dispatchMessage(Handler.java:102)
E/StrictMode( 1546):   at android.os.Looper.loop(Looper.java:136)
E/StrictMode( 1546):   at android.os.HandlerThread.run(HandlerThread.java:61)

在js調用後的Java回調線程並不是主線程。如打印日誌可驗證

ThreadInfo=Thread[WebViewCoreThread,5,main]

解決上述的異常,將webview操作放在主線程中即可。

webView.post(new Runnable() {
    @Override
    public void run() {
        webView.loadUrl(YOUR_URL).
    }
});

感謝:http://droidyue.com/blog/2014/09/20/interaction-between-java-and-javascript-in-android/

發佈了30 篇原創文章 · 獲贊 19 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章