当前位置: 首页 > 科技观察

WebView缓存原理分析与应用

时间:2023-03-13 21:12:21 科技观察

一、背景目前的App开发或多或少都采用了Hybrid模式。在WebView中,经常会加载一些js文件(比如用于与WebView进行Native通信的bridge.js),而这些js文件不会经常变化,所以我们希望在WebView加载一次js后,如果有js没有变化,下次不需要再发起网络请求加载,从而减少流量和资源占用。那么有什么办法可以达到这个目的呢?首先从WebView的缓存原理说起。2、WebView的缓存类型WebView主要包括两种缓存,一种是浏览器自带的网页数据缓存,所有浏览器都支持,由HTTP协议定义;另一个是H5缓存,由网页开发者设置。H5缓存主要包括AppCache、DOMStorage、LocalStorage、WebSQLDatabase存储机制等,这里主要介绍AppCache来缓存js文件。三、浏览器自身的网页数据缓存1、工作原理浏览器缓存机制是一种通过HTTP协议Header中的Cache-Control(或Expires)、Last-Modified(或Etag)等字段来控制文件缓存的机制。关于这些字段的作用和浏览器的缓存更新机制,可以看这两篇文章(H5缓存机制浅析,移动端Web加载性能优化,Android:手把手教你搭建WebView缓存机制&资源预加载方案),其中包含详细介绍。下面从我实际应用的角度,介绍一下HTTP协议中经常遇到的headers。这两个字段是浏览器在收到响应时决定文件是否需要缓存的字段,或者当文件需要加载时浏览器是否需要发出请求的字段。Cache-Control:max-age=315360000,表示缓存时长为315360000秒。如果在315360000秒内需要再次请求该文件,浏览器将不再请求,直接使用本地缓存的文件。这是HTTP/1.1标准中的一个字段。Expires:Thu,31Dec203723:55:55GMT,也就是说这个文件的过期时间是2037年12月31日晚上23:55:55。在这个时间之前,浏览器不会再发送请求到获取此文件。这是HTTP/1.0中的一个字段。如果客户端和服务器的时间不同步,就会导致缓存出现问题。于是就有了上面的Cache-Control。当它们同时出现在HTTPResponseHeader中时,Cache-Control的优先级更高。下面两个字段是服务器发起请求时用来判断文件是否需要更新的字段。Last-Modified:Wed,28Sep201609:24:35GMT,表示该文件的最后修改时间为2016年9月28日9:24:35。对于浏览器,该字段将被包含为If-Modified-Since字段在下一个请求中的RequestHeader。比如浏览器缓存的文件已经超过了Cache-Control(或者Expires),那么当需要加载文件的时候,就会发送一个请求,请求的header中有一个字段为If-Modified-Since:2016年9月28日星期三09:24:35GMT。服务器收到请求后,会将文件的Last-Modified时间与这个时间进行比较。如果时间没有改变,浏览器会返回304NotModified给浏览器,并且content-length必须是0bytes。如果时间发生变化,服务器会返回200OK,并将相应的内容返回给浏览器。ETag:“57eb8c5c-129”,这是文件的特征字符串。功能与上面的Last-Modified相同。只有当浏览器发出下一个请求时,ETag才会作为RequestHeader中的If-None-Match:"57eb8c5c-129"字段传递给服务器。比较服务器和***的文件特征串,相同则返回304NotModified,不同则返回200OK。当ETag和Last-Modified同时出现时,只要任何一个字段生效,就认为文件没有更新。2、如何设置WebView支持以上协议从上面的介绍来看,只要是主流的、合格的浏览器,在HTTP协议层面应该都可以支持这些字段。这不是我们开发者可以修改的配置,也不应该修改。在Android上,我们的WebView也支持这些字段。但是我们可以通过代码设置WebView的CacheMode来使协议有效或者无效。WebView有以下几种缓存模式:LOAD_CACHE_ONLY:不使用网络,只读取本地缓存数据。LOAD_DEFAULT:根据cache-control决定是否从网络取数据。LOAD_CACHE_NORMAL:在API级别17中已过时,它与API级别11中的LOAD_DEFAULT模式相同LOAD_NO_CACHE:不使用缓存,仅从网络获取数据。LOAD_CACHE_ELSE_NETWORK,只要本地有,不管是过期还是无缓存,都会使用缓存中的数据。仅当没有本地缓存??时才从网络中获取。设置WebView缓存CacheMode的示例代码如下:WebSettingssettings=webView.getSettings();settings.setCacheMode(WebSettings.LOAD_DEFAULT);网上很多人说根据网络情况选择CacheMode。当有网络时,将其设置为LOAD_DEFAULT。没有网络时设置为LOAD_CACHE_ELSE_NETWORK。但是在我的业务中,js文件的更新都是非覆盖更新,也就是js文件每次变化,文件的url地址肯定会变化,所以希望浏览器能够缓存js,保存到使用它,然后我将它设置为LOAD_CACHE_ELSE_NETWORK。当然,如果你能改一下js的cdn服务器的Cache-Control字段,那也是可以的。只需使用LOAD_DEFAULT。至于文件更新是覆盖还是非覆盖,不是我今天要讨论的。在web前端领域,这是一个可以聊的话题。关于iOS的WebView,同事在实际测试中发现,控制文件缓存的ResponseHeader就是Expires字段。.而且iOS无法为整个WebView设置CacheMode,只能为每个URLRequest设置。.以后有机会去了解一下iOS的情况。3、手机中的存储路径浏览器缓存的文件默认是怎么存储的?这个问题从接触WebView开始就一直是个谜。这次由于工作需要,特意root了两台手机,一台红米1(安卓4.4)和一台小米4c(安卓5.1),两台高root系统版本(6.0和7.1)的Nexus都root了with失败后,我决定看一下WebView自带的缓存在4.4和5.1系统上存放在哪里。首先,不用想也能知道,这些文件一定在/data/数据/包名/目录下。在我之前的博客中提到过,这是每个应用程序的内部存储目录。接下来,我们打开终端,使用adb连接手机,然后按照下面的命令操作。//1。首先进入shelladbshel??l//2。打开root账号su//3。修改文件夹权限chmod777data/data/你的应用包名///4.修改子文件夹权限,因为Android命令行不支持Recursivechmod的实现类似于Linux中的-R命令。..chmod777data/data/你的应用包名/*//5.所以如果你有更深层次的应用目录,你需要进一步chmod。..chmod777data/data/你的应用包名/*/*//6.直到终端提示你说nosuchfileordirectory,就说明chmod结束了,可以看到内部存储中的所有文件夹和文件了。有的话请告诉我好的方法,谢谢~Android4.4目录:/data/data/包名/app_webview/cache/,第二个文件夹如下图。您可能已经注意到第一个文件夹名为ApplicationCache,我们稍后会讲到。Android5.1的目录:/data/data/包名/cache/org.chromium.android_webview/下,如下图。但是在5.1系统上,/data/data/包名/app_webview/文件夹依然存在,但是4.4系统上存放WebView自身缓存的app_webview/cache文件夹已经不存在了(注意AppCache目录还在),如下所示。综上所述,WebView自带的浏览器协议支持的缓存在不同的系统版本上位置不同。可能除了我已经root过的4.4和5.1,其他版本系统的WebView内置缓存也可能存在于不同的目录下。还有一个是关于缓存文件的存储格式和索引格式,在不同的手机上可能不一样,因为之前在网上看到有人说有一个文件叫webview.db或者webviewCache.db,这个文件是不在app_webview/cache或org.chromium.android_webview中,而是在/data/data/packagename/database/中。但是我的两台root过的手机都没有看到这种文件,我打开/data/data/包名/下的所有db文件,也没有发现存放url记录的表。.其实以5.1系统为例,我看到/data/data/包名/cache/org.chromium.android_webview/下有index和/index-dir/the-real-index的文件,还有一个bunchofnames是一个md5+underscore+number的文件,在上图中也可以看到。对这块的原理还有一些疑惑,希望有专业的高手解答。4、H5的缓存说完了WebView自带的缓存,再来说说H5中的AppCache。这个Cache是??由开发网页的开发者控制的,不是Native控制的,但是Native中的WebView也需要我们做一些设置来支持H5的这个特性。一、工作原理在编写网页代码时,指定manifest属性,使页面使用AppCache。通常html页面代码会这样写:xxx.appcache文件使用相对路径。此时appcache文件的路径和页面是一样的。也可以使用绝对路径,但域名必须与页面一致。一个完整的xxx.appcache文件一般包括3个部分,基本格式如下:CACHEMANIFEST#2017-05-13v1.0.0/bridge.jsNETWORK:*FALLBACK:/404.htmlCACHEMANIFEST下面的文件是要被浏览器缓存NETWORK下的文件就是要加载的文件。FALLBACK下的文件是目标页面加载失败时显示的页面。AppCache的工作原理:当加载一个带有manifest文件集的html页面时,会加载CACHEMANIFEST指定的文件。缓存到浏览器的AppCache目录。下次加载这个页面时,会先申请通过manifest缓存的文件,然后向服务器发起加载xxx.appcache文件的请求。如果xxx.appcache文件没有被修改过,服务器会返回304NotModified给浏览器,如果xxx.appcache文件已经被修改过,服务器会返回200OK并将新的xxx.appcache文件的内容返回给浏览器浏览器。内容已加载并缓存。可以看出AppCache缓存需要在每次加载页面时发送一个xxx.appcache请求来检查manifest文件是否更新(逐字节)。AppCache有一些坑,官方不推荐使用,但目前主流浏览器还是支持的。文章主要提到了以下几个陷阱:要更新缓存文件,你需要更新包含它的清单文件,即使只添加一个空格。一个常用的方法是修改manifest文件注释中的版本号。例如:#2012-02-21v1.0.0浏览器先使用缓存文件,然后通过检查manifest文件是否更新来更新缓存文件。此缓存文件可能未使用最新版本。在更新缓存的过程中,如果一个文件更新失败,则整个更新都会失败。清单和引用它的HTML必须位于同一主机上。manifest文件中的文件列表,如果是相对路径,就是相对于manifest文件的相对路径。清单也可能更新不正确,导致无法更新缓存文件。没有缓存的资源无法在缓存的HTML中加载,即使有网络。例如:[url=]http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/[/url]manifest文件本身不能缓存,更新的manifest文件使用的是一种浏览器缓存机制。所以manifest文件的Cache-Control缓存时间不能设置的太长。2、如何设置WebView支持AppCacheWebView默认不开启AppCache支持,需要添加如下几行代码进行设置:WebSettingswebSettings=webView.getSettings();webSettings.setAppCacheEnabled(true);StringcachePath=getApplicationContext().getCacheDir().getPath();//使用内部私有缓存目录'/data/data/包名/cache/'作为WebView的AppCache的存放路径webSettings.setAppCachePath(cachePath);webSettings.setAppCacheMaxSize(5*1024*1024);注意:必须同时调用WebSettings的setAppCacheEnabled和setAppCachePath。3.AppCache的存放路径根据AndroidSDK的API说明,可以通过setAppCachePath来设置AppCache的路径,但是我实际测试发现无论怎么设置这个路径,是否设置为内部私有目录应用程序或外部SD卡,它将无法生效。AppCache缓存文件最终会存放在/data/data/包名/app_webview/cache/ApplicationCache文件夹下,在上面Android4.4和5.1系统目录截图中可以看到,但是如果不调用setAppCachePath方法,WebView不会生成这个目录。这对我来说有点奇怪。我猜想可能是从某个系统版本开始的。为考虑缓存文件的完整性和安全性,SDK实现时将AppCache缓存目录设置为内部私有存储。5.相同点总结WebView内置的缓存和AppCache可以进行文件级缓存,基本满足了非覆盖js、css等文件更新的需要。不同的是,WebView自带的缓存是在协议层实现的(浏览器内核标准实现,开发者无法更改);而AppCache是??在应用层实现的。WebView的缓存目录在不同的系统上可能不一样;对于AppCache,虽然有方法可以设置AppCache的存储路径,但最终还是存储在一个固定的内部私有目录中。WebView内置缓存,缓存生效时不需要发送HTTP请求;而AppCache肯定会发送清单文件请求。WebView自带的缓存可以通过设置CacheMode来改变WebView的缓存机制;而AppCache的缓存策略是由manifest文件控制的,也就是说是由网页开发者控制的。***告诉你,其实很多时候,这两种缓存是一起工作的。当manifest文件没有控制某些资源的加载时,比如我上面写的xxx.appcache文件中,NETWORK部分使用了*,这意味着所有没有被缓存的文件都必须从网络加载。此时,这些资源就会进入WebView自带的缓存机制。结合WebView的CacheMode,我们实际上把这些文件缓存了一次。了解这两类缓存的原理,有助于我们更好地设计自己的页面和APP,尽可能减少网络请求,提高APP运行效率。