Android中Java和JavaScript互動

技術小黑屋發表於2014-12-12

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

如何實現

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

  • WebView開啟JavaScript指令碼執行
  • WebView設定供JavaScript呼叫的互動介面。
  • 客戶端和網頁端編寫呼叫對方的程式碼。

本例程式碼

為了便於講解,先貼出全部程式碼

Java程式碼

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)”)

呼叫js無參無返回值函式

String call = "javascript:sayHello()";
webView.loadUrl(call);

呼叫js有參無返回值函式

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

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

呼叫js有引數有返回值的函式

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

1.Java呼叫js程式碼

String call = "javascript:sumToJava(1,2)";
webView.loadUrl(call);

2.js函式處理,並將結果通過呼叫java方法返回

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

3.Java在回撥方法中獲取js函式返回值

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

4.4處理

Android 4.4之後使用evaluateJavascript即可。這裡展示一個簡單的互動示例 具有返回值的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也執行在主執行緒。

疑問解答

Alert無法彈出

你應該是沒有設定WebChromeClient,按照以下程式碼設定

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

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函式
  }

});

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,然後將其引入。注意這個介面不能混淆。

注,建立@JavascriptInterface程式碼

public @interface JavascriptInterface {

}

程式碼混淆問題

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

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

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).
    }
});

相關文章