Android WebView —— Java 與 JavaScript 交互總結(jié)

相比于 Native App 和 Web App,Hybrid App 憑借其迭代靈活环戈、控制自如闷板、多端同步的優(yōu)勢在應用市場上越發(fā)顯得優(yōu)勝,主要得力于院塞,其將變更頻繁的部分產(chǎn)品功能使用 H5 開發(fā)并在客戶端中借助 WebView 控件嵌入應用當中遮晚。所以,開發(fā)中我們總會遇到原生 Java 代碼與網(wǎng)頁中的 Js 代碼之間相互調(diào)用從而產(chǎn)生的交互問題拦止。

Java 與 Js 彼此調(diào)用的前提是設置 WebView 支持 JavaScript 功能:

mWebView.getSettings().setJavaScriptEnabled(true);

Java 調(diào)用 Js

第一步县遣,在網(wǎng)頁中使用 Js 定義提供給 Java 訪問的方法糜颠,就像普通方法定義一樣,如:

<script type="text/javascript">

? ? function javaCallJs(message){

? ? ? ? alert(message);

? ? }

</script>

第二步萧求,在 Java 代碼中按照 "javascript:XXX" 的 Url 格式使用 WebView 加載訪問即可:

mWebView.loadUrl("javascript:javaCallJs(" + "'Message From Java'" + ")");

注意:String 類型的參數(shù)需要使用單引號 “'” 包裹括蝠,數(shù)組類型的參數(shù)則不用,如:javascript:javaCallJs([01, 02, 03])饭聚,其他復雜類型的參數(shù)可以轉(zhuǎn)換為 Json 字符串的形式傳遞忌警。


Js 調(diào)用 Java

第一步,在 Java 對象中定義 Js 訪問的方法秒梳,如:

@JavascriptInterface

public void jsCallJava(String message){

? ? Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

}

注意事項:提供給 Js 訪問的屬性和方法必須定義為 public 類型法绵,并且添加注解 @JavascriptInterface。在 API 17 及更高版本的系統(tǒng)中酪碘,任何暴露給 Js 訪問的 Java 接口都需要添加這個注解朋譬,否則會報異常:Uncaught TypeError: Object [object Object] has no method 'XXX'。系統(tǒng)這種做法也是為了降低應用的安全隱患兴垦,因為在之前的版本中徙赢,Js 可以通過反射的方式訪問注入 WebView 中的 Java 對象的 public 類型 field 和 method,從而隨意修改宿主程序探越。

第二步狡赐,將提供給 Js 訪問的接口內(nèi)容所屬的 Java 對象注入 WebView 中:

mWebView.addJavascriptInterface(MainActivity.this, "main");

addJavascriptInterface(Object object, String name) 參數(shù)說明:object 表示 Js 訪問的接口內(nèi)容所在的 Java 對象;name 表示 Js 調(diào)用 Java 代碼時的接口名稱钦幔,與 Js 中的調(diào)用保持一致即可枕屉。

第三步,Js 按照指定的接口名訪問 Java 代碼鲤氢,有如下兩種寫法:

<button type="button" onClick="javascript:main.jsCallJava('Message From Js')" >Js Call Java</button>

<!--<button type="button" onClick="window.main.jsCallJava('Message From Js')" >Js Call Java</button>-->

這里簡單提供一個可供測試的 Html 網(wǎng)頁和 Activity 代碼:

test.html:

<html>

? ? <head>?

? ? ? ? <meta http-equiv="Content-Type"? content="text/html;charset=UTF-8">

? ? ? ? <script type="text/javascript">

? ? ? ? ? ? function javaCallJs(message){

? ? ? ? ? ? ? ? alert(message);

? ? ? ? ? ? }

? ? ? ? </script>

? ? </head>?

? ? <body>

? ? ? ? <button type="button" onClick="window.main.jsCallJava('Message From Js')" >Js Call Java</button>

? ? </body>?

</html>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

? ? private WebView mWebView;

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_main);

? ? ? ? Toolbar mToolbarTb = (Toolbar) findViewById(R.id.tb_toolbar);

? ? ? ? setSupportActionBar(mToolbarTb);

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

? ? ? ? mWebView.getSettings().setJavaScriptEnabled(true);

? ? ? ? mWebView.loadUrl("file:///android_asset/test.html");

