【Android Studio】深入探究webView的缓存机制
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.
最近一直都在搞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
);
这里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);
"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。开始缓存了。
好吧,差不多写完了。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK