最近一直都在搞webview,搞过Android的人可能会知道,webView本身自带了缓存机制,company的需求是不用webView 的缓存机制,写自己的缓存机制,哇哈哈,有挑战性咯。写这篇博客主要是记录一下我的学习过程。写的不好,勿喷。
首先我们要搞明白webView的缓存机制是什么?
webView中有两种缓存:
一是网页数据缓存(即浏览网页中的资源),而是H5缓存(即appCache)。
webView的缓存目录:
/data/data/package_name/cache/
webView的缓存模式:
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,都使用缓存中的数据。
当然上面是我几番百度和查文档得到的结果。因此需要验证一下对不对。
所以第一步我写了一个demo.
mWebView = (WebView) findViewById(R.id.webview);
mWebView.setWebViewClient(new WebViewClientImpl(this));
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.getSettings().setBuiltInZoomControls(false);
mWebView.getSettings().setDomStorageEnabled(true);
//如果有缓存 就使用缓存数据 如果没有 就从网络中获取
mWebView.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
mWebView.getSettings().setDatabaseEnabled(true);
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.loadUrl("http://www.csdn.net/");
这里setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK)打开了webView的本身缓存。
验证之后的结果是对的,图片文字的什么的都存在于data目录下。第一步已经成功了。可是这与需求还差十万八千里啊。
接下来我们又要解决以下几个问题:
1、webView的缓存在data目录下的,我们都知道手机内部存储是有限的,如何将webView的缓存存储到sd卡上呢?
2、如上述,如果解决了存储在SD卡中,如何写出自己的缓存机制呢?
好吧,思路就是这样,我们来探讨第一个问题。既然要缓存到SD卡上,但是webView是缓存在内部,我们是否可以看到的完整的目录呢?我们去查查文档,果然是有的。
/**
* Returns the absolute path to the application specific cache directory
* on the filesystem. These files will be ones that get deleted first when the
* device runs low on storage.
* There is no guarantee when these files will be deleted.
*
* <strong>Note: you should not <em>rely</em> on the system deleting these
* files for you; you should always have a reasonable maximum, such as 1 MB,
* for the amount of space you consume with cache files, and prune those
* files when exceeding that space.</strong>
*
* @return The path of the directory holding application cache files.
*
* @see #openFileOutput
* @see #getFileStreamPath
* @see #getDir
*/
public abstract File getCacheDir();
这个是webView拿到缓存目录的方法。既然是这样的话,我们把这个方法覆盖,换成我们自己SD卡上的目录不就可以了,说干就干。
@Override
public void onCreate() {
super.onCreate();
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
File externalStorageDir = Environment.getExternalStorageDirectory();
if (externalStorageDir != null){
// {SD_PATH}/Android/data/
extStorageAppBasePath = new File(externalStorageDir.getAbsolutePath() +
File.separator +"Android" + File.separator + "data"+File.separator + getPackageName());
Log.i(TAG,extStorageAppBasePath+"=====");
}
if (extStorageAppBasePath != null){
extStorageAppCachePath = new File(extStorageAppBasePath.getAbsolutePath()+File.separator + "webViewCache");
Log.d("extStorageAppCachePath","extStorageAppCachePath = "+extStorageAppCachePath);
boolean isCachePathAvailable = true;
if (!extStorageAppCachePath.exists()){
isCachePathAvailable = extStorageAppCachePath.mkdirs();
if (!isCachePathAvailable){
extStorageAppCachePath = null;
}
}
}
}
}
@Override
public File getCacheDir() {
if (extStorageAppCachePath != null){
return extStorageAppCachePath;
}else{
return super.getCacheDir();
}
}
这些代码我放在application中,重写了getCacheDir(),把地址覆盖了。我们在webView的那个Activity中拿到即可。
@Override
public File getCacheDir() {
File cacheDir = getApplicationContext().getCacheDir();
Log.d("cacheDir","cacheDir = "+cacheDir.getAbsolutePath());
return cacheDir;
}
测试了一下,表示坑爹了,怎么也缓存不到了SD卡上,搞得整整搞了一上午,发现原来Android4.4搞得鬼。Android4.4 设置了权限。这个大家可以去了解了解。这里不详细说。不过总算是拨的云雾见苍天了,哇哈哈。。。
换了一个测试机,果然是有效的。离目标更近了一步。接下来要出大招了。写自己的缓存机制。可以需求是缓存是有选择的,不能什么都缓存,这个可是有点难度的。首先我们来看下面一段代码。
public class WebViewClientImpl extends WebViewClient {
private Activity activity = null;
private UrlCache urlCache = null;
public WebViewClientImpl(Activity activity) {
this.activity = activity;
this.urlCache = new UrlCache(activity);
this.urlCache.register("http://www.csdn.net/", "cache",
"text/html", "UTF-8", 5 * UrlCache.ONE_MINUTE);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.indexOf("csdn.net") > -1 ) return false;
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
activity.startActivity(intent);
return true;
}
@Override
public void onLoadResource(WebView view, String url) {
super.onLoadResource(view, url);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
Log.d(TAG,"webResourceResponse ==== "+ url);
return this.urlCache.load(url);
}
}
这里我们重写了WebViewClient,里面初始化了一个UrlCache类,这个类封装了Cache的方法。看代码最后一个方法,shouldInterceptRequest中拦截了所有请求,我这里做的粗略了一点,去匹配register()方法中标注的url,可以筛选缓存想要缓存的东西。
public class UrlCache {
public static final long ONE_SECOND = 1000L;
public static final long ONE_MINUTE = 60L * ONE_SECOND;
public static final long ONE_HOUR = 60L * ONE_MINUTE;
public static final long ONE_DAY = 24 * ONE_HOUR;
private static class CacheEntry {
public String url;
public String fileName;
public String mimeType;
public String encoding;
public long maxAgeMillis;
private CacheEntry(String url, String fileName,
String mimeType, String encoding, long maxAgeMillis) {
this.url = url;
this.fileName = fileName;
this.mimeType = mimeType;
this.encoding = encoding;
this.maxAgeMillis = maxAgeMillis;
}
}
protected Map<String, CacheEntry> cacheEntries = new HashMap<String, CacheEntry>();
protected Activity activity = null;
protected File rootDir = null;
public UrlCache(Activity activity) {
this.activity = activity;
// this.rootDir = this.activity.getFilesDir();
this.rootDir = this.activity.getCacheDir();
}
public UrlCache(Activity activity, File rootDir) {
this.activity = activity;
this.rootDir = rootDir;
}
public void register(String url, String cacheFileName,
String mimeType, String encoding,
long maxAgeMillis) {
CacheEntry entry = new CacheEntry(url, cacheFileName, mimeType, encoding, maxAgeMillis);
this.cacheEntries.put(url, entry);
}
public WebResourceResponse load(final String url){
final CacheEntry cacheEntry = this.cacheEntries.get(url);
if(cacheEntry == null) return null;
final File cachedFile = new File(this.rootDir.getPath() + File.separator + cacheEntry.fileName);
Log.d(Constants.LOG_TAG, "cacheFile from cache----: " + cachedFile.toString()+"=="+url);
if(cachedFile.exists()){
long cacheEntryAge = System.currentTimeMillis() - cachedFile.lastModified();
if(cacheEntryAge > cacheEntry.maxAgeMillis){
cachedFile.delete();
//cached file deleted, call load() again.
Log.d(Constants.LOG_TAG, "Deleting from cache: " + url);
return load(url);
}
//cached file exists and is not too old. Return file.
Log.d(Constants.LOG_TAG, "Loading from cache: " + url);
try {
return new WebResourceResponse(
cacheEntry.mimeType, cacheEntry.encoding, new FileInputStream(cachedFile));
} catch (FileNotFoundException e) {
Log.d(Constants.LOG_TAG, "Error loading cached file: " + cachedFile.getPath() + " : "
+ e.getMessage(), e);
}
} else {
try{
downloadAndStore(url, cacheEntry, cachedFile);
//now the file exists in the cache, so we can just call this method again to read it.
return load(url);
} catch(Exception e){
Log.d(Constants.LOG_TAG, "Error reading file over network: " + cachedFile.getPath(), e);
}
}
return null;
}
private void downloadAndStore(String url, CacheEntry cacheEntry, File cachedFile)
throws IOException {
URL urlObj = new URL(url);
URLConnection urlConnection = urlObj.openConnection();
InputStream urlInput = urlConnection.getInputStream();
FileOutputStream fileOutputStream =
this.activity.openFileOutput(cacheEntry.fileName, Context.MODE_PRIVATE);
int data = urlInput.read();
while( data != -1 ){
fileOutputStream.write(data);
data = urlInput.read();
}
urlInput.close();
fileOutputStream.close();
Log.d(Constants.LOG_TAG, "Cache file: " + cacheEntry.fileName + " stored. ");
}
这个就是封装好的Cache,load()这个方法就是把webViewClient上拦截到的url来匹配CacheEntry实体类的url。开始缓存了。
好吧,差不多写完了。
版权声明:本文为博主原创文章,未经博主允许不得转载。