参考:
WebView全面解析
安卓中原生与H5(webview)之间交互时cookie的同步
详细的Webview使用攻略
WebView与JavaScript的交互总结
webview与HTTPS
速度提升框架:VasSonic
Android展示网页内容有两种方式,一种是通过手机系统浏览器,二是在布局中内嵌
WebView
WebView是android中一个非常重要的控件,它的作用是用来展示一个web页面。它使用的内核是webkit
引擎,4.4版本之后,直接使用Chrome作为内置网页浏览器
0.加载网页和内容
//方式一:加载一个网页
webView.loadUrl("http://www.baidu.com");
//方式二:加载应用资源文件内的网页
webView.loadUrl("file:///android_asset/test.html");
//方式三:加载一段代码
webView.loadData(String data,String mimeType, String encoding);
//方式四:加载网页或内容(推荐)
webView.loadDataWithBaseURL("http://www.baidu.com", body, "text/html", "utf-8",null);
1.WebSetting
方法名 | 方法描述 |
---|---|
setJavaScriptEnabled(true) |
是否支持JS 加载,默认为false
|
setJavaScriptCanOpenWindowsAutomatically(true) |
支持通过JS 打开新窗口 |
setPluginsEnabled(true) |
是否支持插件 |
setUseWideViewPort(true) |
将图片调整到适合webview 的大小 |
setLoadWithOverviewMode |
缩放至屏幕的大小 |
setSupportZoom(true) |
支持缩放,默认为true 。是setBuiltInZoomControls(true) 的前提 |
setBuiltInZoomControls(true) |
设置内置的缩放控件。若为false ,则该WebView 不可缩放 |
setDisplayZoomControls(false) |
隐藏原生的缩放控件 |
setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK) |
LOAD_CACHE_ONLY : 不使用网络,只读取本地缓存数据LOAD_DEFAULT : (默认)根据cache-control决定是否从网络上取数据。LOAD_NO_CACHE : 不使用缓存,只从网络获取数据.LOAD_CACHE_ELSE_NETWORK ,只要本地有,无论是否过期,或者no-cache ,都使用缓存中的数据。 |
setAllowFileAccess(true) |
设置可以访问文件 |
setLoadsImagesAutomatically(true) |
支持自动加载图片 |
setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN) |
支持内容重新布局 |
setDefaultTextEncodingName("utf-8") |
设置编码格式 |
setDomStorageEnabled(true) |
开启 DOM storage API 功能,有的网页如淘宝样式加载不出来就需要配置此项DOM storage
|
setDatabaseEnabled(true) |
开启 database storage API 功能 |
setAppCacheEnabled(true) |
开启 Application Caches 功能 |
setAppCachePath(cacheDirPath) |
设置 Application Caches 缓存目录 |
setAppCacheMaxSize(1024*8) |
缓存的最大空间 |
setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW) |
5.1以上默认禁止了https和http混用,所以大于5.1的需要开启 |
setRenderPriority(WebSettings.RenderPriority.HIGH) |
提高渲染的优先级 |
setStandardFontFamily("") |
设置 WebView 的字体,默认字体为 "sans-serif"
|
webSettings.setDefaultFontSize(20) |
设置WebView 字体的大小,默认大小为 16 |
webSettings.setMinimumFontSize(12) |
设置 WebView 支持的最小字体大小,默认为 8 |
setGeolocationEnabled(true) |
允许网页执行定位操作 |
2.WebViewClient
方法名 | 方法描述 |
---|---|
onPageStarted(WebView view, String url, Bitmap favicon) |
页面开始加载 |
onPageFinished(WebView view, String url) |
页面加载结束 |
boolean shouldOverrideUrlLoading(WebView view, String url) 或(新5.0以上)boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
|
拦截URL请求,重定向 |
onReceivedError() |
加载页面的服务器出现错误(比如404)时回调。 |
onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) |
https 请求异常 |
shouldInterceptRequest(WebView view, String url) |
每次发生资源加载 |
onLoadResource(WebView view, String url) |
页面加载资源时调用 |
shouldOverrideKeyEvent() |
重写此方法才能处理浏览器中的按键事件 |
doUpdateVisitedHistory() |
更新历史记录 |
onFormResubmission() |
应用程序重新请求网页数据 |
onReceivedHttpAuthRequest() |
获取返回信息授权请求 |
onScaleChanged() |
WebView 发生缩放改变时调用。 |
onUnhandledKeyEvent() |
Key 事件未被加载时调用 |
-
shouldOverrideUrlLoading()
:拦截URL请求,重定向(有2个方法,一个是兼容5.0以下,一个是兼容5.0以上,保险起见两个都重写)。- 无论返回
true
还是false
,只要为WebView
设置了WebViewClient
,系统就不会再将url
交给第三方的浏览器去处理了。 - 如果返回
false
,代表将url
交给当前WebView
加载,也就是正常的加载状态;shouldOverrideUrlLoading()
返回true
,代表开发者已经对url
进行了处理,WebView
就不会再对这个url
进行加载了。 - 另外,使用
post
的方式加载页面,此方法不会被调用。
- 无论返回
webView.setWebViewClient(new WebViewClient(){
//重定向URL请求,返回true表示拦截此url,返回false表示不拦截此url。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//作用1:重定向url
if(url.startsWith("weixin://")){
url = url.replace("weixin://","http://");
webView.loadUrl(url);
}
//作用2:在本页面的WebView打开,防止外部浏览器打开此链接
view.loadUrl(url);
return true;
}
});
- 情况一:
loadUrl()
无重定向时
onPageStarted->onPageFinished
- 情况二:
loadUrl()
网页A重定向到B时
onPageStarted->onPageFinished->shouldOverrideUrlLoading->onPageStarted->onPageFinished
也有可能重定向多次
shouldOverrideUrlLoading->shouldOverrideUrlLoading->onPageStarted->onPageFinished
- 情况三:在已加载的页面中点击链接,加载页面A(无重定向)
shouldOverrideUrlLoading->onPageStarted->onPageFinished
- 情况四:在已加载的页面中点击链接,加载页面A(页面A重定向至页面B)
shouldOverrideUrlLoading->shouldOverrideUrlLoading->onPageStarted->onPageFinished
- 情况五:执行
goBack/goForward/reload
方法
onPageStarted->onPageFinished
- 情况六:发生资源加载
shouldInterceptRequest->onLoadResource
-
onReceivedSslError()
:webView默认是不处理https请求的,页面显示空白,需要进行如下设置:
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); //表示等待证书响应
// handler.cancel(); //表示挂起连接,为默认方式
// handler.handleMessage(null); //可做其他处理
}
});
- 上面异常的处理办法:
https://www.jianshu.com/p/119823e5cfb5
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!url.startsWith("http")) return true;
return super.shouldOverrideUrlLoading(view, url);
}
3.WebChromeClient
辅助 WebView 处理 Javascript 的对话框,网站图标,网站标题等等
方法名 | 方法描述 |
---|---|
onProgressChanged() |
获得网页的加载进度并显示。 |
onReceivedTitle() |
获得网页的标题时回调。 |
onReceivedIcon() |
获得网页的图标时回调。 |
onCreateWindow() |
打开新窗口时回调。 |
onCloseWindow() |
关闭窗口时回调。 |
onJsAlert() |
网页弹出提示框时触发此方法 |
onJsConfirm() |
支持javascript的确认框 |
onJsPrompt() |
支持javascript输入框,点击确认返回输入框中的值,点击取消返回 null。 |
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Toast.makeText(MainActivity.this,"Im alert",Toast.LENGTH_SHORT).show();
//部分机型只会弹出一次提示框,调用此方法即可解决此问题。
result.cancel();
//返回true表示不弹出系统的提示框,返回false表示弹出
return true;
}
webview.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("JsConfirm")
.setMessage(message)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
})
.setCancelable(false)
.show();
// 返回布尔值:判断点击时确认还是取消
// true表示点击了确认;false表示点击了取消;
return true;
}
webview.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
final EditText et = new EditText(MainActivity.this);
et.setText(defaultValue);
new AlertDialog.Builder(MainActivity.this)
.setTitle(message)
.setView(et)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm(et.getText().toString());
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
})
.setCancelable(false)
.show();
return true;
}
4.Cookie
安卓中原生与H5(webview)之间交互时cookie的同步
在执行webview的loadurl之前,先执行cookie同步
- 设置
cookie
public static void synchronousWebCookies(Context context,String url,String cookies){
if ( !TextUtils.isEmpty(url) )
if (!TextUtils.isEmpty(cookies) ) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP){
CookieSyncManager.createInstance( context);
}
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie( true );
cookieManager.removeSessionCookie();// 移除
cookieManager.removeAllCookie();
StringBuilder sbCookie = new StringBuilder();//创建一个拼接cookie的容器,为什么这么拼接,大家查阅一下http头Cookie的结构
sbCookie.append(cookies);//拼接sessionId
// sbCookie.append(String.format(";domain=%s", ""));
// sbCookie.append(String.format(";path=%s", ""));
String cookieValue = sbCookie.toString();
cookieManager.setCookie(url, cookieValue);//为url设置cookie
CookieSyncManager.getInstance().sync();//同步cookie
String newCookie = cookieManager.getCookie(url);
LogManager.i("同步后cookie", newCookie);
}
}
- 获取
cookie
public static String syncCookie(String url) {
CookieManager cookieManager = CookieManager.getInstance();
return cookieManager.getCookie(url);
}
- 清除
cookie
CookieManager.getInstance().removeSessionCookies();// 移除所有过期 cookie
CookieManager.getInstance().removeAllCookies(); // 移除所有的 cookie
//设置清除cookie后的回调方法
private void removeCookie(Context context) {
CookieManager.getInstance().removeAllCookies(new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean value) {
// 清除结果
}
});
}
5. JS 交互
WebView与JavaScript的交互总结
最全面总结 Android WebView与 JS 的交互方式
Android调用JS | 方法描述 |
---|---|
loadUrl() |
loadUrl("javascript:callJS()"); 只会执行一次 |
evaluateJavascript() |
通过WebView 的evaluateJavascript() ,可多次调用,需要Android 4.4以上 |
首先开启js
//允许WebView使用JS
settings.setJavaScriptEnabled(true);
//支持通过JS打开新窗口(允许JS弹窗)
settings.setJavaScriptCanOpenWindowsAutomatically(true);
- a.
Android
通过loadUrl
调用js
btnCallJs.setOnClickListener(view1 -> webView.loadUrl("javascript:callJS()"));
只有第一次会调用,且调用需要在onPageFinished
方法之后
- b
Android
通过evaluateJavascript()
调用js
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mBinding.btnCallJs.setOnClickListener(view1 ->
mBinding.webView.evaluateJavascript("javascript:callJS()", s -> {
//s是JS方法的返回值
L.e(TAG,s);
}));
}
这种方式比第一种方式效率高(执行时不会刷新页面),同时可以获取返回值。缺点是只兼容到Android4.4(19)
版本以后,如果重写了WebChromeClient
的onJsAlert
,返回值为true的情况下,JsResult
需要调用cancel
,如下:
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Toast.makeText(MainActivity.this,"Im alert",Toast.LENGTH_SHORT).show();
//部分机型只会弹出一次提示框,调用此方法即可解决此问题。
result.cancel();
//返回true表示不弹出系统的提示框,返回false表示弹出
return true;
}
JS调用Android | 方法描述 |
---|---|
addJavascriptInterface() |
对象映射webView.addJavascriptInterface(new AndroidJs(),"android");
|
shouldOverrideUrlLoading () |
WebViewClient 的shouldOverrideUrlLoading () 方法回调拦截url
|
onJsAlert() |
通过 WebChromeClient 的onJsAlert() 拦截JS 对话框alert()
|
onJsConfirm() |
通过 WebChromeClient 的onJsConfirm() 拦截JS 对话框confirm()
|
onJsPrompt() |
通过 WebChromeClient 的onJsAlert() 拦截JS 对话框prompt()
|
- a.
addJavascriptInterface()
的使用
创建一个测试的网页test.html
到assets
文件夹中
<head>
</head>
<body>
<h1 onClick='callAndroid()'>这是JS调用原生1</h1>
<Script>
function callAndroid(){
android.hello("js调用了");
}
</Script>
</body>
创建一个对象,关联js调用方法
public class AndroidJs{
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void hello(String msg) {
ToastUtil.showShort(msg);
}
}
Activity
使用时:加载网页test.html
,让webView把对象,和网页函数关联起来
webView.loadUrl("file:///android_asset/test.html");
webView.addJavascriptInterface(new AndroidJs(),"android");
- 注意:正常情况下这里的
test.html
应该是后台的网页地址,new AndroidJs()
就是我们桥接的js
和Android
的对象,后面的的"android"
就是我们在网页test.html中使用的android.hello("js调用了");
- 它的调用顺序应该是,我们点击了网页
test.html
中的h1
标签,触发onClick
方法,执行callAndroid()
函数,在callAndroid()
函数中又会调用android.hello("js调用了");
,它就会通过android
去查询安卓对应的映射对象AndroidJs
,找到后再调用里面AndroidJs
里面的hello
方法,并把参数传递进去。 - 这种方法优点是使用简单,缺点是存在严重的漏洞问题,请看文章:你不知道的 Android WebView 使用漏洞
- b.
shouldOverrideUrlLoading ()
的使用
- 创建一个
javascript.html
到src/main/assets
文件夹里
<!DOCTYPE html>
<html>
<head>
<script>
function callAndroid(){
/*约定的url协议为:js://webview?arg1=111&arg2=222*/
document.location = "js://webview?arg1=111&arg2=222";
}
</script>
</head>
<!-- 点击按钮则调用callAndroid()方法 -->
<body>
<button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
</body>
</html>
- 页面调用
String url = "file:///android_asset/javascript.html";
webView.loadUrl(url);
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
//"js://webview?arg1=111&arg2=222"
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();//空间:相当于冒号之前的部分
String authority = uri.getAuthority();//授权:双斜杠之后,?之前的部分
if (scheme.equals("js")){
if (authority.equals("webview")){
Set<String> keys = uri.getQueryParameterNames();//参数key:arg1,arg2
for (String key : keys) {
L.e("key=="+key);
L.e("value=="+uri.getQueryParameter(key));
}
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
- 与后台定义好对应的空间和授权,拦截到对应的空间后,根据不同的参数来调用不同的
Android
原生方法,就是这样。
- c.通过 拦截
WebChromeClient
的onJsAlert(),onJsConfirm(),onJsPrompt()
方法,来实现调用Android
的原生方法。
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
L.e("url=="+url);
L.e("message=="+message);
return super.onJsAlert(view, url, message, result);
}
test.html
<!DOCTYPE html>
<html>
<body>
<h1 onClick='cleanData()'>清空数据</h1>
<script>
function cleanData(){
var a=confirm("确定要清空数据吗?");
if(a==true){
document.write("恭喜你清空了数据!");
}else{
document.write("取消清空操作");
}
}
</script>
</body>
</html>
webView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
L.e("url=="+url);
L.e("message=="+message);
new AlertDialog.Builder(_mActivity).
setTitle("警告").setMessage(message).setPositiveButton("确定",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
result.confirm();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
result.cancel();
}
})
.create().show();
return true;
}
});
- 我们可以拦截弹出框的提示语:
"确定要清空数据吗?"
用AlertDialog
,让后根据AlertDialog
操作执行不同的JsResult
,如确定result.confirm();
取消result.cancel();
test.html
<!DOCTYPE html>
<html>
<body>
<h1 onClick='takePrompt()'>体能测试</h1>
<script>
function takePrompt(){
var result = prompt("请输入你的身高?","170");
alert(result);
}
</script>
</body>
</html>
mBinding.webView.setWebChromeClient(new WebChromeClient(){
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
L.e(TAG,message + " , " + defaultValue);
if(message.contains("身高")){
final EditText et = new EditText(_mActivity);
new AlertDialog.Builder(_mActivity)
.setMessage(message)
.setView(et)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
String s = et.getText().toString();
result.confirm(s);
}
})
.create().show();
}
return true; //返回true表示不弹出系统提示框
}
});
6. WebView相关方法
方法名 | 方法描述 |
---|---|
canGoBack() |
是否可以返回 |
goBack() |
返回 |
canGoForward() |
是否可以前进 |
goForward() |
前进网页 |
goBackOrForward(intsteps) |
以当前的index 为起始点前进或者后退到历史记录中指定的steps ,如果steps 为负数则为后退,正数则为前进 |
onResume() |
激活WebView 为活跃状态,能正常执行网页的响应 |
onPause() |
通过onPause 动作通知内核暂停所有的动作,比如DOM 的解析、plugin 的执行、JavaScript 执行 |
pauseTimers() |
当应用程序(存在webview )被切换到后台时,这个方法不仅仅针对当前的webview 而是全局的全应用程序的webview ,它会暂停所有webview 的layout,parsing,javascripttimer 。降低CPU 功耗。 |
resumeTimers() |
恢复pauseTimers 状态 |
destroy() |
销毁Webview
|
clearCache(true) |
清除网页访问留下的缓存,由于内核缓存是全局的因此这个方法不仅仅针对webview 而是针对整个应用程序. |
clearHistory() |
清除当前webview 访问的历史记录,除了当前访问记录 |
clearFormData() |
清除自动完成填充的表单数据 |
stopLoading () |
停止当前加载 |
zoomBy(float zoomFactor) |
在此Web 视图中执行缩放操作,zoomFactor 在0.01 到100.0 之间 |
zoomIn() |
在此Web 视图中执行放大操作 |
zoomOut() |
在此Web 视图中执行缩小 |
在Fragment
中返回网页的上一页,而不是直接关闭
@Override
public boolean onBackPressedSupport() {
if (webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onBackPressedSupport();
}
在Activity
中返回网页的上一页,而不是直接关闭
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && webView.canGoBack()) {
webView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
7. Cache缓存
- 当加载 html 页面时,WebView会在/data/data/包名目录下生成 database 与 cache 两个文件夹
- 请求的 URL记录保存在 WebViewCache.db,而 URL的内容是保存在 WebViewCache 文件夹下
- 优先使用缓存
WebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
- 不使用网络,只读取本地缓存数据
LOAD_CACHE_ONLY
- (默认)根据
cache-control
决定是否从网络上取数据LOAD_DEFAULT
- 不使用网络,只读取本地缓存数据
LOAD_NO_CACHE
- 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据
LOAD_CACHE_ELSE_NETWORK
离线加载: 每个 Application
只调用一次 WebSettings.setAppCachePath()
,WebSettings.setAppCacheMaxSize()
if (NetStatusUtil.isConnected(getApplicationContext())) {
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);//根据cache-control决定是否从网络上取数据。
} else {
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);//没网,则从本地获取,即离线加载
}
webSettings.setDomStorageEnabled(true); // 开启 DOM storage API 功能
webSettings.setDatabaseEnabled(true); //开启 database storage API 功能
webSettings.setAppCacheEnabled(true);//开启 Application Caches 功能
String cacheDirPath = getFilesDir().getAbsolutePath() + APP_CACAHE_DIRNAME;
webSettings.setAppCachePath(cacheDirPath); //设置 Application Caches 缓存目录