Android中Java與JavaScript交互的幾種方式

0x00 背景

近年來,由于開發(fā)成本,開發(fā)效率础芍,用戶需求等原因灸叼,對于移動 App 的開發(fā)方案已經(jīng)從原生開發(fā)趨向于混合(Hybrid)開發(fā)的方式神汹,甚至于說直接基于一些大的 App 平臺提供的 JS SD K直接開發(fā) Web 頁面,例如微信古今、手機QQ等超級 App邻耕。最近氨淌,就在我寫這篇文章的時候,在微信公開課 Pro 活動上,張小龍?zhí)岢隽宋⑿抨P(guān)于“應(yīng)用號”的規(guī)劃富弦,具體請看這篇文章預(yù)埋兩年的線索,傳說會干掉 App 的微信應(yīng)用號是什么虫埂?贷盲,可見混合開發(fā)這種開發(fā)方式的重要性。

基于混合開發(fā)方式的優(yōu)勢是非常明顯的:它既能使用原生的一些手機特性拟逮,而且又擁有隨時發(fā)布的能力撬统。前者是通過提供相關(guān) JS API 使 Web 頁面具有一些原生的功能,而后者是 Web 頁面天生具有的特性敦迄。也就是說:我們在開發(fā)原生應(yīng)用的基礎(chǔ)上嵌入 WebView宪摧,但是整體的架構(gòu)使用原生應(yīng)用提供。關(guān)于這種開發(fā)方式颅崩,如果你想要更進(jìn)一步了解几于,請參考這篇《Hybrid App 開發(fā)實戰(zhàn)》

而本文的目的就是想要知道這些 JS API 是如何實現(xiàn)的沿后,或者更直白一點:Android 中 Java 與 JavaScript 是怎么交互/通信的沿彭?你要知道 JavaScript 是運行在瀏覽器環(huán)境下的腳本語言。當(dāng)然尖滚,網(wǎng)上關(guān)于這方面的資料非常多喉刘,但是我這里還是想總結(jié)與實踐一下瞧柔,因為之前的項目需求開發(fā)接觸 Hybrid 開發(fā)這種方式,在 Android 上寫了一些 JS API睦裳,后來又接觸了前端開發(fā)造锅,開始使用這些 JS API,所以很想了解一下其中的相關(guān)原理廉邑。

0x01 兩類交互方式

在進(jìn)入主題之前哥蔚,還需要提到一點:本文主要是涉及 JavaScript 如何調(diào)用 Java?而反過來蛛蒙,Java 調(diào)用 JavaScript 因為比較簡單一點糙箍,我這里稍微提下,直接上代碼:


String url = "javascript:" + methodName + "(" + jsonParams + ");void(0);"

webView.loadUrl(url);

就是這么簡單牵祟,直接調(diào)用 WebView 的loadUrl(url)方法深夯,當(dāng)然參數(shù) url 是比較特殊,前面的javascript:偽協(xié)議讓我們可以通過一個鏈接來調(diào)用 JavaScript 函數(shù)诺苹,中間methodName是 JavaScript 中實現(xiàn)的函數(shù)咕晋,jsonParams是傳入的參數(shù)。關(guān)于后面的void(0);收奔,可以參考這篇文檔《void operator》中的說明掌呜。Java 調(diào)用 JavaScript 的方式就說到這里,下面我們繼續(xù)討論 JavaScript 是如何調(diào)用 Java 的筹淫,實現(xiàn)的方法有很多種站辉,我把它歸為兩類:

  1. Android WebView api 本身就支持的方式addJavascriptInterface

  2. 通過偽協(xié)議攔截頁面的“請求”损姜,即需要 JavaScript 與 Java 端(native)事先約定饰剥,方法有shouldOverrideUrlLoadingwindow.prompt摧阅、Console.logalert等汰蓉,我們通常稱這種方式為JsBridge

addJavascriptInterface

首先棒卷,我們來看第一類addJavascriptInterface顾孽,其方法聲明如下所示:


public void addJavascriptInterface(Object object, String name)

該方法將參數(shù)中提供的 Java 對象(object)注入到 WebView 中。該對象會被注入到頁面主框架(main frame)的 Javascript 上下文中比规,通過參數(shù)中提供的名稱(name)訪問若厚。具體的使用方式,Android 官方文檔有給出:


class JsObject {
    @JavascriptInterface

    public String toString() {

        return "injectedObject";

    }
}

webView.addJavascriptInterface(new JsObject(), "injectedObject"); // 只有頁面再加載蜒什,該對象才可見

webView.loadData("", "text/html", null);

webView.loadUrl("javascript:alert(injectedObject.toString())");

這個例子大家一看就很明了测秸,addJavascriptInterface這種方式非常簡單好用。但是這種方式在 Android 4.2之前的版本中存在安全問題:在4.2之前被注入的對象的所有公共方法(包括從父類繼承過來的方法)都可以被訪問到;在4.2以后霎冯,只有通過@JavascriptInterface注解的公共方法才能被訪問铃拇。具體請看這篇WebView中接口隱患與手機掛馬利用

除此之外,對于該方法還需要注意的是:

  • 在該方式下沈撞,JavaScript 調(diào)用 Java 通過 WebView 的一個私有后臺線程慷荔,所以,需要我們需要注意線程安全缠俺;

  • Java對象的域是不可訪問的显晶;

  • 在 Android 5.0及以上,被注入對象的方法可被 JavaScript 枚舉晋修。

下面吧碾,我們來看第二類方法凰盔,這類方法的特點是:JS 端與 Native 端存在一個偽協(xié)議墓卦,Native 端口根據(jù)這個協(xié)議去偵聽/截獲頁面的相關(guān)行為。所以户敬,我們首先需要定義一個協(xié)議(可參考上面的javascript:偽協(xié)議):協(xié)議名+方法名+相關(guān)參數(shù)落剪。在本文中,我們假定該協(xié)議格式為:"jsbridge://" + "method" + "jsonParams"尿庐,整個協(xié)議就是個特殊的字符串忠怖。之后我們要做的工作是把這個字符串從 JS 端傳到 Native 端,然后 Native 去解析這個字符串并執(zhí)行相關(guān)代碼抄瑟。這其中的關(guān)鍵就是如何傳這個字符串凡泣,方法有很多,我們一個一個來看:

shouldOverrideUrlLoading

shouldOverrideUrlLoading是類WebViewClient中的一個方法皮假。它的作用是控制當(dāng)前 Webview 加載新 url 的相關(guān)行為鞋拟。在默認(rèn)情況下,Webview 沒有設(shè)置 WebViewClient惹资,所以它會請求 Activity Manager 來處理該 url (一般就是調(diào)用相關(guān)瀏覽器應(yīng)用)贺纲。該方法的方法聲明如下:


public boolean shouldOverrideUrlLoading(Webview view, String url)

從方法聲明可知,我們將通過參數(shù)String url來傳遞我們協(xié)議字符串褪测,所以在 Native 端我們創(chuàng)建設(shè)置 WebViewClient 子類猴誊,該子類覆寫shouldOverrideUrlLoading方法,這個就可以攔截 Webview 加載新 url 了侮措。那么在 JS 端該如何生成這個 url 呢懈叹?一般我們可以創(chuàng)建一個 iframe,設(shè)置它的 src 屬性分扎,并將其添加到頁面的文檔流中澄成,或者直接設(shè)置window.location.href。相關(guān)代碼如下:


// 方式(1) 直接設(shè)置window.location.href

window.location.href = "jsbridge://toast?{msg:jstojava}";

// 方式(2) 在需要js調(diào)用native api的時候,js在頁面中創(chuàng)建一個不可見的iframe,設(shè)置這個iframe的地址

var iframe = document.createElement("iframe");

iframe.style.display = "none";

document.documentElement.appendChild(iframe);

iframe.src = "jsbridge://toast?{msg:jstojava}";

prompt环揽,console.log略荡,alert

這部分我們要講的三個方法(原理同上),都是瀏覽器實現(xiàn)的API接口:

  1. prompt:默認(rèn)顯示一個對話框歉胶,對話框中包含一條文字信息汛兜,用來提示用戶輸入文字;

  2. console.log:默認(rèn)向web控制臺輸出一條消息通今;

  3. alert:默認(rèn)用于顯示帶有一條指定消息和一個 OK 按鈕的警告框粥谬。

對于上述三個方法的默認(rèn)行為,大家可通過 chrome 的開發(fā)者工具試試辫塌,調(diào)用方式非常簡單漏策。所以,我們只要能攔截這三個方法的默認(rèn)行為并獲得其中的參數(shù)即可臼氨。而 Android 中的類WebChromeClient確實存在相對應(yīng)的方法來處理掺喻,只要覆寫 WebChromeClient 中相對應(yīng)的三個方法,并設(shè)置 Webview储矩。下面是這三個方法的方法聲明感耙,要注意的是這三個方法的參數(shù)差異是有點大,具體使用那個參數(shù)可能需要與 JS 端配合:


class WebChromeClientImp extends WebChromeClient {

    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {

    }

    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {

    }

    @Override
    public boolean onJsAlert(WebView view, String url, String message, JsResult result) {

    }

}

0x02 相關(guān)代碼實現(xiàn)

