Java與js進行交互方法

轉自:https://imnerd.org/android-webview-and-js.html

WebView 是移動端應用中的一個控件萨咕,提供了類似瀏覽器可以在 App 中加載網頁的功能∏垂牛現(xiàn)在市面上很多應用都會使用這種方式內嵌一些 h5 頁面用來實現(xiàn)產品功能徐矩。使用這種方式帶來的好處就是支持快速迭代更新抛猫,并且頁面的功能是全網升級附较。當然目前 RN 和 Codorva 給我們帶來的熱更新方案也是可以的饵沧,只是目前 Apple 的態(tài)度很拒絕锨络,這里我們略過不表。在 WebView 中的網頁勢必存在和客戶端進行交互的動作狼牺,進行數(shù)據(jù)的共享羡儿。下面我們就來說說 Android WebView 中 JS 和 Native 的交互方式。

客戶端調用 JS

loadUrl()

我們明白 WebView 其實就是在加載網頁是钥,所以客戶端可以直接訪問?javascript:console.log('hello')?這樣的偽 URL 即可實現(xiàn)在頁面注入需要執(zhí)行的 JS 代碼掠归。調用方法如下:

WebView webview = (WebView) findViewById(R.id.webView);

webview.loadUrl("javascript:console.log('hello')");

這樣我們就實現(xiàn)了調用 JS 的目的了。loadUrl()?的方案從另外一個角度來看可以算是 hack 方案了悄泥,對客戶端來說虏冻,他們的 JS 交互本質上其實就是一個拼接 JS 字符串的過程。

evaluateJavascript()

剛才我們也說了?loadUrl()?不是 Android 的正經解決方法弹囚。好在官方也想到了這點厨相,在 Android 4.4+ 之后,官方給提供了原生的方法支持調用鸥鹉,那就是?evaluateJavascript()蛮穿。這個方法最大的好處就是能夠直接在一次執(zhí)行的時候獲取到 JS 返回的結果。如果是使用?loadUrl()?的方式的話毁渗,執(zhí)行完后對客戶端來說這句話就結束了践磅,如果想要拿到返回的結果的話另外需要 JS 調用客戶端的方法返回。

WebView webview = (WebView) findViewById(R.id.webView);

webview.evaluateJavascript("javascript:Date.now()", new ValueCallback<String>() {

? ? @Override

? ? public void onReceiveValue(String value) {

? ? ? ? System.out.println(value); //1515827651551

? ? }

});

可以看到調用方法和?loadUrl()?非常類似灸异,區(qū)別是增加了一個 callback 方法可以獲取到 JS 返回的值府适。該方法無疑比較優(yōu)秀羔飞,不過對兼容性有要求,目前市面上用還是使用前一種方法的比較多细溅。

JS 調用客戶端

相比較客戶端調用 JS 的方法褥傍,JS 調用客戶端的方法就比較多了,簡單歸類一下其實可以分為注入映射和方法劫持兩種喇聊。注入映射主要是使用官方提供的?addJavascriptInterface()?方法將 Java 對象和 JS 對象進行映射恍风。而方法劫持則是利用 JS 的一些系統(tǒng)方法調用會觸發(fā) Java 的事件回調,然后在回調中進行事件劫持誓篱,從而執(zhí)行客戶端方法朋贬。下面我們來具體看看。

addJavascriptInterface

addJavascriptInterface()?方法的使用非常簡單窜骄,定義好被調用的方法對象后直接配置映射關系即可锦募。

//定義好 Java 接口對象

public class SDK extends Object {

? ? @JavascriptInterface

? ? public void hello(String msg) {

? ? ? ? System.out.println("Hello World");

? ? }

}

//Webview 中調用

WebView webview = (WebView) findViewById(R.id.webview);

webview.addJavascriptInterface(new SDK(), 'sdk');

webview.loadUrl('http://imnerd.org'); //注入后加載頁面

