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

来源:互联网 发布:复制信息淘宝打不开 编辑:程序博客网 时间:2024/06/05 10:16

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

0 0