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)。
定義將會用到的變數:
初始化 WebView 時加上 Workaround:
最後,我們還是可以如使用原生的 JavaScript Interface 一般,在 HTML5 + JavaScript 中呼叫自定的 API:
簡單來說,就是創造假的 URL 和相應的 Parser,去接收從 JavaScript 傳來的要求。
後記
如果說一直專心並精通於某樣領域的人稱之為專家,那筆者肯定不是 Android 專家。但是,好在長久來與 Open Source 拼命所練就出來的功夫,無論什麼樣的火,總算都能夠快速找到火源和撲滅,應該可以算是滅火專家吧。 :-D
這次碰到的問題就是 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
想請教一下,不知道大大能否認同我以下的說法。
回覆刪除我總認為硬體廠支援度不同,JavaScript的問題會加更繁雜。尤其像是HTC做了Sense更動了底層,是改得完善,還是製造了更多問題。當然我這個說法一直沒時間去證實。大家可以測測看,用裝有HTC Sense的手機和其他廠牌作比較,來瀏覽http://repl.it,比較heavy的javascript。
You got the point!
回覆刪除其實 JavaScript 只是一個小部份而已,整個 Android 到處充滿這樣的問題。
而原因出在每一家廠商都想當微軟或Apple。但卻沒有『真正』擁有一個『自己』的 OS。一旦 Google 推出 Android 新版,各廠商還是要跟上,甚至重新修一些以前已經修掉的 Bug。最嚴重的是如果有出貨壓力,很多東西只求『可動』,就如您所說,可能是製造更多問題。
另一方面,Google 本身就沒有意願顧到產品端,下游廠商也自做自己的,改過的東西當寶一樣不肯釋出。或是改的東西,自己心理有數可能因為下個版本架構變更,要重新來想別的辦法改過,所寫出來的東西都是 Workaround,沒有累積性。
可悲的是,這狀況無解,因為現實中沒有任何廠商願意相信並追隨非 Google Official 的 Android 版本,就算願意組個聯盟來做,也會因為各自心懷鬼胎而失敗。
現實只能說,大家不想付授權費,又想當Apple,然後丟『創新』言論讓消費者買單。最慘的是我們這些要出生入死的RD,一直在補破衣服,才一直刺到自己的手。
作者已經移除這則留言。
回覆刪除謝謝提供寶貴的資訊
回覆刪除我手上的 SE X10 (2.3.3) Nexus S (2.3.6) 都沒這問題。我猜是這些廠商 AOSP 沒跟上的關係。
回覆刪除我發現 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,就可以試驗了。
To elleryq:
回覆刪除感謝找到問題,那段 code 是我直接從Project 剪過來後修改的,漏掉沒改掉。 :-D
已經修正了。
額外補充,如果是用 eng mode 去編的 apk,會沒有這問題。這問題會出現在 user mode 下。 :-)
回覆刪除版主,您好:
回覆刪除我有Android的問題請教您,可否跟您要聯絡電話...(有酬金)
謝謝!!
請先用 email 聯絡我吧,您太神秘了,我不知道如何與您聯繫。:-)
刪除版主,您好:
回覆刪除我們道緣五明學會
我有Android2.3.6 看網站影片無法撥放 的問題請教您,可否跟您要聯絡電話...(有酬金)
謝謝!!
電話:0920872866