探索App性能优化之消耗流量优化-WebView缓存机制

942 阅读9分钟

一、痛点

由于H5具备开发周期短、灵活性好、动态化的特点,所以现在App大多数是Hybrid开发模式,用WebView组件加载H5页面进行业务的开发。

但让开发人员最苦恼的就是WebView的性能问题,卡、慢、页面滑动不流畅,特别突出的是:加载速度慢和耗费流量。每次使用H5页面时,用户都需要重新加载H5页面,每加载一个H5页面,都会产生较多网络请求,每一个请求都串行的,这么多请求串起来,这导致消耗的流量也会越多。

耗费流量优化是App性能优化中重要的一环,对用户的体验和品牌效应非常有影响。

通过缓存机制,可以有效地解决WebView加载H5页面耗费流量多的问题。

二、缓存机制介绍

1、概述

缓存机制即离线存储,这意味着H5网页被加载后课存储在缓存区域进行缓存,在没有网络连接时也可以访问。WebView的本质是在Android应用中嵌入H5页面,所以WebView自带的缓存机制其实就是H5页面的缓存机制。

2、作用

离线浏览:用户可在没有网络连接时进行H5页面访问 提高页面加载速度 & 减少流量消耗:直接使用已缓存的资源,不需要重新加载 减少服务器负载:浏览器只会从服务器上下载,更新过或更改过的资源

3、缓存类型

主要包括两类缓存,一类是浏览器自带的网页数据缓存,这是所有的浏览器都支持的,由HTTP协议定义的缓存;另一类是H5缓存,这是由web页面的开发者设置的,H5缓存主要包括了Application Cache、Dom Storage、Web SQL Database、Indexed Database、File System存储机制。

  • 浏览器缓存机制
  • Application Cache 缓存机制
  • Dom Storage 缓存机制
  • Web SQL Database 缓存机制
  • Indexed Database 缓存机制
  • File System 缓存机制(H5页面新加入的缓存机制,AndroidWebView暂时不支持)

(一) 浏览器缓存机制

1、原理

浏览器缓存机制是通过HTTP协议Header报文头里的Cache-Control/Expires和Last-Modified/Etag等字段来控制文件缓存的机制。

Cache-Control/Expires 字段是接收响应时,浏览器决定文件是否需要被缓存 或者 在加载文件时决定是否需要发出请求。

Cache-Control:用于控制文件在本地缓存有效时长

如服务器返回:Cache-Control:max-age=900,则表示文件在本地应该缓存 & 有效时长是900秒(从发出请求算起)。如果900秒内需要再次请求这个资源文件,浏览器不会发出HTTP请求,而是直接使用本地缓存的文件。这是HTTP/1.1标准中的字段。

Expires:与Cache-Control功能相同,即控制缓存的有效时间

如服务器返回:Expires:Thu, 31 Dec 2037 23:55:55 GMT, 表示该文件的过期时间是2037年12月31日23点55分55秒,在这个时间之前浏览器都不会再次发出请求去获取这个文件。这是HTTP/1.0中的字段。如果客户端和服务器时间不同步会导致缓存出现问题,因此才有了上面的Cache-Control。

当Cache-Control和Expries同时出现在HTTP Response的Header中时,Cache-Control优先级更高。

Last-Modified/Etag 字段是发起请求时,服务器决定文件是否需要更新。

Last-Modified:标识文件在服务器上的最新更新时间

Last-Modified:Wed, 28 Sep 2016 09:24:35 GMT, 这表示这个文件最后的修改时间是2016年9月28日9点24分35秒。这个字段对于浏览器来说,会在下次请求的时候,作为请求头Request Header的If-Modified-Since字段带上。例如浏览器缓存的文件已经超过了Cache-Control/Expires,那么需要加载这个文件时,就会发出请求,请求的Header有一个字段为If-Modified-Since:Wed, 28 Sep 2016 09:24:35 GMT,服务器接收到请求后,会把文件的Last-Modified时间和If-Modified-Since的这个时间对比,如果时间没变,那么浏览器将返回304 Not Modified给浏览器, 且content-length是0个字节。如果时间有变化,那么服务器会返回200 OK, 并返回相应的content给浏览器。

Etag:功能同Last-Modified ,即标识文件在服务器上的最新更新时间 ETag:"57eb8c5c-129",这是文件的特征串。功能同上面的Last-Modified是一样的。只是在浏览器下次请求时,ETag是作为请求头Request Header中的If-None-Match:"57eb8c5c-129"字段传到服务器。 服务器和最新的文件特征串对比,如果相同那么返回304 Not Modified,不同则返回200 OK。

当ETag和Last-Modified同时出现时,任何一个字段只要生效了,就认为文件是没有更新的。

2、特别说明

Cache-Control与Last-Modified一起使用;Expires与Etag一起使用;即一个用于控制缓存有效时间,另一个用于在缓存失效后,向服务查询是否有更新。

