Android 問題百出之 2.3.x 的 JavaScript Interface

有鑑於 Android 問題太多,只好定了個系列標題『Android 問題百出』當開頭,並將碰到的問題和解決或避開的方法記錄在內。話說回來,筆者個人其實相當討厭 Android,自 Android 出現以來,從未真的投入其中並賺過什麼錢,會接觸,多半是興趣玩弄或是幫一些朋友的公司臨時打工救火。不過,既然是救火,任何千奇百怪的問題或狀況都會遭遇到,甚至還得『限時』解決別人解決不了的問題。

這次碰到的問題就是 Android 2.3.x Gingerbread 缺少 JavaScript Interface 的實作,如果你的應用程式有自己實作 API 供 JavaScript 程式使用,那這些 API 將會完全失效。而這樣的問題,對於使用 Web 技術(HTML5 + Javascript)的應用程式來說,相當嚴重。

話說 Android 在 2.3 版本之後,採用了 V8 做為 JavaScript Engine。在快速掃過 Android 關於 WebView 和 WebKit 的程式碼後,發現不幸的是『Google 再次未將程式寫完』。但這一次,不只是功能未完成,而是將原本可以用的功能(在 Android 2.2),變成不能用。這讓筆者非常不能理解,為何 Google 換了 V8 Engine 後,明知功能沒有改完,卻仍將這部份實作隨新版 Android 釋出?然後,絕大多數廠商,在毫無感覺之下,直接繼承了這樣的 Bug。

對硬體裝置的廠商來說,重新實作這個 JavaScript Interface 支援是最佳的解決辦法,髒一點的方法是將 V8 改回舊的 JSCore。但很可惜的是,就算有人改好了,也沒有廠商願意釋出這部份的程式碼。(所以才會不停有人願意花大錢找筆者這種臨時救火工呀。)

此外,如果你是一般的應用程式開發者,因為動不了底層,則完全無解。但是,這邊有個 Workaround,可以暫時代替 JavaScript Interface,讓應用程式開發者可以避開這問題(以下假設自定的 JavaScript Interface Class 為 myAPI)。

定義將會用到的變數:
/* Define private variable */
private static WebView mWebView;
private static myAPI mJSIF;
private static boolean javascriptInterfaceBroken = false;

初始化 WebView 時加上 Workaround:
/* Initializing WebView */
mWebView = (WebView) findViewById(R.id.supermark_dialog);
mWebView.getSettings().setJavaScriptEnabled(true);

/* Check Android version */
try {
    if (Build.VERSION.RELEASE.contains("2.3.")) {
        javascriptInterfaceBroken = true;
    }
} catch (Exception e) { }

/* Create own JavaScript Interface */
mJSIF = new myAPI(mWebView);

if (!javascriptInterfaceBroken) {
    mWebView.addJavascriptInterface(mJSIF, "Fred");
} else {
    /* Workaround to add JavaScript Interface to webview for Fucking Android 2.3 */
    mWebView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            if (javascriptInterfaceBroken) {
                String handleGingerbreadStupidity =
                    "javascript: function do() {window.location='http://Fred:do:null';};" +
                    "javascript: function handler() {" +
                    "this.do=do;" +
                    "}; " +
                    "javascript: var Fred = new handler();";
                view.loadUrl(handleGingerbreadStupidity);
            }
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (javascriptInterfaceBroken) {
                if (url.contains("Fred")) {
                    /* Parsing URL */
                    StringTokenizer st = new StringTokenizer(url, ":");
                    st.nextToken();
                    st.nextToken();
                    String function = st.nextToken();
                    String parameter = st.nextToken();

                    /* Call function */
                    if (function.equals("do")) {
                        mJSIF.do();
                    }
                }

                return true;
            }

            return false;
        }
    });
}

最後,我們還是可以如使用原生的 JavaScript Interface 一般,在 HTML5 + JavaScript 中呼叫自定的 API:
<script>
window.Fred.do();
</script>

