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 videoview 如何缓存 webview视频缓存_webview H5缓存


android videoview 如何缓存 webview视频缓存_webview H5缓存_02

图一

android videoview 如何缓存 webview视频缓存_webview H5缓存_03

 红米 Android 4.4 真机上看到的  图二

android videoview 如何缓存 webview视频缓存_webview离线机制_04

android videoview 如何缓存 webview视频缓存_webview离线机制_05

Cookies数据库中的cookies表  图三

android videoview 如何缓存 webview视频缓存_缓存_06

图四(图四来自网络)


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
    }


}