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
相關文章

Android Vuforia with jPCT-AE (5) – 多重模型載入,以 obj 為例

要進到這系列最後一篇文章了,這篇文章拖了很久,一直沒有時間整理,結果到最後 ... 程式碼留下來了,記憶卻有些模糊了,這邊小蛙配著程式碼盡可能的把還記得的東西寫下來。
2016-07-06 11:24:19
hans

18

Android Vuforia with jPCT-AE (4) – 載入 3DS 測試

延續 jPCT-AE Loader 載入模型的部份,這篇要記錄載入 3ds 檔案格式的方法,基本上跟前面幾個 obj, md2 的方法差不多,只有一些小小的變化而已。
2016-07-06 10:24:33
hans

18

Android Vuforia with jPCT-AE (3) – 載入 md2 測試

前兩篇介紹如何直接透過 jPCT-AE 直接繪圖以及載入 .obj 檔案,這邊繼續介紹 jPCT-AE 載入 md2 的方法,載入動作大致上類似,唯一不同的只有 texture 設定部份。
2016-07-06 09:24:43
hans

18

Android Vuforia with jPCT-AE (2) – 載入 obj 測試

上一篇 Android Vuforia with jPCT-AE (1) –
2016-07-06 08:24:55
hans

18

Android Vuforia with jPCT-AE (1) – 基本範例

說來慚愧,從 Google Adsense 被 ban 之後,就好久沒有發文了,站上 Vuforia 的文章也已經是好久以前的事情了 (遠目),最近有專案要用到 Vuforia,爬以前的文回來看,蛙哩咧 ... 現在已經更新到 Vu
2016-07-06 07:57:58
hans

18

Qualcomm Vuforia 教學 (5) – 替換茶壺 – Android JNI 版本

前面的文章提到為什麼要用 Android NDK 來開發,既然已經有了 Android 版本替換茶壺的文章,這邊記錄 Android NDK 版本的替換茶壺。
2014-12-09 17:54:17
hans

18

Qualcomm Vuforia 教學 (4) – 使用 Android NDK 版本並開啟多重偵測

接續前幾篇教學,我們已經建置好 Vuforia 開發及執行環境
2014-12-09 17:10:57
hans

18

Qualcomm Vuforia 教學 (3) – 替換茶壺 – Android 版本

接續上一篇 
2014-12-09 15:38:17
hans

18

Android 與 WebView 中的 Javascript 相互溝通

最近的一個 app 需要讓 Android 可以呼叫 WebView 中的 Javascript,並且按下 WebView 中的按鈕時,也可以呼叫 Android 內部的方法,這篇文章記錄一下大概的做法,免得下次遇到又忘記 ...
2014-03-28 20:25:19
hans

18




 回覆

你可以使用以下語法 HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="">

(required)

(required)

   
© 2012 蛙齋 Suffusion theme by Sayontan Sinha