Android WebView 相关知识

    |     2016年7月22日   |   Android UI界面   |     0 条评论   |    1164

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

关键三步:

  1. 打开 JS:mWebView.getSettings().setJavaScriptEnabled(true);
  2. 实现交互接口。
  3. 把接口实例 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。

转载请注明来源:Android WebView 相关知识
本文链接地址:https://ai.zhousir.top/?p=1640
回复 取消