Android 2.3 @JavascriptInterface Issue

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 方法不太會用,卡了一段時間 …

    發佈留言

    發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

    這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料