在上面一節(jié)主要介紹了 JS 與 Java 互相調(diào)用的方法持隧,在這一節(jié)主要是如何這些方法即硼。雖然以上這些還是很簡單的,但寫個 demo 實踐一下還是必要的屡拨。這個 demo 已上傳至 github 只酥,有興趣的同學(xué),請點這里 jsbridgeDemo呀狼。這個demo的功能如下:

  1. 利用上述的兩種方式實現(xiàn) JS 調(diào)用 Native 端的 toast 功能裂允;

  2. Native 端調(diào)用 JS 端的方法實現(xiàn)修改頁面背景色的功能。

在這個 demo 實現(xiàn)比較簡單赠潦,我這里就稍微說明幾點:

第一叫胖,這個 demo 項目需要一個頁面來承載,這個頁面可以發(fā)布在外網(wǎng)上或者它就寫在本地項目中她奥。本文使用了后面這種方式瓮增,因為比較方便,具體操作方式是:在 Android 項目的根目錄下創(chuàng)建assets目錄(如果該目錄不存在的話)哩俭,并創(chuàng)建頁面jsdemo.html在該目錄下绷跑,代碼中加載頁面的方式為webView.loadUrl("file:///android_asset/jsdemo.html")

第二凡资,JS 端調(diào)用prompt()alert()后砸捏,Native 端必須給 JS 端回調(diào)確認(rèn)谬运,否則會有問題,因為兩者都是會彈框垦藏,需要給響應(yīng):


result.confirm();

第三梆暖,在 Native 代碼需要設(shè)置 Webview 啟用WJavaScript:


WebSettings webSettings = webView.getSettings();

webSettings.setJavaScriptEnabled(true);

第四,目前實現(xiàn)只是對偽協(xié)議做了字符串比較掂骏,最好的方式當(dāng)然是在 JS 和 Native 端各自封裝相對模塊來處理相關(guān)邏輯轰驳,后續(xù)有時間我會做下修改。

0x03 總結(jié)

本文主要介紹了 Java 與 JavaScript 相互調(diào)用的方式弟灼,特別是 JavaScript 調(diào)用 Java 的幾種方法:當(dāng)然级解,Android 原生提供的方式由于安全的問題是不被推薦的,但是隨著 Android 4.2及之后版本的普及田绑,這未必不是一種好的方式勤哗;關(guān)于其他幾種方法應(yīng)該都是可以使用的,但都需要自己做一定封裝掩驱;還有就是這幾種方法的相關(guān)調(diào)用性能估計是不一樣芒划,大家在選擇的時候需要做下對比,本文暫時沒有涉及到昙篙。

0x04 參考文章

  1. 在WebView中如何讓JS與Java安全地互相調(diào)用
  2. android webview
  3. Load local HTML file into WebView
  4. WebView中實現(xiàn)js與java互相調(diào)用
  5. Building Web Apps in WebView
  6. 微信的jsbridge實現(xiàn)
  7. 讓Java跟Javascript更加親密
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腊状,一起剝皮案震驚了整個濱河市诱咏,隨后出現(xiàn)的幾起案子苔可,更是在濱河造成了極大的恐慌,老刑警劉巖袋狞,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焚辅,死亡現(xiàn)場離奇詭異,居然都是意外死亡苟鸯,警方通過查閱死者的電腦和手機同蜻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來早处,“玉大人湾蔓,你說我怎么就攤上這事∑霭穑” “怎么了默责?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咸包。 經(jīng)常有香客問我桃序,道長,這世上最難降的妖魔是什么烂瘫? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任媒熊,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芦鳍。我一直安慰自己嚷往,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布柠衅。 她就那樣靜靜地躺著间影,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茄茁。 梳的紋絲不亂的頭發(fā)上魂贬,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音裙顽,去河邊找鬼付燥。 笑死,一個胖子當(dāng)著我的面吹牛愈犹,可吹牛的內(nèi)容都是我干的键科。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼漩怎,長吁一口氣:“原來是場噩夢啊……” “哼勋颖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起勋锤,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤饭玲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叁执,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茄厘,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年谈宛,在試婚紗的時候發(fā)現(xiàn)自己被綠了次哈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡吆录,死狀恐怖窑滞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恢筝,我是刑警寧澤哀卫,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站滋恬,受9級特大地震影響聊训,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恢氯,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一带斑、第九天 我趴在偏房一處隱蔽的房頂上張望鼓寺。 院中可真熱鬧,春花似錦勋磕、人聲如沸妈候。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苦银。三九已至,卻和暖如春赶站,著一層夾襖步出監(jiān)牢的瞬間幔虏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工贝椿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留想括,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓烙博,卻偏偏與公主長得像瑟蜈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子渣窜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容