最新消息:欢迎访问Android开发中文站!商务联系微信:loading_in

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

开发进阶 AndroidChina 21155浏览 0评论

最近一直都在搞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 &gt; 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的缓存机制

您必须 登录 才能发表评论!