這樣加載的頁面中就可以直接執(zhí)行?sdk.hello()?方法來執(zhí)行客戶端方法了。不過這種官方推薦的方法在 4.2- 的系統(tǒng)上存在遠程執(zhí)行安全漏洞邻遏,對 4.2 以下系統(tǒng)版本有要求的應用需要謹慎使用糠亩。目前來看 4.2 還是需要保持支持的。

URL劫持

URL劫持主要是使用?shouldOverrideUrlLoading()?進行 WebView URL 劫持准验。從方法名可以看出赎线,它是 WebView 攔截 URL 的一種回調,當 WebView 發(fā)生 URL 跳轉的時候會觸發(fā)該回調糊饱。在該回調中我們能夠獲取到前端提供的 URL 地址垂寥。我們通過構造約定協(xié)議的 URL 地址提供給客戶端識別,識別成功后執(zhí)行對應的方法即可另锋。

WebView webview = (WebView) findViewById(R.id.webview);

webview.loadUrl('http://imnerd.org');

webview.setWebViewClient(new WebViewClient() {

? @Override

? public boolean shouldOverrideUrlLoading(WebView view, String url) {

? ? if(url.equals('sdk:hello')) {

? ? ? System.out.println('hello world');

? ? ? return true;

? ? }

? ? return super.shouldOverrideUrlLoading(view, url);

? }

});

方法劫持

同 URL 劫持類似滞项,方法劫持主要是利用 JS 的一些方法執(zhí)行時會觸發(fā) Android 客戶端中的一些回調,通過對前端參數(shù)進行識別來執(zhí)行對應的客戶端代碼夭坪。目前前端主要有以下四種方法會觸發(fā)對應的回調方法文判,對應關系如下:

| JS方法 | 客戶端回調 |

|-------------|------------------|

| alert |?onJsAlert?|

| prompt |?onJsPrompt?|

| confirm |?onJsConfirm?|

| console.log |?onConsoleMessage?|

將這四個方法列在一塊是因為這幾個方法的本質上都是差不多,定義好對應的回調方法即可台舱÷筛埽客戶端具體的配置如下:

//定義好劫持回調類

private class hijackWebChromeClient extends WebChromeClient {?

? public boolean hijack(String text) {

? ? if(text.equals('sdk:hello')) {

? ? ? System.out.println('hello world');

? ? ? return true;

? ? }

? ? return false;

? }

? @Override

? public boolean onJsPrompt(WebView view, String message, String defaultValue, JSPromptResult result) {

? ? if(this.hijack(message)) {

? ? ? return true;

? ? }

? ? return super.onJsPrompt(view, url, message, defaultValue, result);

? }

? @Override

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

? ? if(this.hijack(message)) {

? ? ? return true;

? ? }


? ? return super.onJsAlert(view, url, message, result);

? }

? @Override

? public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {

? ? if(this.hijack(message)) {

? ? ? return true;

? ? }


? ? return super.onJsConfirm(view, url, message, result);

? }

? @Override?

? public boolean onConsoleMessage(ConsoleMessage consoleMessage) {?

? ? String message = consoleMessage.message();

? ? if(this.hijack(message)) {

? ? ? return true;

? ? }


? ? return super.onConsoleMessage(consoleMessage);?

? }?

? @Override?

? public void onConsoleMessage(String message, int lineNumber, String sourceID) {

? ? if(this.hijack(message)) {

? ? ? return true;

? ? }

? ? super.onConsoleMessage(message, lineNumber, sourceID);?

? }?

}

//注入劫持回調類

WebView webview = (WebView) findViewById(R.id.webview);

webview.loadUrl('http://imnerd.org');

webview.setWebChromeClient(new hijackChromeClient);

這里為了方便展示,將所有回調的方法都寫全了竞惋,實際上在實際的使用過程中一般都是約定好一種調用方式即可。另外?console.log?對應的回調寫了兩種灰嫉,三參數(shù)的是老版本方法拆宛,在新API中已經被廢棄,推薦使用?ConsoleMessage?對象傳參方式讼撒。

