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)

01
02
03
04
05
06
07
08
09
10
11
12
13
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,

01
02
03
04
05
06
07
08
09
10
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());
  • 依照版本設定對應動作
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 方法不太會用,卡了一段時間 …

發佈留言

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