簡單來說,就是創造假的 URL 和相應的 Parser,去接收從 JavaScript 傳來的要求。

後記

如果說一直專心並精通於某樣領域的人稱之為專家,那筆者肯定不是 Android 專家。但是,好在長久來與 Open Source 拼命所練就出來的功夫,無論什麼樣的火,總算都能夠快速找到火源和撲滅,應該可以算是滅火專家吧。 :-D

留言

  1. 想請教一下,不知道大大能否認同我以下的說法。
    我總認為硬體廠支援度不同,JavaScript的問題會加更繁雜。尤其像是HTC做了Sense更動了底層,是改得完善,還是製造了更多問題。當然我這個說法一直沒時間去證實。大家可以測測看,用裝有HTC Sense的手機和其他廠牌作比較,來瀏覽http://repl.it,比較heavy的javascript。

    回覆刪除
  2. You got the point!

    其實 JavaScript 只是一個小部份而已,整個 Android 到處充滿這樣的問題。

    而原因出在每一家廠商都想當微軟或Apple。但卻沒有『真正』擁有一個『自己』的 OS。一旦 Google 推出 Android 新版,各廠商還是要跟上,甚至重新修一些以前已經修掉的 Bug。最嚴重的是如果有出貨壓力,很多東西只求『可動』,就如您所說,可能是製造更多問題。

    另一方面,Google 本身就沒有意願顧到產品端,下游廠商也自做自己的,改過的東西當寶一樣不肯釋出。或是改的東西,自己心理有數可能因為下個版本架構變更,要重新來想別的辦法改過,所寫出來的東西都是 Workaround,沒有累積性。

    可悲的是,這狀況無解,因為現實中沒有任何廠商願意相信並追隨非 Google Official 的 Android 版本,就算願意組個聯盟來做,也會因為各自心懷鬼胎而失敗。

    現實只能說,大家不想付授權費,又想當Apple,然後丟『創新』言論讓消費者買單。最慘的是我們這些要出生入死的RD,一直在補破衣服,才一直刺到自己的手。

    回覆刪除
  3. 作者已經移除這則留言。

    回覆刪除
  4. 我手上的 SE X10 (2.3.3) Nexus S (2.3.6) 都沒這問題。我猜是這些廠商 AOSP 沒跟上的關係。

    回覆刪除
  5. 我發現 loadUrl( "javascript: function do() {window.location='http://Supermark:search:null';};" );
    是不行的,但改為 loadUrl("javascript: var do=function() {window.location='http://Supermark:search:null';}; do();" ); 就可以執行了。
    如果有要用的人,要注意一下。

    實驗的方法可以先把組出來的字串貼到 Google Chrome 的網址列上試試看,貼上去以後,最前面要補上"javascript:",再按下enter,就可以試驗了。

    回覆刪除
  6. To elleryq:

    感謝找到問題,那段 code 是我直接從Project 剪過來後修改的,漏掉沒改掉。 :-D

    已經修正了。

    回覆刪除
  7. 額外補充,如果是用 eng mode 去編的 apk,會沒有這問題。這問題會出現在 user mode 下。 :-)

    回覆刪除
  8. 版主,您好:
    我有Android的問題請教您,可否跟您要聯絡電話...(有酬金)
    謝謝!!

    回覆刪除
    回覆
    1. 請先用 email 聯絡我吧,您太神秘了,我不知道如何與您聯繫。:-)

      刪除
  9. 版主,您好:
    我們道緣五明學會
    我有Android2.3.6 看網站影片無法撥放 的問題請教您,可否跟您要聯絡電話...(有酬金)
    謝謝!!
    電話:0920872866

    回覆刪除

張貼留言

這個網誌中的熱門文章

有趣的邏輯問題:是誰在說謊

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(二)

淺談 USB 通訊架構之定義(一)

Reverse SSH Tunnel 反向打洞實錄