閱讀本文前請(qǐng)先閱讀:Android開發(fā):最全面、最易懂的Webview詳解
Android與JS通過WebView互相調(diào)用方法,實(shí)際上是:
二者溝通的橋梁是WebView
對(duì)于Android調(diào)用JS代碼的方法有2種:
1. 通過WebView的loadUrl()
2. 通過WebView的evaluateJavascript()
對(duì)于JS調(diào)用Android代碼的方法有3種:
1. 通過WebView的addJavascriptInterface()進(jìn)行對(duì)象映射
2. 通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調(diào)攔截 url
3. 通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調(diào)攔截JS對(duì)話框alert()、confirm()、prompt() 消息
2.1 Android通過WebView調(diào)用 JS 代碼
對(duì)于Android調(diào)用JS代碼的方法有2種:
1. 通過WebView的loadUrl()
2. 通過WebView的evaluateJavascript()
方式1:通過WebView的loadUrl()
步驟1:將需要調(diào)用的JS代碼以.html格式放到src/main/assets文件夾里
需要加載JS代碼:javascript.html
// 文本名:javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
// JS代碼
<script>
// Android需要調(diào)用的方法
function callJS(){
alert("Android調(diào)用了JS的callJS方法");
}
</script>
</head>
</html>
步驟2:在Android里通過WebView設(shè)置調(diào)用JS代碼
Android代碼:MainActivity.java
注釋已經(jīng)非常清楚
public class MainActivity extends AppCompatActivity {
WebView mWebView;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView =(WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// 設(shè)置與Js交互的權(quán)限
webSettings.setJavaScriptEnabled(true);
// 設(shè)置允許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先載入JS代碼
// 格式規(guī)定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 通過Handler發(fā)送消息
mWebView.post(new Runnable() {
@Override
public void run() {
// 注意調(diào)用的JS方法名要對(duì)應(yīng)上
// 調(diào)用javascript的callJS()方法
mWebView.loadUrl("javascript:callJS()");
}
});
}
});
// 由于設(shè)置了彈窗檢驗(yàn)調(diào)用結(jié)果,所以需要支持js對(duì)話框
// webview只是載體,內(nèi)容的渲染需要使用webviewChromClient類去實(shí)現(xiàn)
// 通過設(shè)置WebChromeClient對(duì)象處理JavaScript的對(duì)話框
//設(shè)置響應(yīng)js 的Alert()函數(shù)
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this);
b.setTitle("Alert");
b.setMessage(message);
b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
});
b.setCancelable(false);
b.create().show();
return true;
}
});
}
}
特別注意:JS代碼調(diào)用一定要在 onPageFinished() 回調(diào)之后才能調(diào)用,否則不會(huì)調(diào)用。
onPageFinished()屬于WebViewClient類的方法,主要在頁面加載結(jié)束時(shí)調(diào)用
// 只需要將第一種方法的loadUrl()換成下面該方法即可
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此處為 js 返回的結(jié)果
}
});
}
2.1.2 方法對(duì)比
2.1.3 使用建議
兩種方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2
// Android版本變量
final int version = Build.VERSION.SDK_INT;
// 因?yàn)樵摲椒ㄔ?Android 4.4 版本才可使用,所以使用時(shí)需進(jìn)行版本判斷
if (version < 18) {
mWebView.loadUrl("javascript:callJS()");
} else {
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此處為 js 返回的結(jié)果
}
});
}
對(duì)于JS調(diào)用Android代碼的方法有3種:
1. 通過WebView的addJavascriptInterface()進(jìn)行對(duì)象映射
2. 通過 WebViewClient 的shouldOverrideUrlLoading ()方法回調(diào)攔截 url
3. 通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調(diào)攔截JS對(duì)話框alert()、confirm()、prompt() 消息
2.2.1 方法分析
方式1:通過 WebView的addJavascriptInterface()進(jìn)行對(duì)象映射
步驟1:定義一個(gè)與JS對(duì)象映射關(guān)系的Android類:AndroidtoJs
AndroidtoJs.java(注釋已經(jīng)非常清楚)
// 繼承自O(shè)bject類
public class AndroidtoJs extends Object {
// 定義JS需要調(diào)用的方法
// 被JS調(diào)用的方法必須加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
System.out.println("JS調(diào)用了Android的hello方法");
}
}
步驟2:將需要調(diào)用的JS代碼以.html格式放到src/main/assets文件夾里
需要加載JS代碼:javascript.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson</title>
<script>
function callAndroid(){
// 由于對(duì)象映射,所以調(diào)用test對(duì)象等于調(diào)用Android映射的對(duì)象
test.hello("js調(diào)用了android中的hello方法");
}
</script>
</head>
<body>
//點(diǎn)擊按鈕則調(diào)用callAndroid函數(shù)
<button type="button" id="button1" onclick="callAndroid()"></button>
</body>
</html>
步驟3:在Android里通過WebView設(shè)置Android類與JS代碼的映射
詳細(xì)請(qǐng)看注釋
public class MainActivity extends AppCompatActivity {
WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// 設(shè)置與Js交互的權(quán)限
webSettings.setJavaScriptEnabled(true);
// 通過addJavascriptInterface()將Java對(duì)象映射到JS對(duì)象
//參數(shù)1:Javascript對(duì)象名
//參數(shù)2:Java對(duì)象名
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS類對(duì)象映射到j(luò)s的test對(duì)象
// 加載JS代碼
// 格式規(guī)定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
僅將Android對(duì)象和JS對(duì)象映射即可
即JS需要調(diào)用Android的方法
以.html格式放到src/main/assets文件夾里
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function callAndroid(){
/*約定的url協(xié)議為:js://webview?arg1=111&arg2=222*/
document.location = "js://webview?arg1=111&arg2=222";
}
</script>
</head>
<!-- 點(diǎn)擊按鈕則調(diào)用callAndroid()方法 -->
<body>
<button type="button" id="button1" onclick="callAndroid()">點(diǎn)擊調(diào)用Android代碼</button>
</body>
</html>
當(dāng)該JS通過Android的mWebView.loadUrl("file:///android_asset/javascript.html")加載后,就會(huì)回調(diào)shouldOverrideUrlLoading (),接下來繼續(xù)看步驟2:
步驟2:在Android通過WebViewClient復(fù)寫shouldOverrideUrlLoading ()
MainActivity.java
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// 設(shè)置與Js交互的權(quán)限
webSettings.setJavaScriptEnabled(true);
// 設(shè)置允許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 步驟1:加載JS代碼
// 格式規(guī)定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
// 復(fù)寫WebViewClient類的shouldOverrideUrlLoading方法
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 步驟2:根據(jù)協(xié)議的參數(shù),判斷是否是所需要的url
// 一般根據(jù)scheme(協(xié)議格式) & authority(協(xié)議名)判斷(前兩個(gè)參數(shù))
//假定傳入進(jìn)來的 url = "js://webview?arg1=111&arg2=222"(同時(shí)也是約定好的需要攔截的)
Uri uri = Uri.parse(url);
// 如果url的協(xié)議 = 預(yù)先約定的 js 協(xié)議
// 就解析往下解析參數(shù)
if ( uri.getScheme().equals("js")) {
// 如果 authority = 預(yù)先約定協(xié)議里的 webview,即代表都符合約定的協(xié)議
// 所以攔截url,下面JS開始調(diào)用Android需要的方法
if (uri.getAuthority().equals("webview")) {
// 步驟3:
// 執(zhí)行JS所需要調(diào)用的邏輯
System.out.println("js調(diào)用了Android的方法");
// 可以在協(xié)議上帶有參數(shù)并傳遞到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
}
);
}
}
如果JS想要得到Android方法的返回值,只能通過 WebView 的 loadUrl ()去執(zhí)行 JS 方法把返回值傳遞回去,相關(guān)的代碼如下:
// Android:MainActivity.java
mWebView.loadUrl("javascript:returnResult(" + result + ")");
// JS:javascript.html
function returnResult(result){
alert("result is" + result);
}
在JS中,有三個(gè)常用的對(duì)話框方法:
方式3的原理:Android通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回調(diào)分別攔截JS對(duì)話框
(即上述三個(gè)方法),得到他們的消息內(nèi)容,然后解析即可。
下面的例子將用攔截 JS的輸入框(即prompt()方法)說明 :
步驟1:加載JS代碼,如下:
javascript.html
以.html格式放到src/main/assets文件夾里
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Carson_Ho</title>
<script>
function clickprompt(){
// 調(diào)用prompt()
var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo " + result);
}
</script>
</head>
<!-- 點(diǎn)擊按鈕則調(diào)用clickprompt() -->
<body>
<button type="button" id="button1" onclick="clickprompt()">點(diǎn)擊調(diào)用Android代碼</button>
</body>
</html>
當(dāng)使用mWebView.loadUrl("file:///android_asset/javascript.html")加載了上述JS代碼后,就會(huì)觸發(fā)回調(diào)onJsPrompt(),具體如下:
步驟2:在Android通過WebChromeClient復(fù)寫onJsPrompt()
public class MainActivity extends AppCompatActivity {
WebView mWebView;
// Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = mWebView.getSettings();
// 設(shè)置與Js交互的權(quán)限
webSettings.setJavaScriptEnabled(true);
// 設(shè)置允許JS彈窗
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
// 先加載JS代碼
// 格式規(guī)定為:file:///android_asset/文件名.html
mWebView.loadUrl("file:///android_asset/javascript.html");
mWebView.setWebChromeClient(new WebChromeClient() {
// 攔截輸入框(原理同方式2)
// 參數(shù)message:代表promt()的內(nèi)容(不是url)
// 參數(shù)result:代表輸入框的返回值
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 根據(jù)協(xié)議的參數(shù),判斷是否是所需要的url(原理同方式2)
// 一般根據(jù)scheme(協(xié)議格式) & authority(協(xié)議名)判斷(前兩個(gè)參數(shù))
//假定傳入進(jìn)來的 url = "js://webview?arg1=111&arg2=222"(同時(shí)也是約定好的需要攔截的)
Uri uri = Uri.parse(message);
// 如果url的協(xié)議 = 預(yù)先約定的 js 協(xié)議
// 就解析往下解析參數(shù)
if ( uri.getScheme().equals("js")) {
// 如果 authority = 預(yù)先約定協(xié)議里的 webview,即代表都符合約定的協(xié)議
// 所以攔截url,下面JS開始調(diào)用Android需要的方法
if (uri.getAuthority().equals("webview")) {
//
// 執(zhí)行JS所需要調(diào)用的邏輯
System.out.println("js調(diào)用了Android的方法");
// 可以在協(xié)議上帶有參數(shù)并傳遞到Android上
HashMap<String, String> params = new HashMap<>();
Set<String> collection = uri.getQueryParameterNames();
//參數(shù)result:代表消息框的返回值(輸入值)
result.confirm("js調(diào)用了Android的方法成功啦");
}
return true;
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
// 通過alert()和confirm()攔截的原理相同,此處不作過多講述
// 攔截JS的警告框
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
// 攔截JS的確認(rèn)框
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
}
);
}
}
2.2.2 三種方式的對(duì)比 & 使用場(chǎng)景
目前很多公司的 App 就只使用一個(gè) WebView 作為整體框架, App 中的所有內(nèi)容全部使用 HTML5 進(jìn)行展示,這樣只需要寫一次 HTML5 代碼,就可以在 Android 和 iOS 平臺(tái)上運(yùn)行,這就是所謂的「 跨平臺(tái) 」。隨著 HTML5 的普及,很多 App 都會(huì)內(nèi)嵌 WebView 來加載 HTML5 頁面,即 Native 和 HTML5 共存,這就是當(dāng)下最流行的「 混合開發(fā) 」。HTML5 除了開發(fā)簡單,還有一個(gè)優(yōu)勢(shì)就是迭代方便, 只需要修改服務(wù)端的 HTML5 頁面,App 會(huì)同步更新,無論是做活動(dòng)推廣 App 還是及時(shí)修復(fù) Bug 都帶來的極大的便利。不過 HTML5 劣勢(shì)也很明顯,受制于國內(nèi)的網(wǎng)速限制。 雖然國內(nèi)已經(jīng)普及了 4g 網(wǎng)絡(luò),但是網(wǎng)速還是不盡如人意。HTML5 加載受限于網(wǎng)絡(luò),沒有原生控件流暢,用戶體驗(yàn)相對(duì)較差, 所以目前完全使用 HTML5 開發(fā) App 并沒有成為主流。我所在的項(xiàng)目組也使用HTML5開發(fā)比較頻繁,這個(gè)時(shí)候了解WebView使用就變得尤為重要了,而WebView的坑也是非常的多,我在開發(fā)中就遇到了許多莫名其妙的問題。 所以準(zhǔn)備寫幾篇文章總結(jié)一下,文章按照類來進(jìn)行分類。WebView開發(fā)常用的類是 WebView , WebSettings , WebViewClient , WebChromeClient。
這里是WebView開發(fā)第一篇: WebView 的使用介紹;
A View that displays web pages. This class is the basis upon which you can roll your own web browser or simply display some online content within your Activity. It uses the WebKit rendering engine to display web pages and includes methods to navigate forward and backward through a history, zoom in and out, perform text searches and more.
總結(jié)起來就是:WebView是一個(gè)基于WebKit引擎的,并且可以在activity展現(xiàn)Web頁面的控件。
<uses-permission android:name="android.permission.INTERNET" />
3.2 添加布局頁面
這里介紹另外一種方式,通過addView添加進(jìn)去
1.在布局頁面添加布局
<FrameLayout
android:id="@+id/webViewWrap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
2.實(shí)體化webview并添加到布局中
webViewWrap = findViewById(R.id.webViewWrap); //上面的FrameLayout
webView = new WebView(MyApplication.getAppContext()); //使用應(yīng)用上下文,可以防止內(nèi)存泄露,如果傳入activity的上下文,需要將webview remove掉。下面有講解。
webViewWrap.addView(webView);
3.加載頁面
//方式1. 加載一個(gè)網(wǎng)頁:
webView.loadUrl("http://www.google.com/");
//方式2:加載apk包中的html頁面
webView.loadUrl("file:///android_asset/test.html");
//方式3:加載手機(jī)本地的html頁面
webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");
// 方式4: 加載 HTML 頁面的一小段內(nèi)容
WebView.loadData(String data, String mimeType, String encoding)
// 參數(shù)說明:
// 參數(shù)1:一段HTML代碼
// 參數(shù)2:展示內(nèi)容的類型
// 參數(shù)3:字節(jié)碼
到這一步我們就能夠展示一個(gè)WebView頁面了,不過是通過外部瀏覽器打開的。但是還有許多優(yōu)化的地方。
//激活WebView為活躍狀態(tài),能正常執(zhí)行網(wǎng)頁的響應(yīng),可以在activity 的回調(diào)方法中調(diào)用
webView.onResume() ;
//當(dāng)頁面被失去焦點(diǎn)被切換到后臺(tái)不可見狀態(tài),需要執(zhí)行onPause,可以在activity 的回調(diào)方法中調(diào)用
//通過onPause動(dòng)作通知內(nèi)核暫停所有的動(dòng)作,比如DOM的解析、plugin的執(zhí)行、JavaScript執(zhí)行。
webView.onPause();
//當(dāng)應(yīng)用程序(存在webview)被切換到后臺(tái)時(shí),這個(gè)方法不僅僅針對(duì)當(dāng)前的webview而是全局的全應(yīng)用程序的webview
//它會(huì)暫停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
webView.pauseTimers()
//恢復(fù)pauseTimers狀態(tài)
webView.resumeTimers();
//銷毀Webview
//在關(guān)閉了Activity時(shí),如果Webview的音樂或視頻,還在播放。就必須銷毀Webview
//但是注意:webview調(diào)用destory時(shí),webview仍綁定在Activity上
//這是由于自定義webview構(gòu)建時(shí)傳入了該Activity的context對(duì)象
//因此需要先從父容器中移除webview,然后再銷毀webview:
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);// 防止內(nèi)存泄露
}
webView.destroy();
//是否可以后退
Webview.canGoBack()
//后退網(wǎng)頁
Webview.goBack()
//是否可以前進(jìn)
Webview.canGoForward()
//前進(jìn)網(wǎng)頁
Webview.goForward()
//以當(dāng)前的index為起始點(diǎn)前進(jìn)或者后退到歷史記錄中指定的steps
//如果steps為負(fù)數(shù)則為后退,正數(shù)則為前進(jìn)
Webview.goBackOrForward(intsteps)
常見用法:Back鍵控制網(wǎng)頁后退
問題:在不做任何處理前提下 ,瀏覽網(wǎng)頁時(shí)點(diǎn)擊系統(tǒng)的“Back”鍵,整個(gè) Browser 會(huì)調(diào)用 finish()而結(jié)束自身
目標(biāo):點(diǎn)擊返回后,是網(wǎng)頁回退而不是推出瀏覽器
解決方案:在當(dāng)前Activity中處理并消費(fèi)掉該 Back 事件
//清除網(wǎng)頁訪問留下的緩存
//由于內(nèi)核緩存是全局的因此這個(gè)方法不僅僅針對(duì)webview而是針對(duì)整個(gè)應(yīng)用程序.
Webview.clearCache(true);
//清除當(dāng)前webview訪問的歷史記錄
//只會(huì)webview訪問歷史記錄里的所有記錄除了當(dāng)前訪問記錄
Webview.clearHistory();
//這個(gè)api僅僅清除自動(dòng)完成填充的表單數(shù)據(jù),并不會(huì)清除WebView存儲(chǔ)到本地的數(shù)據(jù)
Webview.clearFormData();
WebView 的使用介紹就先到這里了,下一篇將會(huì)講解WebSettings的使用。
結(jié):
Android 調(diào)用 js:
在 Android 中創(chuàng)建通往 javascript 的接口;
在 html 中定義要執(zhí)行的方法;
在 Android 中的具體事件中進(jìn)行調(diào)用。
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word;">contentWebView.loadUrl("javascript:javacalljs()");
</pre>
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word;">
js中調(diào)用Android 方法 :
html中調(diào)用 Android 方法則反來,在 Andorid 中定義要調(diào)用的方法, html 中綁定事件進(jìn)行調(diào)用。 </pre>
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word;"><button onclick="window.wjj.startFunction()">點(diǎn)擊調(diào)用java 代碼</button></pre>
Android 展示 html 頁面
(1)project 視圖下,在 Android 工程中新建目錄 assets;
image
(2)在 assets 目錄下新建 html 頁面 如 show.html;
(3)Android 界面中在 WebView 中展示 show.html;
(4)Android 原生按鈕點(diǎn)擊執(zhí)行 html 中的 js 方法;
image
界面設(shè)計(jì):
image
html 中的 js:
image
效果:
image
js 調(diào)用 android 方法
還是需要一個(gè)接口:
image
Andorid中定義相應(yīng)的執(zhí)行方法(注意帶上相應(yīng)的注解):
image
html 中進(jìn)行調(diào)用:
image
效果(彈的吐司沒截下來):
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。