WebView 用来显示 HTML,对 HTML5 也友好,接近 iOS 的 UIWebView。它不需要仅仅因为「用了 WebView」就申请网络权限:只有真正访问网络才会报 Web page not available;加载 assets 或 HTML 字符串,没有 INTERNET 也不会错。
一、WebView 基础使用
1. 布局与 Settings
xml 中:
<WebView
android:id="@+id/blog_detail_webview"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"/>
Activity 中属性不走 xml,而是 getSettings():
mWebView = (WebView)findViewById(R.id.blog_detail_webview);
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.getSettings().setSupportZoom(false);
mWebView.getSettings().setBuiltInZoomControls(false);
mWebView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);
mWebView.getSettings().setDefaultFontSize(18);
-
setJavaScriptEnabled(false):不支持 JS。要 Java 和 JS 交互,或页面本身依赖 JS,改成 true。
-
setSupportZoom(false):是否支持缩放,默认 true。
-
setBuiltInZoomControls(false):是否显示缩放控件,默认 false。
-
setDefaultFontSize(18):默认字体,默认 16,有效区间 1–72。
setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN) 不常用。普通网页常出现横向滚动条,阅读很别扭。LayoutAlgorithm 控制 HTML 布局,三种:
| 取值 |
效果 |
NORMAL |
正常显示,没有渲染变化 |
SINGLE_COLUMN |
把所有内容放到与 WebView 等宽的一列 |
NARROW_COLUMNS |
可能的话,列宽不超过屏幕宽度 |
2. 加载内容
(1)加载 assets 里的本地网页,形如:
mWebView.loadUrl("file:///android_asset/html/test1.html");
(2)加载远程网页:
mWebView.loadUrl("http://www.google.com");
(3)片段而不是完整网页时,用 loadData 或更常用的 loadDataWithBaseURL。完整站点直接用浏览器即可,不必做成应用。
void loadDataWithBaseURL (String baseUrl, String data, String mimeType, String encoding, String historyUrl)
loadDataWithBaseURL()比loadData()多两个参数,可以指定HTML代码片段中相关资源的相对根路径,也可以指定历史Url,其余三个参数相同。
baseUrl 指定 data 里超链接、图片相对路径的基准。很多站点用相对地址,没有 baseUrl 就加载不到资源。
例子(相对路径图片,必须带上站点根):
String body ="示例:这里有个img标签,地址是相对路径<img src='/uploads/allimg/130923/1FP02V7-0.png' />";
mWebView.loadDataWithBaseURL("http://www.jcodecraeer.com", body, "text/html", "utf-8",null);
baseUrl 若不指定为 http://www.jcodecraeer.com,这张图就显示不出来。
也可以先把 assets 文件读成字符串再 loadData:
String data = "";
try {
// 读取assets目录下的文件需要用到AssetManager对象的Open方法打开文件
InputStream is = getAssets().open("html/test2.html");
// loadData()方法需要的是一个字符串数据所以我们需要把文件转成字符串
ByteArrayBuffer baf = new ByteArrayBuffer(500);
int count = 0;
while ((count = is.read()) != -1) {
baf.append(count);
}
data = EncodingUtils.getString(baf.toByteArray(), "utf-8");
} catch (IOException e) {
e.printStackTrace();
}
// 下面两种方法都可以加载成功
mWebView.loadData(data, "text/html", "utf-8");
// wv.loadDataWithBaseURL("", data, "text/html", "utf-8", "");
这和 loadUrl("file:///android_asset/html/test1.html") 效果接近,但 loadData 没有基准 URL,文件里的资源或链接会失效。
loadData() 的 HTML 不能包含 #、%、\、? 四种特殊字符。平时可能碰巧不出问题,出问题时再替换。
-
-
#:会让 goBack 失效,但 canGoBack 仍为 true,于是返回按钮亮着却回不去。
3. 内容处理与滚动条
WebView 上 padding 几乎不起作用,内容仍贴边,反而会挪滚动条。这是控件问题。外包一层 layout 只能改善一点。正确做法是在加载的 CSS 里加 padding,不必为这点空白改 xml。
重写 shouldOverrideUrlLoading 时可以限定:只有 URL 包含 eoe.cn 才在 WebView 打开,否则走系统浏览器:
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
LogUtil.i(this, "url=" + url);
if ( url.contains("eoe.cn") == true){
view.loadUrl(url);
return true;
}else{
Intent in = new Intent (Intent.ACTION_VIEW , Uri.parse(url));
startActivity(in);
return true;
}
}
android:scrollbarStyle 控制滚动条位置:SCROLLBARS_INSIDE_OVERLAY 像 CSS padding,条子在 page 里;OUTSIDE_OVERLAY 在外侧。
mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
二、WebView 和 JavaScript 交互
分两种:JS 调 Android;Android 调 JS。
第一种:JS 调用 Android
关键三步:
-
打开 JS:
mWebView.getSettings().setJavaScriptEnabled(true);
-
-
把接口实例
addJavascriptInterface 到 WebView(关键)。
interface MyJavaScriptInterface {
void nativeMethod();
}
MyJavaScriptInterface myJavaScriptInterface = new MyJavaScriptInterface() {
@Override
public void nativeMethod() {
Toast.makeText(WebViewActivity.this,"对话框",Toast.LENGTH_SHORT).show();
}
};
/**
* WebView.addJavascriptInterface(Object,String)方法
* Object 交互接口对象
* String 交互接口名称
*/
mWebView.addJavascriptInterface(myJavaScriptInterface, "MyJavaScriptInterface");
HTML 里通过 window.接口名.方法名 调用:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>表单</title>
<script>
//window:浏览器窗口对象
//MyJavaScriptInterface:js与原生android应用交互的接口名
//showToast: js与原生android应用交互的接口的方法名
function toNativeFunction()
{
window.MyJavaScriptInterface.nativeMethod();
}
</script>
</head>
<body>
<button onclick="toNativeFunction()">执行跳转</button>
</body>
</html>
第二种:Android 调用 JS
mWebView.loadUrl("javascript:testByNative()");
页面实现 testByNative:
<script>
function testByNative(){
window.location.href = "https://ai.zhousir.top"; //跳转到指定url
}
</script>
三、WebView 页面加载效果
WebViewClient 管开始/结束和 URL 拦截;WebChromeClient.onProgressChanged 管进度条。
public void setWebVieClient(){
mWebView.setWebViewClient(new WebViewClient(){
/**
* 请求的url嵌入webview显示
*/
@Override
public boolean shouldOverrideUrlLoading(WebView webview, String url) {
webview.loadUrl(url);
return true;
}
/**
* 加载web页面调用
* @param view
* @param url
* @param favicon
*/
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mLoadTxt.setText("正在加载中.....");
}
/**
* web页面加载完成调用
* @param view
* @param url
*/
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mLoadTxt.setText("加载完成");
}
});
mWebView.setWebChromeClient(new WebChromeClient(){
/**
* web页面加载进度转变调用
* @param view
* @param newProgress
*/
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
Logs.e("onProgressChanged :"+newProgress);
mLoadingSeekBar.setProgress(newProgress);
}
});
}
物理返回键优先在 WebView 历史里回退:
@Override
/**
*嵌入网页回退操作
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
mWebView.goBack();// 返回前一个页面
return true;
}
return super.onKeyDown(keyCode, event);
}
四、WebView 缓存相关
加载 HTML 时,会在 data/应用 package 下生成 database 与 cache:请求过的 URL 记在 webviewCache.db,内容在 webviewCache 文件夹。WebView 有两类缓存:网页数据缓存(打开过的页面及资源)、H5 缓存(AppCache)。
网页缓存构成
-
/data/data/package_name/cache/
-
/data/data/package_name/database/webview.db
-
/data/data/package_name/database/webviewCache.db
浏览过的网页 URL 以及 css、图片、js 等会进这些表。
缓存模式有 5 种:
| 模式 |
含义 |
LOAD_CACHE_ONLY |
不使用网络,只读本地缓存 |
LOAD_DEFAULT |
按 cache-control 决定是否联网 |
LOAD_CACHE_NORMAL |
API 17 已废弃,API 11 起同 LOAD_DEFAULT |
LOAD_NO_CACHE |
不用缓存,只从网络取 |
LOAD_CACHE_ELSE_NETWORK |
本地有就用,不论是否过期或 no-cache |
例如 www.taobao.com 的 cache-control 为 no-cache:LOAD_DEFAULT 下总会联网,没网就错误页;LOAD_CACHE_ELSE_NETWORK 下只要本地有缓存就用。www.360.com.cn 的 cache-control 为 max-age=60,两种模式都会用本地缓存。
建议:有网用 LOAD_DEFAULT,无网用 LOAD_CACHE_ELSE_NETWORK。
mWebView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); // 设置 缓存模式
// 开启 DOM storage API 功能
mWebView.getSettings().setDomStorageEnabled(true);
// 开启 database storage API 功能
mWebView.getSettings().setDatabaseEnabled(true);
String cacheDirPath = getFilesDir().getAbsolutePath()+ APP_CACAHE_DIRNAME;
// String cacheDirPath = getCacheDir().getAbsolutePath()+Constant.APP_DB_DIRNAME;
Log.i(TAG, "cacheDirPath=" + cacheDirPath);
// 设置数据库缓存路径
mWebView.getSettings().setDatabasePath(cacheDirPath);
// 设置 Application Caches 缓存目录
mWebView.getSettings().setAppCachePath(cacheDirPath);
// 开启 Application Caches 功能
mWebView.getSettings().setAppCacheEnabled(true);
清除 WebView 缓存
public void clearWebViewCache() {
// 清理Webview缓存数据库
try {
deleteDatabase("webview.db");
deleteDatabase("webviewCache.db");
} catch (Exception e) {
e.printStackTrace();
}
// WebView 缓存文件
File appCacheDir = new File(getFilesDir().getAbsolutePath()
+ APP_CACAHE_DIRNAME);
Log.e(TAG, "appCacheDir path=" + appCacheDir.getAbsolutePath());
File webviewCacheDir = new File(getCacheDir().getAbsolutePath()
+ "/webviewCache");
Log.e(TAG, "webviewCacheDir path=" + webviewCacheDir.getAbsolutePath());
// 删除webview 缓存目录
if (webviewCacheDir.exists()) {
deleteFile(webviewCacheDir);
}
// 删除webview 缓存 缓存目录
if (appCacheDir.exists()) {
deleteFile(appCacheDir);
}
}
/**
* 递归删除 文件/文件夹
*
* @param file
*/
public void deleteFile(File file) {
Log.i(TAG, "delete file path=" + file.getAbsolutePath());
if (file.exists()) {
if (file.isFile()) {
file.delete();
} else if (file.isDirectory()) {
File files[] = file.listFiles();
for (int i = 0; i < files.length; i++) {
deleteFile(files[i]);
}
}
file.delete();
} else {
Log.e(TAG, "delete file no exists " + file.getAbsolutePath());
}
}
五、通过 WebView 自带的缓存功能来实现离线阅读
展开步骤见:通过WebView自带的缓存功能来实现离线阅读
一句话总结:本地 HTML 不用网络权限;远程才要。片段用 loadDataWithBaseURL 补 baseUrl,JS 桥走 addJavascriptInterface,缓存按有网/无网切换 LOAD_DEFAULT 与 LOAD_CACHE_ELSE_NETWORK。