浏览器缓存机制是浏览器内核的机制,一般都是标准的实现,只要是个主流的、合格的浏览器,都应该能够支持HTTP协议层面的这几个字段,即Cache-Control、Last-Modified、Expires、Etag都是标准实现,这不是我们开发者可以更改或设置的。

缓存文件需要首次加载后才会产生,浏览器缓存的存储空间有限,缓存有被清除的可能,缓存的文件没有校验。

适用于静态资源文件的存储,如JS/CSS/字体/图片等。

3、WebView如何设置才能支持上面的协议

WebView内置自动实现,即不需要设置即实现。Webview会将缓存的文件记录及文件内容会存在当前data/data目录中。Android4.4后的WebView浏览器版本内核是Chrome,所以肯定支持这几个字段,但是我们可以通过代码去设置WebView的CacheMode,而使得协议生效或者无效。

WebView有下面几个Cache Mode:

LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据
LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
LOAD_NO_CACHE: 不使用缓存,只从网络获取数据.
LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。

设置WebView缓存的Cache Mode示例代码如下:

WebSettings settings = webView.getSettings();
settings.setCacheMode(WebSettings.LOAD_DEFAULT);

可以根据网络条件去选择CacheMode。当有网络时设置为LOAD_DEFAULT,当没有网络时设置为LOAD_CACHE_ELSE_NETWORK。最终还是要根据实际业务去选择,js文件的更新都是非覆盖式的更新,也就是时候每次改变js文件的时候,文件的url地址一定会发生变化。那么就只设置为LOAD_CACHE_ELSE_NETWORK。当然如果你要是可以改js的cdn服务器的Cache-Control字段,用LOAD_DEFAULT就可以了。

(二) H5的缓存-Application Cache

1、原理

以文件为单位进行缓存,是由开发H5页面的人员控制的,而不是由Native去控制的,但是Native里面的WebView也需要做设置才能支持H5的缓存。AppCache原理有两个关键点:manifest属性和manifest文件。

AppCache在首次加载生成后,也有更新机制。被缓存的文件如果要更新,需要更新manifest文件。因为浏览器在下次加载时,除了会默认使用缓存外,还会在后台检查manifest文件有没有修改,发现有修改,就会重新获取manifest文件,对Section:CACHEMANIFEST下文件列表检查更新。manifest文件与缓存文件的检查更新也遵守浏览器缓存机制。

<!DOCTYPE html>
<html manifest="manifest.appcache">
//HTML在头中通过manifest属性引用manifest文件
//manifest文件:就是上面manifest.appcache文件,列出了需要缓存的文件列表
//浏览器在首次加载HTML文件时,会解析manifest属性,并读取manifest文件,
    获取Section:CACHE MANIFEST下要缓存的文件列表,再对文件缓存
<body>
...
</body>
</html>

manifest.appcache文件如下:

  • CACHE MANIFEST下面文件就是要被浏览器缓存的文件
  • NETWORK下面的文件就是要被加载的文件
  • FALLBACK下面的文件是目标页面加载失败时的显示的页面
CACHE MANIFEST
# 2020-05-05 v1.0.0
/bridge.js
/a.js
/b.js

NETWORK:
*

FALLBACK:
/404.html

2、特别说明

AppCache是对浏览器缓存机制的补充不是替代。

适用于静态资源文件的存储,如JS/CSS/字体/图片等。

3、WebView如何设置才能支持AppCache

WebView默认是没有开启AppCache支持的,需要添加下面这几行代码来设置:

// 通过设置WebView的settings来实现
WebSettings webSettings = webView.getSettings();

//开启Application Cache存储机制
webSettings.setAppCacheEnabled(true);

//把内部私有缓存目录'/data/data/包名/cache/'作为WebView的AppCache的存储路径
String cacheDirPath = getApplicationContext().getCacheDir().getPath(); 

//设置缓存路径
webSettings.setAppCachePath(cachePath);

//设置缓存大小
webSettings.setAppCacheMaxSize(5 * 1024 * 1024);

4、特别注意

WebSettings的setAppCacheEnabled和setAppCachePath都必须要调用才行。

AppCache缓存路径是通过WebSettings.setAppCachePath设置的,但不管怎么设置这个路径,设置到应用内部私有目录or外部SD卡都无法生效。AppCache缓存文件最终都会存到/data/data/包名/app_webview/cache/Application Cache这个文件夹下面,但是如果你不调用setAppCachePath方法,WebView将不会产生这个目录。我猜测可能从某一个系统版本开始,为了缓存文件的完整性和安全性考虑,SDK实现的时候就把AppCache缓存目录设置到了内部私有存储。

三、总结

以上分别从浏览器缓存和H5缓存-ApplicationCache,介绍了利用WebView缓存机制优化App流量的耗费,但在实际工作中还会有其他方法,比如我们公司的App是Native通过WebViewClient的shouOverrideUrlLoading拦截H5的请求,通过原生去发送网络请求,服务器返回的数据会先到原生,然后再回调给H5,因此可以通过客户端拦截资源,把资源缓存到本地的方式进行缓存。