JavascriptInterface 這個問題相信 Google 一下就可以找到很多文章,雖然如此小蛙還是試了好久才成功,這篇文章記錄一下當時小蛙不成功的盲點在哪裡。上一篇文章「Android 與 WebView 中的 Javascript 相互溝通 @ 蛙齋」記錄怎麼讓 Android 與 Javascript 相互溝通。

小蛙在實作 WebView 載入的網頁與 Android 互動時,一直都以手邊的 Nexus 7 2013 來做測試,直到所有功能都已就緒,有一天突然想起之前好像有看到 Javascript 在 2.3.x 會出現錯誤的問題,於是開了 2.3 版本的 emulator 來做測試,由於小蛙實作的部分是從 Javascript 呼叫某一個 function 來實作開啟行事曆的功能,這邊發生了大概類似下面的這種錯誤(小蛙的錯誤訊息已經洗掉了,以下內容來字參考資料1)

JNI WARNING: jarray 0xb5d256f0 points to non-array object (Ljava/lang/String;)
"WebViewCoreThread" prio=5 tid=8 NATIVE
  | group="main" sCount=0 dsCount=0 obj=0xb5cfc348 self=0x8234e98
  | sysTid=2023 nice=0 sched=0/0 cgrp=[fopen-error:2] handle=136531904
  at android.webkit.WebViewCore.nativeTouchUp(Native Method)
  at android.webkit.WebViewCore.nativeTouchUp(Native Method)
  at android.webkit.WebViewCore.access$3300(WebViewCore.java:53)
  at android.webkit.WebViewCore$EventHub$1.handleMessage(WebViewCore.java:1162)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:130)
  at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:633)
  at java.lang.Thread.run(Thread.java:1019)
VM aborting

對於這個問題,參考資料1 的作者將 JavascriptInterface 整理成一個比較完整的 solution,不過因為教學文章「漏漏長」,所以小蛙後來沒有採用這個方案,雖然方案不同,不過解決這個問題的邏輯是差不多的。
小蛙後來使用了參考資料2的方法來解決,這邊大概記錄一下作法 (不知道怎麼讓 Android 與 Javascript 互通的話可參考「Android 與 WebView 中的 Javascript 相互溝通 @ 蛙齋」):

  • 檢查 Android 版本

設定參數來記錄 Android 版本是否為 2.3.x,

private static JavaScriptInterface JSInterface;
// 設置 javascriptInterfaceBroken 來判斷是否為 2.3.x
private static boolean javascriptInterfaceBroken = false;
public static WebView wv;
try {
        if (Build.VERSION.RELEASE.contains("2.3.")) {
                javascriptInterfaceBroken = true;
        }
} catch (Exception e) { }
JSInterface = new JavaScriptInterface(getActivity());
  • 依照版本設定對應動作
if(!javascriptInterfaceBroken){
	// 非 2.3.x 版本,正常執行
	wv.addJavascriptInterface(JSIterface, "JSInterface");
	...
}else{
	// 2.3.x 版本
	wv.setWebViewClient(new WebViewClient(){
		@Override
		public void onPageFinished(WebView view, String url){
		super.onPageFinished(view, url);
		String handleGingerbreadStupidity =
				"javascript:function addToNotify(title, url, start, end) { window.location='http://JSInterface:addToNotify:'+(title)+'◎'+(url)+'◎'+(start)+'◎'+(end); }; "
				+ "javascript: function handler() { this.addToNotify=addToNotify }; "
				+ "javascript: var JSInterface = new handler();";
				view.loadUrl(handleGingerbreadStupidity);
		}
		@Override
		public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
			if (url.contains("JSInterface:addToNotify")) {
				// 在這邊可用 regular expression 把剛剛組出來的 url 解回
                                // 須注意如果有中文的話,字串要先從 ISO-8859-1 轉回 UTF-8
				String nUrl = new String(url.getBytes("ISO-8859-1"), "UTF-8");
				String title = "title";
				String desc = "desc";
				String start = "start";
				String end = "end";
				try {
					Method sMethod = JSInterface.getClass().getMethod("addToNotify", new Class[] { String.class, String.class, String.class, String.class });
					sMethod.invoke(JSInterface, title, desc, start, end);
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
			return true;
		}
	});
}

上述程式碼中有幾個部份小蛙提出來記錄:
handleGingerbreadStupidity 字串中的 addToNotify 改成自己的方法名稱;JSInterface 改成自己的 JavaScriptInterface 名稱;後面參數有幾個都用「:」連接,因為小蛙的參數中有冒號會跟原本的冒號混淆,導致要拆出字串的時候很麻煩,因此這邊改成用「◎」來區隔。
shouldOverrideUrlLoading 會接收到從 onPageFinished 過來的 url,接著判斷如果包含剛剛我們自己組合的字串的話,就使用 reflection 的方式來呼叫對應方法。JSInterface.getClass().getMethod 中的 JSInterface 是開頭宣告的 JavaScriptInterface 的物件名稱,getMethod 第一個參數為呼叫的方法名稱,第二個參數是傳入的參數型別,都設定好之後就用 sMethod.invoke 呼叫該方法並傳入需要的物件及參數。
記錄的有點亂,小蛙當時卡在 onPagefinished 中傳參數的部分,因為內容包含了冒號導致發生一些問題;再來是 getMethod 方法不太會用,卡了一段時間 … 

參考資料:

  1. Android: WebView.addJavascriptInterface() crash workaround for Gingerbread 2.3.x @ Twig’s Tech Tips
    http://twigstechtips.blogspot.tw/2013/09/android-webviewaddjavascriptinterface.html
  2. Android 問題百出之 2.3.x 的 JavaScript Interface @ Fred’s blog
    http://fred-zone.blogspot.tw/2011/12/android-23x-javascript-interface.html

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *