HybridCache in a nutshell is actually a set of shared cache solutions between Native and WebView. But before we look at the implementation details of HybridCache and the problems it solves, let’s take a look at the caching mechanisms involved in Web development

Web caching mechanism

In fact, web development has a fairly complete cache mechanism, and the Android system WebView to these existing cache mechanism basically provides a complete support.

Web caching mechanisms fall into the following two categories:

  • Browser caching mechanism
  • Caching mechanisms in Web development

Browser caching mechanism

The browser’s own caching mechanism is based on the information in the HEADERS of the HTTP protocol layer

  • Cache-control && Expires

    When receiving a response, the browser decides whether the file needs to be cached. Or when a file needs to be loaded, the browser decides whether to make the request

    Cache-control common values include no-cache, no-store, and max-age. Max-age = XXX indicates that cached content will expire after XXX seconds. This option is only available in HTTP 1.1 and has a higher priority when used with last-Modified.

  • Last-Modified && ETag

    These two fields are used to determine whether the file needs to be updated when the request is made. A server responds to a browser request by adding a last-Modified header field that indicates when the requested file was Last Modified. The browser returns this value to the server on the next request via the if-Modified-since header field to determine whether the file needs to be updated

These technologies are defined by the protocol layer. In Android webView, we can decide whether to adopt the header properties of these protocols through configuration. The Settings are as follows:

webView.settings.cacheMode=WebSettings.LOAD_DEFAULT
// cacheMode values are defined as follows:
@IntDef({LOAD_DEFAULT, LOAD_NORMAL, LOAD_CACHE_ELSE_NETWORK, LOAD_NO_CACHE, LOAD_CACHE_ONLY})
@Retention(RetentionPolicy.SOURCE)
public @interface CacheMode {}
Copy the code

Caching mechanisms in Web development

  • Application Cache
  • Dom Storage caching mechanism
  • Web SQL Database caching mechanism
  • IndexedDB caching mechanism
  • File System

For more information about the caching mechanism in Web development, you can refer to this article Android: Step-by-step Guide to building a comprehensive WebView caching mechanism & resource loading solution

Know HybridCache

HybridCache aims to provide a solution for sharing caches between Native and webView, especially image caches in shared Native. In Native development, we use a wide variety of image loading libraries, such as:

  • fresco
  • picasso
  • glide

These native image loading frameworks provide us with a very good image caching experience. A specific application of HybridCache is to cache the pictures in the Webview by our native picture loading framework (or the file cache we implement ourselves). The benefits are as follows:

  • The ability to hold images in webView longer (and without the front-end developer’s concern)
  • More unified app image cache
  • Webview and Native cache contributions save traffic and speed up loading in some applicable scenarios

Of course, image cache is only a very specific application. In fact, HybridCache provides a broader function of webView resource loading interception. It intercepts the download of various resources (including pictures, JS files, CSS style files, HTML page files, etc.) in the process of rendering web pages in webView. Considering the caching strategy according to the business scenario, we can provide the webView caching technical solution from the APP side (which does not need to be perceived by the front-end personnel).

Realize the principle of

Android webView allows users to intervene in the process of loading a web page through the API provided by the system. HybridCache intercepts the web resource request. In this process, the WebViewClient provides the following two entrances:

public class WebViewClient {

	// Android5.0 + added
   public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
        return shouldInterceptRequest(view, request.getUrl().toString());
    }

	  @Deprecated
    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
        return null; }}Copy the code

The above two apis are callbacks to web resources (including HTML files, js files, CSS files, and image files) after calling WebView#loadUrl(). There are a few points to note about these two apis:

  • The callback doesn’t happen on the main thread, so you can’t do anything with the UI
  • The return value of the interface is synchronous
  • WebResourceResponseThe return value can be self-constructed, and the key attribute is primarily: an input stream representing the content of the resourceInputStreamAnd to mark the content type of the resourcemMimeType

As long as we construct the correct WebResourceResponse object at both entrances, we can replace the resources provided to us by the default request. Therefore, the solution shared by WebView and Native cache is to determine whether there is local cache according to the requested URL/WebResourceRequest every time resources are requested, and return the cached input stream if the cache exists, as shown in the diagram below:

The project design

Let’s start with a design class diagram for solution implementation:

Ps: This class diagram was drawn at the beginning of the design scheme. After several subsequent reconstructions and adjustments, part of it has been inconsistent, but the core concept and structure have been basically maintained

HybridCache’s core task is to intercept resource requests, download and cache resources, so the design of the whole library is divided into the following three core points:

  • Request to intercept
  • Resource response (download/read cache)
  • The cache

Resource request interception

Referring to the idea of OKHTTP interceptor, WebResInterceptor and Chain are designed to define the action of intercepting and the Chain driving the interceptor. In fact, both interfaces are only visible inside the class library. The concrete implementation is BaseInterceptor and DefaultInterceptorChain two objects.

BaseInterceptor is the core object for intercepting occurrences and resource responses. It handles the basic logic internally, including finding cached resources, downloading resources, and writing to the cache. At the same time, it is an abstract class, and subclasses only need to implement it and define whether to participate in interception and optionally customize the behavior of downloading and caching based on the corresponding resource request.

DefaultInterceptorChain is just a stream used to drive the interceptor chain, visible inside the class library

Resource response

There are two types of resource responses:

  • Cache the response
  • Download the response

If the corresponding resource cache does not exist, the resource download is triggered. In the class library, HttpConnectionDownloader directly establishes an HttpURLConnection to download the resource and obtain the file flow of the resource.

At the same time reference proxy mode, designed while reading while writing action. That is, the downloaded resource flow is returned directly after being wrapped as a WebResInputStreamWrapper object. WebResInputStreamWrapper inherits from InputStream and holds an instance of TempFileWriter internally. At the same time that WebResInputStreamWrapper is read by the browser, TempFileWriter writes the corresponding resource to the cache, enabling read and write

The cache

The CacheProvider defines an implementation specification that provides caching. It can provide any caching implementation solution based on actual business scenarios. At the same time, the library provides a simple file cache implementation SimpleCacheProvider through LruCache. To extend the implementation of shared image caching, the class library also provides a FrescoImageProvider, a fresco-based image caching provider

The CacheKeyProvider enables businesses to provide a strategy for generating cached keys based on actual scenarios.

For implementation details, follow my GitHub repository HybridCache

Access to the use of

In the image cache and simple use of file cache resources these two scenarios, the solution has provided direct implementation, can be simple one-click access to use, the total access steps are as follows:

  1. Define your interceptors according to your business needs. You just inheritBaseInterceptorAnd implement only one abstract method. If you need an image blocker, you can use one provided inside the class libraryImageInterceptor
  2. While defining interceptors, you can implement your cache provider and provide your cache management strategy. This is used by defaultSimpleCacheProviderProvide file caching
  3. useHybridCacheManager#addCacheInterceptor()Add interceptors to the Capital manager.
  4. When initializing the WebView, set the customWebViewClientObject and called in its entry method that intercepts resource requestsHybridCacheManager#interceptWebResRequest()methods

These simple steps will enable native and WebView shared caching. For an example, see the GitHub repository demo.

You may come across a pit

When using a WebView, you may encounter some pitfalls

  • Resources on the page include HTTP and HTTPS requests

    If you load a page and request resources for the page with HTTP and HTTPS packets, you may have some resources fail to load. This is because after Android5.0, webview by default forbids including two protocol requests in a single page. You need to add setMixedContentMode(websettings.mixed_content_always_allow)

  • There is no callback for a resource request error in the loaded portion of the page (such as 404)

    As mentioned earlier, after Android6.0, you can receive resource request error callbacks via WebViewClient#onReceivedHttpError(), but prior to that there was no API. The other callback, APIWebViewClient#onReceivedError(), is called only when the entire page is unreachable and not because of a resource request problem, as noted in the documentation.

    After using HybridCache resource intercepts, you can pass the SettingsBaseInterceptor#setOnErrorListener(onErrorListener)A situation where a resource load error is perceived

  • The cache cannot be shared due to different cache keys

At present, this scheme has been used in our project. You can see a simple example of this in my GitHub repository HybridCache. Welcome to express your opinion and improvement on the design of this program, thank you.