【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。开始缓存了。

好吧,差不多写完了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-15 00:34:40

【Android Studio】深入探究webView的缓存机制的相关文章

手把手教你构建 Android WebView 的缓存机制 &amp; 资源预加载方案

前言 由于H5具备 开发周期短.灵活性好 的特点,所以现在 Android App大多嵌入了 Android Webview 组件进行 Hybrid 开发 但我知道你一定在烦恼 Android Webview 的性能问题,特别突出的是:加载速度慢 & 消耗流量 今天,我将针对 Android Webview 的性能问题,提出一些有效解决方案. 目录 1. Android WebView 存在什么性能问题? Android WebView 里 H5 页面加载速度慢 耗费流量 下面会详细介绍. 1.

王立平--WebView的缓存机制

WebView的缓存可以分为页面缓存和数据缓存. 1.   页面缓存是指加载一个网页时的html.JS.CSS等页面或者资源数据.这些缓存资源是由于浏览器的行为而产生,开发者只能通过配置HTTP响应头影响浏览器的行为才能间接地影响到这些缓存数据. 他们的索引存放在/data/data/package_name/databases下.他们的文件存放在/data/data/package_name/cache/xxxwebviewcachexxx下.文件夹的名字在2.x和4.x上有所不同,但都文件夹

[转]Android性能优化之ListView的缓存机制

要想优化ListView首先要了解它的工作原理,列表的显示需要三个元素:ListView.Adapter.显示的数据: 这里的Adapter就是用到了适配器模式,不管传入的是什么View在ListView中都能显示出来. 下面简单说下上图的原理: 1.如果你有几千几万甚至更多的选项(item)时,其中只有可见的项目(满屏显示的Item数目)存在内存(说的优化就是说在内存中的优化!)中,其他的在Recycler中 2.ListView先请求一个type1视图(getView)然后请求其他可见的项目

如何清除Android Studio下载的远程aar缓存

在AS的包内找到下图的文件目录然后删除对应包名的文件目录即可

H5 缓存机制解析

线上项目需要更新一个有问题的资源(可能是图片,js,css,json数据等),这个资源已经发布了很长一段时间,为什么页面在浏览器里打开还是没有看到更新? 有些web开发经验的同学应该马上会想到,可能是资源发布出了岔子导致没有实际发布成功,更大的可能是老的资源被缓存了.说到web缓存,首先我们要弄清它是什么.Web缓存可以理解为Web资源在Web服务器和客户端(浏览器)的副本,其作用体现在减少网络带宽消耗.降低服务器压力和减少网络延迟,加快页面打开速度等方面(笔者在香港求学期间看到港台地区将cac

Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用

在上一篇文章Android IPC机制(二)用Messenger进行进程间通信中我们介绍了使用Messenger来进行进程间通信的方法,但是我们能发现Messenger是以串行的方式来处理客户端发来的信息,如果有大量的消息发到服务端,服务端仍然一个一个的处理再响应客户端显然是不合适的.另外,Messenger用来进程间进行数据传递但是却不能满足跨进程的方法调用,接下来我们来使用AIDL来实现跨进程方法调用,此前我们都是用Eclipse来实现的,这次我们看看在Android Studio中使用AI

Android客户端中Bitmap的下载过程和缓存机制

加载流程: if(内存命中){ 从内存中读取 }else{ create AsyncTasks,task中的多个Runnable是通过堆栈先进后出的方式来调度,而非队列式的先进先出,目的是最先加载用户最近划到或打开的图片. } AsyncTask: //do in background——该后台进程在用户scroll列表的时候会暂停,从而减小了列表划动时cpu的overhead,此方法也被ImageLoader和facebook的官方app所使用. if(磁盘缓存命中){ 从缓存中读取 }els

Android WebView的缓存方式分析

WebView的缓存可以分为(1)页面缓存和(2)数据缓存. 页面缓存是指当WebView加载一个网页时的html.JS.CSS等页面或者资源数据.这些缓存资源是由于浏览器的行为而产生,开发者只能通过配置HTTP响应头来影响浏览器的行为才能间接地影响到这些缓存数据. 他们的索引存放在/data/data/package_name/databases下.他们的文件存放在/data/data/package_name/cache/xxxwebviewcachexxx下.文件夹的名字在2.x和4.x上

Android笔记(二十五) ListView的缓存机制与BaseAdapter

之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容放入到缓冲池中,再从缓存池中拿出新的内容显示出来,这就是ListView的缓存机制,这一机制可以极大的节省系统资源. BaseAdapter BaseAdapter通常用于被扩展,扩展BaseAdapter可以对各项列表进行最大限度的定制. 我们可以用自己的类去继承BaseAdapter,然后实现g