總結

以上講述了 JS 調用客戶端的方法浑厚,以及客戶端調用前端的方法股耽。除了這兩種單向調用的方式之外,往往比較多的是 JS 調用客戶端方法钳幅,客戶端再調用 JS 返回結果的雙向調用物蝙。在 JS 調用的時候需要傳入一個回調方法名,然后客戶端直接執(zhí)行回調方法敢艰。這樣就完成了一個完成的信息交流的過程诬乞。

window.hello = function(text) {

? console.log(text);

};

console.log('$hello:{"callback": "hello"}');

webview.loadUrl('javascript:hello("hello world")');

這些調用方法有兩點需要注意:

不管是前端調用還是客戶端調用,所有的調用的結果返回都是異步的钠导≌鸺担客戶端?loadUrl()?需要另外通過 JS 異步回調客戶端方法告訴結果,evaluateJavascript()?也需要傳如一個異步回調方法牡属。前端調用中?addJavascriptInterface()?是無返回值的票堵,而方法劫持中,需要等待客戶端回調我們的 JS 方法才能異步獲取到數(shù)據(jù)逮栅。所以我們需要對異步通信進行妥善處理悴势。

由于 JS 和客戶端無法實現(xiàn)內存共享,所以所有的數(shù)據(jù)必須字符串化措伐,只能通過字符串進行交流特纤。例如兩邊的復雜對象數(shù)據(jù),需要使用類似 JSON 的格式進行字符串化废士,而文件/圖片等二進制數(shù)據(jù)最好使用 base64 字符串化叫潦。

后記

基本上交互的基本方式就是以上幾種,不過有人將通信機制進行了封裝官硝,形成一套完善的 WebviewJSBridge 方案矗蕊,提供了客戶端調前端,前端調用客戶端的系統(tǒng)解決方案氢架。例如?lzyzsd/JsBridge?項目傻咖,我們從代碼中可以看到,其實它在底層是使用了 URL 劫持的方法與 JS 進行交互岖研。雖然原理簡單卿操,不過它提供了系統(tǒng)方案,同時也統(tǒng)一了 Android 和 iOS 多端的調用方法孙援,如果是準備從0開始實現(xiàn)交互的話推薦使用害淤。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拓售,隨后出現(xiàn)的幾起案子窥摄,更是在濱河造成了極大的恐慌,老刑警劉巖础淤,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崭放,死亡現(xiàn)場離奇詭異哨苛,居然都是意外死亡,警方通過查閱死者的電腦和手機币砂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門建峭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人决摧,你說我怎么就攤上這事亿蒸。” “怎么了蜜徽?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵祝懂,是天一觀的道長。 經常有香客問我拘鞋,道長砚蓬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任盆色,我火速辦了婚禮灰蛙,結果婚禮上,老公的妹妹穿的比我還像新娘隔躲。我一直安慰自己摩梧,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布宣旱。 她就那樣靜靜地躺著仅父,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浑吟。 梳的紋絲不亂的頭發(fā)上笙纤,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音组力,去河邊找鬼省容。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播梧宫,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笼蛛!你這毒婦竟也來了?” 一聲冷哼從身側響起蛉鹿,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤伐弹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后榨为,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惨好,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年随闺,在試婚紗的時候發(fā)現(xiàn)自己被綠了日川。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡矩乐,死狀恐怖龄句,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情散罕,我是刑警寧澤分歇,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站欧漱,受9級特大地震影響职抡,放射性物質發(fā)生泄漏。R本人自食惡果不足惜误甚,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一缚甩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窑邦,春花似錦擅威、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞧筛,卻和暖如春厉熟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驾窟。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工庆猫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绅络。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓月培,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恩急。 傳聞我的和親對象是個殘疾皇子杉畜,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容