3

【Android Studio】深入探究webView的缓存机制

 2 years ago
source link: http://www.androidchina.net/3168.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
【Android Studio】深入探究webView的缓存机制 – Android开发中文站
你的位置:Android开发中文站 > Android开发 > 开发进阶 > 【Android Studio】深入探究webView的缓存机制

最近一直都在搞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。开始缓存了。
好吧,差不多写完了。

转载请注明:Android开发中文站 » 【Android Studio】深入探究webView的缓存机制


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK