webview 缓存机制详解

来源:互联网 发布:arp查看mac 编辑:程序博客网 时间:2024/05/17 07:05

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 needwebseting.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.pngimages/background.pngclock.html clock.css clock.js  NETWORK: test.cgiCACHE: style/default.cssFALLBACK: /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    }}



参考:

Android老码农 的 Android WebView缓存分析

脚本之家 Android的webview支持HTML5的离线应用功能详细配置

0 0