? ? ? ? mWebView.addJavascriptInterface(MainActivity.this, "main");

? ? ? ? mWebView.setWebChromeClient(new WebChromeClient() {

? ? ? ? ? ? @Override

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

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

? ? ? ? ? ? }

? ? ? ? });

? ? }

? ? public void javaCallJs(View v){

? ? ? ? mWebView.loadUrl("javascript:javaCallJs(" + "'Message From Java'" + ")");

? ? }

? ? @JavascriptInterface

? ? public void jsCallJava(String message){

? ? ? ? Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

? ? }

? ? @Override

? ? public boolean onCreateOptionsMenu(Menu menu) {

? ? ? ? getMenuInflater().inflate(R.menu.search, menu);

? ? ? ? return super.onCreateOptionsMenu(menu);

? ? }

}

效果圖:https://lc-gold-cdn.xitu.io/0cb1ac02567d72f4d69c.gif?imageView2/0/w/1280/h/960/format/webp/ignore-error/1

注意:無論是 Java 調(diào)用 Js 還是 Js 調(diào)用 Java搀擂,只能通過參數(shù)傳遞數(shù)據(jù),而無法獲取彼此方法的返回值卷玉!解決方案就是額外添加一層回調(diào)來達到這個目的哨颂。比如 Java 調(diào)用 Js 的方法,Js 計算結(jié)束所得結(jié)果不能通過 return 語句返回給 Java 調(diào)用者相种,而是再回調(diào) Java 的另一個方法威恼,通過傳參的形式傳遞給 Java。

注意事項

1.使用 loadUrl() 方法實現(xiàn) Java 調(diào)用 Js 功能時蚂子,必須放置在主線程中沃测,否則會發(fā)生崩潰異常。比如修改上面的代碼:

new Thread(new Runnable() {

? ? @Override

? ? public void run() {

? ? ? ? mWebView.loadUrl("javascript:javaCallJs(" + "'Message From Java'" + ")");

? ? }

}).start();

運行時會得到如下 logcat 異常信息:

java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'Thread-18022'. All WebView methods must be called on the same thread.

如果真的在子線程中遇到調(diào)用 Js 的功能食茎,也要將其轉(zhuǎn)換到主線程中去:

mWebView.post(new Runnable() {

? ? @Override

? ? public void run() {

? ? ? ? mWebView.loadUrl("javascript:javaCallJs(" + "'Message From Java'" + ")");

? ? }

});

2.Js 調(diào)用 Java 方法時蒂破,不是在主線程 (Thread Name:main) 中運行的,而是在一個名為 JavaBridge 的線程中執(zhí)行的别渔,通過如下代碼可以測試:

@JavascriptInterface

? ? public void jsCallJava(String message){

? ? ? ? Log.i("thread", Thread.currentThread().getName());

? ? ? ? Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

? ? }

所以這里需要注意的是附迷,當 Js 調(diào)用 Java 時惧互,如果需要 Java 繼續(xù)回調(diào) Js,千萬別在 JavascriptInterface 方法體中直接執(zhí)行 loadUrl() 方法喇伯,而是像前面一樣進行線程切換操作喊儡。

3.代碼混淆時,記得保持 JavascriptInterface 內(nèi)容稻据,在 proguard 文件中添加如下類似規(guī)則 (有關類名按需修改):

keepattributes *Annotation*

keepattributes JavascriptInterface

-keep public class com.mypackage.MyClass$MyJavaScriptInterface

-keep public class * implements com.mypackage.MyClass$MyJavaScriptInterface

-keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface {

? ? <methods>;

}

Url 攔截

除了上面這種 Java 與 Js 互調(diào)方法的方式艾猜,還可以利用 WebView 攔截 Url 的方式實現(xiàn)原生應用與 H5 之間的交互動作。通過 WebViewClient 提供的接口攔截網(wǎng)頁內(nèi)諸如二級跳轉(zhuǎn)的 Url 鏈接捻悯,便可以進行業(yè)務邏輯上的判斷處理匆赃、Url 參數(shù)傳遞等功能,如:

mWebView.setWebViewClient(new WebViewClient(){

? ? @Override

? ? public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {

? ? ? ? // request.getUrl()

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

? ? }

});

注意:過去常用的 shouldOverrideUrlLoading(WebView view, String url) 方法已經(jīng)被廢棄今缚。

