HybridCache:一种简单的native与webview共享缓存的设计

HybridCache简而言之实际上是一套native和webview共享缓存的解决方案。不过在了解HybridCache的实现细节以及可以解决的问题以前,先大概了解一下web开发中涉及到的缓存机制css

Web缓存机制

实际上,web开发当中已经具有至关完善的缓存机制,而且Android系统的WebView对这些已有的缓存机制基本上都提供了完备的支持。html

web的缓存机制有如下两大类:前端

  • 浏览器缓存机制
  • web开发中的缓存机制

浏览器缓存机制

浏览器自身的缓存机制是基于http协议层的Header中的信息实现的java

  • Cache-control && Expiresandroid

    这两个字段的做用是:接收响应时,浏览器决定文件是否须要被缓存;或者须要加载文件时,浏览器决定是否须要发出请求git

    Cache-control常见的值包括:no-cache、no-store、max-age等。其中max-age=xxx表示缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并若是和Last-Modified一块儿使用时, 优先级较高。github

  • Last-Modified && ETagweb

    这两个字段的做用是:发起请求时,服务器决定文件是否须要更新。服务端响应浏览器的请求时会添加一个Last-Modified的头部字段,字段内容表示请求的文件最后的更改时间。而浏览器会在下一次请求经过If-Modified-Since头部字段将这个值返回给服务端,以决定是否须要更新文件api

这些技术都是协议层所定义的,在Android的webview当中咱们能够经过配置决定是否采纳这几个协议的头部属性。设置以下:浏览器

webView.settings.cacheMode=WebSettings.LOAD_DEFAULT
// cacheMode的取值定义以下:
@IntDef({LOAD_DEFAULT, LOAD_NORMAL, LOAD_CACHE_ELSE_NETWORK, LOAD_NO_CACHE, LOAD_CACHE_ONLY})
@Retention(RetentionPolicy.SOURCE)
public @interface CacheMode {}
复制代码

web开发中的缓存机制

  • Application Cache
  • Dom Storage 缓存机制
  • Web SQL Database 缓存机制
  • IndexedDB 缓存机制
  • File System

关于以上这几个web开发中的缓存机制,能够参考这篇文章Android:手把手教你构建 全面的WebView 缓存机制 & 资源加载方案

认识HybridCache

HybridCache旨在提供一种native和webview之间共享缓存的解决方案,尤为是共享native中的图片缓存。在native开发中,咱们普遍的使用着各类图片加载库,好比:

这些存在native的图片加载框架为咱们提供了很是良好的图片缓存体验。HybridCache的一种具体运用,就是把在webview中的图片交由咱们的native的图片加载框架(或者是咱们本身实现的文件缓存)进行缓存,这样的好处就是:

  • 可以更持久的保存webview中的图片(并且不须要前端开发人员所关注)
  • 可以更加统一app内的图片缓存
  • webview和native的缓存贡献,在某些适用的场景具有节省流量和加快加载速度的优势

固然图片缓存只是一个至关具体的运用,实际上HybridCache提供的是更为普遍的webview资源加载拦截的功能,经过拦截webview中渲染网页过程当中各类资源(包括图片、js文件、css样式文件、html页面文件等)的下载,根据业务的场景考虑缓存的策略,能够从app端提供webview的缓存技术方案(不须要前端人员感知的)。

实现原理

Android的webview在加载网页的时候,用户可以经过系统提供的API干预各个中间过程。而HybridCache要拦截的就是网页资源请求的环节。这个过程,WebViewClient当中提供了如下两个入口:

public class WebViewClient {

	// android5.0以上的版本加入
   public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return shouldInterceptRequest(view, request.getUrl().toString());
    }

	  @Deprecated
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        return null;
    }
}
复制代码

上面的两个API是在调用了WebView#loadUrl()以后,请求网页资源(包括html文件、js文件、css文件以及图片文件)的时候回调。关于这两个API有几个点须要注意:

  • 回调不是发生在主线程,所以不能作一些处理UI的事情
  • 接口的返回值是同步的
  • WebResourceResponse这个返回值能够自行构造,其中关键的属性主要是:表明资源内容的一个输入流InputStream以及标记这个资源内容类型的mMimeType

只要在这两个入口构造正确的WebResourceResponse对象,就能够替换默认的请求为咱们提供的资源。所以,webview和native缓存共享的方案就是经过这两个入口,在每次请求资源的时候根据请求的URL/WebResourceRequest判断是否存在本地的缓存,并在缓存存在的状况下将缓存的输入流返回,示意图以下所示:

方案设计

先放上一张方案实现的设计类图:

ps:这张类图是一开始设计方案的时候画的,后续通过了屡次重构和调整,部分已经不尽一致,不过基本保持了核心的概念和结构

HybridCache的核心任务就是拦截资源请求,下载资源并缓存资源,所以整个库的设计就分为了下面三个核心点:

  • 请求拦截
  • 资源响应(下载/读取缓存)
  • 缓存

资源请求拦截

参考okhttp拦截器的思想设计了WebResInterceptorChain两个接口,定义了拦截的动做以及驱动拦截器的链条。实际上,这两个接口都只是类库内部可见。具体的实现是BaseInterceptorDefaultInterceptorChain两个对象。

BaseInterceptor是拦截发生和资源响应的核心对象,内部处理了包括寻找缓存资源、下载资源和写缓存的基本逻辑。同时它是一个抽象类,子类只须要实现它并根据对应的资源请求定义是否参与拦截、以及选择性的自定义配置下载和缓存的行为便可。

DefaultInterceptorChain仅仅只是用于用于驱动拦截器链条的流转,类库内部可见

资源响应

资源响应有两种状况:

  • 缓存响应
  • 下载响应

当对应的资源缓存不存在的时候,会直接触发资源的下载。在类库内部,会经过HttpConnectionDownloader直接创建一个HttpURLConnection进行资源的下载,得到资源的文件流。

同时参考代理模式,设计了边读边写的动做。即下载的资源流经过被封装为一个WebResInputStreamWrapper对象后直接返回。WebResInputStreamWrapper继承于InputStream,同时内部持有一个TempFileWriter的实例。在WebResInputStreamWrapper被浏览器读取的同时,TempFileWriter会把对应的资源写入到缓存当中,实现边读边写

缓存

CacheProvider定义了提供缓存的实现的规范,能够根据实际的业务场景提供任意的缓存实现方案。同时库内部经过LruCache提供了简单的文件缓存的实现SimpleCacheProvider。同时为了拓展共享图片缓存的实现,类库还提供了一个基于fresco的图片缓存提供实例FrescoImageProvider

CacheKeyProvider使得业务能够根据实际的场景提供缓存的key的生成策略。

关于方案的实现细节,能够关注个人GitHub仓库HybridCache

接入使用

在图片缓存以及简单的使用文件缓存资源这两个场景上,方案已经提供了直接的实现,能够简单的一键接入使用,总的接入步骤以下:

  1. 根据业务须要定义你的拦截器。你只须要继承BaseInterceptor,并实现仅有的一个抽象方法便可。若是你须要图片拦截器,能够直接使用类库内部提供的ImageInterceptor
  2. 在定义拦截器的同时,你能够实现你的缓存提供器,提供你的缓存管理策略。默认的状况下会使用SimpleCacheProvider提供文件缓存
  3. 使用HybridCacheManager#addCacheInterceptor()将拦截器添加都管理器中。
  4. 在初始化webview的时候,设置自定义的WebViewClient对象,并在其拦截资源请求的入口方法中调用HybridCacheManager#interceptWebResRequest()方法

以上简单的几步便可拥有native和webview共享缓存的功能。具体的实例能够参考GitHub仓库中的demo。

你可能会遇到的坑

在使用webview的时候,你可能会遇到一些坑

  • 页面当中资源包含http和https两种请求

    若是你加载的页面以及页面请求的资源包好了http和https两种请求,那么你有可能会出现部分资源没法加载的状况。这是由于在Android5.0以后,webview默认禁止在一个页面当中包含两种协议请求。这时候你须要添加这样的设置:setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW)

  • 加载的页面部分的资源请求出错(好比404)没有回调

    正如前面说起的,在Android6.0之后,你能够经过WebViewClient#onReceivedHttpError()接收到资源请求出错的回调,可是在此以前是没有这个api的,同时另一个APIWebViewClient#onReceivedError()的回调是在整个页面不可达等状况才会回调,而不会由于资源请求问题而响应,具体能够参考文档备注。

    在使用HybridCache资源拦截以后,你能够经过设置BaseInterceptor#setOnErrorListener(onErrorListener)感知到资源加载出错的状况

  • 缓存key不一样致使缓存不能共享

目前这个方案已经在咱们的项目中实际使用。你能够在个人GitHub仓库HybridCache中看到简单的实例.欢迎你们表达对这个方案的设计的见解和改进意见,谢谢。

相关文章
相关标签/搜索