WebView 的缓存场景与物理路径
Android APP加载Html页面时,在以下路径会产生缓存文档
旧版本Android(图一):
/data/data/package_name/cache/xxxwebviewCachexxx (xxx在2.x和4.x有所不同,4.0是webviewCache,文件夹存储的是css、js、image等)
/data/data/package_name/database/webview.db
/data/data/package_name/database/webviewCache.db (存储url、filepath、mimetype、expires、httpstatus等,图四)
新版本Android(图二):
/data/data/package_name/app_webview/Cache (该文件夹内存储的是css、js、image等)
/data/data/package_name/app_webview/Cookies(CookieManager维护的cookie数据库,如图三)
/data/data/package_name/app_webview/Local Storage/http_m.kxh1688.com_0.localstorage (里面只有ItemTable(key,value))
/data/data/package_name/app_webview/Web Data (里面是若干个autofill前缀的自动填充表,主要存储个人、公司邮箱、手机、地址都联系信息)
/data/data/package_name/app_database/xxxx (其中database是由
getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
里的名称生成的,存储url、filepath、mimetype、expires、httpstatus等、名称未测试不知道)
图一
红米 Android 4.4 真机上看到的 图二
Cookies数据库中的cookies表 图三
图四(图四来自网络)
WebView 的缓存种类
WebView中存在着两种缓存:网页缓存(存储打开过的页面及资源)、数据缓存(AppCache和DOM Storage(Web Storage))。
一、页面数据缓存
1、缓存模式(5种)
LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
LOAD_DEFAULT: 根据cache-control决定是否从网络上取数据。
LOAD_CACHE_NORMAL: API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
LOAD_CACHE_ELSE_NETWORK 解释:
Use cache if content is there, even if expired (eg, history nav) If it is not in the cache, load from network. Use with setCacheMode(int)
.
如果内容已经存在cache 则使用cache,即使是过去的历史记录。如果cache中不存在,从网络中获取!
例子:
www.taobao.com的cache-control为no-cache,在模式LOAD_DEFAULT下,无论如何都会从网络上取数据,如果没有网络,就会出现错误页面;在LOAD_CACHE_ELSE_NETWORK模式下,无论是否有网络,只要本地有缓存,都使用缓存。本地没有缓存时才从网络上获取。
www.360.com.cn的cache-control为max-age=60,在两种模式下都使用本地缓存数据。
关于cache-control详情,请参阅大神MSNSHOW的
Http头介绍:Expires,Cache-Control,Last-Modified,ETag
及星之羽憶的 HTTP头的Expires与Cache-control
3、清除缓存
clearCache(boolean)
CacheManager.clear高版本中需要调用
隐藏API
。
4、控制大小
无系统API支持。
可选方式:定时统计缓存大小、按时间顺序删除缓存。
从缓存里读取图片:
/**
* 从缓存获取图片
*
* @return
*/
private Bitmap getPictureFromCache(){
Bitmap bitmap=null;
try {
//这里写死,在实际开发项目中要灵活使用
File file=new File(getCacheDir()+"/webviewCache/10d8d5cd");
FileInputStream inStream=new FileInputStream(file);
bitmap=BitmapFactory.decodeStream(inStream);
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
删除此时之前的缓存:
/**
* clear the cache before time numDays,like this:clearCacheFolder(Activity.getCacheDir(),System.currentTimeMillis())
* @param dir
* @param numDays
* @return
*/
private int DeleteFolder(File dir,long numDays){
int deletedFiles = 0;
if (dir!=null && dir.isDirectory()) {
try {
for (File child:dir.listFiles()) {
if (child.isDirectory()) {
deletedFiles += DeleteFolder(child, numDays);
}
if (child.lastModified() < numDays) {
if (child.delete()) {
deletedFiles++;
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
return deletedFiles;
}
退出应用前删除缓存:
File file = CacheManager.getCacheFileBaseDir();
if (file != null && file.exists() && file.isDirectory()) { for (File item : file.listFiles()) { item.delete();
}
file.delete();
}
context.deleteDatabase("webview.db");
context.deleteDatabase("webviewCache.db");
h5离线缓存java端:
WebSettings webseting = m_webview.getSettings();
webseting.setDomStorageEnabled(true);
webseting.setAppCacheMaxSize(1024*1024*8);//设置缓冲大小,我设的是8M 。该方法在高版本已废弃。
String appCacheDir = this.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
webseting.setAppCachePath(appCacheDir);
webseting.setAllowFileAccess(true); //maybe not need
webseting.setAppCacheEnabled(true);
webseting.setCacheMode(WebSettings.LOAD_DEFAULT);
m_webview.setWebChromeClient(m_chromeClient);
private WebChromeClient m_chromeClient = new WebChromeClient(){
//扩充缓存的容量。<span style="font-family: Arial, Helvetica, sans-serif;">该方法在高版本已废弃,扩充由系统自动维护。</span>
@Override
public void onReachedMaxAppCacheSize(long spaceNeeded,
long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
quotaUpdater.updateQuota(spaceNeeded * 2);
}
};
h5离线缓存html端:
在html标签中声明 <html manifest="clock.manifest">
更新缓存机制分为手动更新和自动更新2种,
自动更新:在cache manifest文件本身发生变化时更新缓存 资源文件发生变化不会触发更新
手动更新:使用window.applicationCache
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
window.applicationCache.update();
}
结合
在线状态检测,
HTML5 提供了
两种
检测是否在线的方式:
navigator.online(true/false)
和
online/offline事件。
h5离线需要提供一个cache manifest文件,列出所有需要在离线状态下使用的资源,如下面的clock.manifest文件:
CACHE MANIFEST
#这是注释
images/sound-icon.png
images/background.png
clock.html
clock.css
clock.js
NETWORK:
test.cgi
CACHE:
style/default.css
FALLBACK:
/files/projects /projects
其次要修改http服务器中的配置,使其支持text/cache-manifest,我使用的是apache服务器,是windows版本的,在apache的conf文件夹中找到mime.types文件,打开后在文件的最后加上
“text/cache-manifest mf manifest”,重启服务器即可。这一步很重要,我就是因为服务器端没有配置这个,所以失败了好多次,最后是在附录链接1的回复中找到的线索。
经过以上设置Webview就可以支持HTML5的离线应用了。
附录链接1中说缓冲目录应该是getApplicationContext().getCacheDir().getAbsolutePath();但我经过试验后发现设置那个目录不起作用,可能是Android版本不同吧,我的是Android4.0.3,而他的可能是以前的Android版本吧。
缓冲目录使用getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath()是从附录链接2中找到的线索。
完整代码:
package com.example.webviewtest;
import java.io.File;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebSettings.RenderPriority;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String APP_CACAHE_DIRNAME = "/webcache";
private TextView tv_topbar_title;
private RelativeLayout rl_loading;
private WebView mWebView;
private String url;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//url:http://m.dianhua.cn/detail/31ccb426119d3c9eaa794df686c58636121d38bc?apikey=jFaWGVHdFVhekZYWTBWV1ZHSkZOVlJWY&app=com.yulore.yellowsdk_ios&uid=355136051337627
url = "http://m.dianhua.cn/detail/31ccb426119d3c9eaa794df686c58636121d38bc?apikey=jFaWGVHdFVhekZYWTBWV1ZHSkZOVlJWY&app=com.yulore.yellowsdk_ios&uid=355136051337627";
findView();
}
private void findView() {
tv_topbar_title = (TextView) findViewById(R.id.tv_topbar_title);
rl_loading = (RelativeLayout) findViewById(R.id.rl_loading);
mWebView = (WebView) findViewById(R.id.mWebView);
initWebView();
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onLoadResource(WebView view, String url) {
Log.i(TAG, "onLoadResource url="+url);
super.onLoadResource(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView webview, String url) {
Log.i(TAG, "intercept url="+url);
webview.loadUrl(url);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.e(TAG, "onPageStarted");
rl_loading.setVisibility(View.VISIBLE); // 显示加载界面
}
@Override
public void onPageFinished(WebView view, String url) {
String title = view.getTitle();
Log.e(TAG, "onPageFinished WebView title=" + title);
tv_topbar_title.setText(title);
tv_topbar_title.setVisibility(View.VISIBLE);
rl_loading.setVisibility(View.GONE); // 隐藏加载界面
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
rl_loading.setVisibility(View.GONE); // 隐藏加载界面
Toast.makeText(getApplicationContext(), "",
Toast.LENGTH_LONG).show();
}
});
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
Log.e(TAG, "onJsAlert " + message);
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
Log.e(TAG, "onJsConfirm " + message);
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
Log.e(TAG, "onJsPrompt " + url);
return super.onJsPrompt(view, url, message, defaultValue, result);
}
});
mWebView.loadUrl(url);
}
private void initWebView() {
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setRenderPriority(RenderPriority.HIGH);
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());
}
}
}
二、数据缓存
数据缓存分为两种:AppCache和DOM Storage(Web Storage)。
他们是因为页面开发者的直接行为而产生。所有的缓存数据都由开发者直接完全地掌控。
1. AppCache 缓存
AppCache缓存需设置以下3步骤:
开启缓存:setAppCacheEnabled(true)
设置路径:setAppCachePath (应用程序整个运行周期只调用一次)
设置容量:setAppCacheMaxSize(long appCacheMaxSize)设置缓存最大容量,默认为Max Integer。但不知怎么的,有时设置后无效。
若想定义超出容量时采取的措施,可重载方法 WebChromeClient.onReachedMaxAppCacheSize(long requiredStorage, long quota,WebStorage.QuotaUpdater quotaUpdater),这两个方法在高版本中已废弃。
缓存路径:my_path/ApplicationCache.db
2. DOM Storage 缓存
主要是H5的Session Storage和Local Storage
Session Storage:在会话结束后缓存自动失效,不需要开发者维护。
Local Storage:数据不会过期,开发者不删除app的缓存数据,缓存永远存在。且删除时,除了删除数据,还要杀死当前程序运行的当前进程,然后重启才能干净。
DOM Storage 缓存需设置以下2步骤:
开启缓存:setDomStorageEnabled(true)
设置路径:setDatabasePath 如下说明,从android 4.4开始已废弃,由浏览器自动维护。
//设置数据库缓存路径 API level 19 , Android 4.4 KitKat, in which the browser engine is switched from Android webkit to chromium webkit
缓存路径:
my_path/localstorage/http_h5.m.taobao.com_0.localstorage
my_path/localstorage/Databases.db
webview 设置缓存完整代码:
package utility;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.view.KeyEvent;
import android.webkit.GeolocationPermissions;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/**
* Author: jamky
* Date: 2015/6/4
* Description: 主要应用于 WebView 设置
*/
public class WebViewConfig {
@SuppressLint("JavascriptInterface")
public static void Config(Context context,WebView wv){
WebSettings ws = wv.getSettings();
//android 4.0-, API 14- 写法 /data/data/packagename/files/webcache
//String cacheDirPath = context.getFilesDir().getAbsolutePath()+"/webcache";
//android 4.0+, API 14+ 写法
String cacheDirPath = context.getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
String geolocationDatabasePath = context.getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
//ws.setDefaultTextEncodingName("GBK"); //设置字符编码 默认UTF-8
//support open local files, default value is true;
ws.setAllowFileAccess(true);
ws.setJavaScriptEnabled(true);
//缓存模式
ws.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
// 缓存模式遵从web页面header信息cache-control,如其值为max-age=60,即60秒过期
//s.setCacheMode(WebSettings.LOAD_DEFAULT);
//support geolocation and set the path of geolocation
ws.setGeolocationEnabled(true);
ws.setGeolocationDatabasePath(geolocationDatabasePath);
/* 以下为 页面缓存 设置 */
//设置 Application Caches 缓存目录 该设置比较特殊,网友说整个应用程序的整个生命同期只调用一次,未测试
ws.setAppCachePath(cacheDirPath);
//开启 Application Caches 功能
ws.setAppCacheEnabled(true);
/* 以下为 DOM Storage(Web Storage)数据缓存 设置 */
//开启 database storage API 功能
ws.setDatabaseEnabled(true);
//设置数据库缓存路径 API level 19 , Android 4.4 KitKat, in which the browser engine is switched from Android webkit to chromium webkit
ws.setDatabasePath(cacheDirPath);
// 开启 DOM storage API 功能
ws.setDomStorageEnabled(true);
/* set WebViewClient */
wv.setWebViewClient(new WebViewClient() {
//if load many images, this method will also call many times
@Override
public void onLoadResource(WebView view, String url) {
L.i("onLoadResource url=" + url);
super.onLoadResource(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView webview, String url) {
L.i("intercept url=" + url);
webview.loadUrl(url);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
L.i("onPageStarted");
//rl_loading.setVisibility(View.VISIBLE);
}
@Override
public void onPageFinished(WebView view, String url) {
String title = view.getTitle();
L.i("onPageFinished WebView title=" + title);
// rl_loading.setVisibility(View.GONE); // 隐藏加载界面
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// rl_loading.setVisibility(View.GONE); // 隐藏加载界面
}
//support https
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
super.onReceivedSslError(view, handler, error);
}
//catch web element key event,if return true,webview will not handler the key event
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return super.shouldOverrideKeyEvent(view, event);
}
});
/* set WebChromeClient */
wv.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
@Override
public void onReceivedTitle(WebView view, String title) {
super.onReceivedTitle(view, title);
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
L.e("onJsAlert " + message);
result.confirm();
return true;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
L.e("onJsConfirm " + message);
return super.onJsConfirm(view, url, message, result);
}
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
L.e("onJsPrompt " + url);
return super.onJsPrompt(view, url, message, defaultValue, result);
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
callback.invoke(origin, true, false);
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
return super.onShowFileChooser(webView, filePathCallback, fileChooserParams);
}
});
//调用java方法 不安全
/* wv.addJavascriptInterface(new Object() {
@JavascriptInterface
public void CallJavaMethod(String url, String title) {
//javascript call java this method,it's not safe. like this: window.OpenObj.CallJavaMethod("param","param");
}
}, "OpenObj");*/
//wv.setScrollBarStyle(WebView.SCROLLBARS_INSIDE_OVERLAY); //滚动条放webview里面 不占空间
wv.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY); //滚动条放webview外面
wv.requestFocus();
//调用javascript方法
//wv.loadUrl("javascript:CallJavascriptMethod('param')");
//wv.loadDataWithBaseURL(null,"", "text/html", "utf-8", null); //不使用loadData,因为loadData方法的data参数不能包含特殊字符'#', '%', '\', '?'
//wv.loadUrl("url"); // please call this method outside
}
}