參考使用

通過 Java 與 Js 之間的交互可以做很多事情算柳,比如獲取網(wǎng)頁中的圖片,利用原生控件予以展示姓言,類似響應微信公眾號文章中的圖片點擊事件瞬项。參考代碼如下:

public class MainActivity extends AppCompatActivity {

? ? private WebView mWebView;

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

? ? ? ? super.onCreate(savedInstanceState);

? ? ? ? setContentView(R.layout.activity_main);

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

? ? ? ? mWebView.getSettings().setJavaScriptEnabled(true);

? ? ? ? mWebView.loadUrl("https://www.taobao.com/");

? ? ? ? mWebView.addJavascriptInterface(new MyJavascriptInterface(), "imageClick");

? ? ? ? mWebView.setWebViewClient(new MyWebViewClient());

? ? }

? ? /**

? ? * 遍歷 <img> 標簽, 添加圖片點擊事件, 將圖片 Url 地址回調(diào)給 Java 方法

? ? */

? ? private void addImageClickListner() {

? ? ? ? mWebView.loadUrl("javascript:(function(){" +

? ? ? ? ? ? ? ? "var objs = document.getElementsByTagName(\"img\"); " +

? ? ? ? ? ? ? ? "for(var i=0;i<objs.length;i++)? " +

? ? ? ? ? ? ? ? "{"

? ? ? ? ? ? ? ? + "? ? objs[i].onclick=function()? " +

? ? ? ? ? ? ? ? "? ? {? "

? ? ? ? ? ? ? ? + "? ? ? ? window.imageClick.openImage(this.src);? " +

? ? ? ? ? ? ? ? "? ? }? " +

? ? ? ? ? ? ? ? "}" +

? ? ? ? ? ? ? ? "})()");

? ? }

? ? public class MyJavascriptInterface {

? ? ? ? public MyJavascriptInterface() {

? ? ? ? }

? ? ? ? @android.webkit.JavascriptInterface

? ? ? ? public void openImage(String imageUrl) {

? ? ? ? ? ? Log.i("imageUrl", imageUrl);

? ? ? ? ? ? // TODO 獲取圖片地址后, 通過原生控件 ImageView 展示, 添加縮放、保存等功能

? ? ? ? }

? ? }

? ? private class MyWebViewClient extends WebViewClient {

? ? ? ? @Override

? ? ? ? public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {

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

? ? ? ? }

? ? ? ? @Override

? ? ? ? public void onPageFinished(WebView view, String url) {

? ? ? ? ? ? super.onPageFinished(view, url);

? ? ? ? ? ? addImageClickListner();

? ? ? ? }

? ? }

}

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末何荚,一起剝皮案震驚了整個濱河市囱淋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兽泣,老刑警劉巖绎橘,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唠倦,居然都是意外死亡,警方通過查閱死者的電腦和手機涮较,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門稠鼻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狂票,你說我怎么就攤上這事候齿。” “怎么了闺属?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵慌盯,是天一觀的道長。 經(jīng)常有香客問我掂器,道長亚皂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任国瓮,我火速辦了婚禮灭必,結(jié)果婚禮上狞谱,老公的妹妹穿的比我還像新娘。我一直安慰自己禁漓,他們只是感情好跟衅,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著播歼,像睡著了一般贮喧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上品洛,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天试吁,我揣著相機與錄音,去河邊找鬼谒撼。 笑死食寡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的廓潜。 我是一名探鬼主播抵皱,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辩蛋!你這毒婦竟也來了呻畸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤悼院,失蹤者是張志新(化名)和其女友劉穎伤为,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體据途,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡绞愚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了颖医。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片位衩。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熔萧,靈堂內(nèi)的尸體忽然破棺而出糖驴,到底是詐尸還是另有隱情,我是刑警寧澤佛致,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布贮缕,位于F島的核電站,受9級特大地震影響俺榆,放射性物質(zhì)發(fā)生泄漏感昼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一肋演、第九天 我趴在偏房一處隱蔽的房頂上張望抑诸。 院中可真熱鬧烂琴,春花似錦、人聲如沸蜕乡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽层玲。三九已至号醉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辛块,已是汗流浹背畔派。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留润绵,地道東北人线椰。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像尘盼,于是被迫代替她去往敵國和親憨愉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355