//wk清空缓存
NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
NSDate *dateFrom = [NSDate dateWithTimeIntervalSince1970:0];
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:dateFrom completionHandler:^{
}];
使用allWebsiteDataTypes 清理后,会影响到weview的后续缓存
allWebsiteDataTypes 包含
#Cookie Type
1. WKWebsiteDataTypeCookies
#Cache Types
//On-disk caches.
2. WKWebsiteDataTypeDiskCache
//HTML offline web app caches.
3. WKWebsiteDataTypeOfflineWebApplicationCache
//In-memory caches.
4. WKWebsiteDataTypeMemoryCache
#Storage Types
//HTML local storage.
5. WKWebsiteDataTypeLocalStorage
//HTML session storage.
6. WKWebsiteDataTypeSessionStorage
#Database Types
//WebSQL databases.
7. WKWebsiteDataTypeWebSQLDatabases
//IndexedDB databases.
8. WKWebsiteDataTypeIndexedDBDatabases
#Global Variable
9. WKWebsiteDataTypeFetchCache
10. WKWebsiteDataTypeServiceWorkerRegistrations
所以 removeDataOfTypes 使用allWebsiteDataTypes 会把数据库也清除掉,印象后续的缓存。
WKWebsiteDataTypeDiskCache
WKWebsiteDataTypeOfflineWebApplicationCache
WKWebsiteDataTypeLocalStorage
WKWebsiteDataTypeCookies
WKWebsiteDataTypeSessionStorage
WKWebsiteDataTypeWebSQLDatabases
WKWebsiteDataTypeIndexedDBDatabases
1. 会话存储:
存储对数据只有在同一个会话中的页面才能访问并且
当会话结束后数据也随之销毁。// 因此sessionStorage
不是一种持久化的本地存储,仅仅是会话级别的存储
WKWebsiteDataTypeSessionStorage,
2.本地存储:
localStorage 类似 sessionStorage,但其区别在于,
存储在 localStorage 的数据可以长期保留.WKWebsiteDataTypeLocalStorage,
// HTTP 磁盘缓存。 WKWebsiteDataTypeDiskCache,
3. Cookies存储:存储所有的cookie数据 WKWebsiteDataTypeCookies,
4. IndexedDB数据库:IndexedDB是WebSQL数据库的替代品。
IndexedDB是key-value型数据库,操作简单。 WKWebsiteDataTypeIndexedDBDatabases,
5。 webSQL数据库:W3C组织在2010年11月18日废弃了webSql 数据库,该数据库接口操作复杂,对用户不友好。WKWebsiteDataTypeWebSQLDatabases
6. html离线Web应用程序缓存。WKWebsiteDataTypeOfflineWebApplicationCache,
7 HTTP 内存缓存。WKWebsiteDataTypeMemoryCache,
通过数据分析,主要是 indexedDB 与 NetworkCache 占据较大比例,可达80%以上。WebKit 磁盘缓存分布如下表:
磁盘文件目录 | 缓存类型 |
---|---|
Library/WebKit | IndexedDB |
LocalStorage | |
MediaKeys | |
ResourceLoadStatistics | |
CacheStorage | |
NetworkCache | |
Library/Caches/WebKit | offlineWebApplicationCache |
ServiceWorkers |
前进后退缓存 – pageCache
在 WebKit 中,pageCache 其实就是对 WebBackForwardCache – 前进后退缓存的封装,本质上是浏览历史的一种记录,不属于上述标准缓存。前进后退缓存,将整个页面快照存入到内存中,下一次使用的时候,不用进行各类资源加载,甚至不用进行渲染工作。
通过源码查看,pageCache 大小会随着可使用内存大小动态变化:
手机可用内存a | 可缓存page页数 |
---|---|
a >= 512M | 2 |
512M > a >= 256M | 1 |
other | 0 |
/ back/forward cache capacity (in pages)
if (memorySize >= 512)
backForwardCacheCapacity = 2;
else if (memorySize >= 256)
backForwardCacheCapacity = 1;
else
backForwardCacheCapacity = 0;
资源的过期时间默认为30分钟。通过定时器触发任务,30分钟后自动清理过期的 page
static const Seconds expirationDelay { 30_min }; //通过定时器触发,到过期时间后,进行资源清理 void WebBackForwardCacheEntry::expirationTimerFired { RELEASE_LOG(BackForwardCache, "%p - WebBackForwardCacheEntry::expirationTimerFired backForwardItemID=%s, hasSuspendedPage=%d", this, m_backForwardItemID.string.utf8.data, !!m_suspendedPage); ASSERT(m_backForwardItemID); auto* item = WebBackForwardListItem::itemForID(m_backForwardItemID); ASSERT(item); m_backForwardCache.removeEntry(*item); // Will destroy |this|. }
因 pageCache 存储页面数量有限,因此当 超出页面缓存上限时,需要通过如下 LRU 算法进行替换:
void BackForwardCache::prune(PruningReason pruningReason) { while (pageCount > maxSize) { auto oldestItem = m_items.takeFirst; oldestItem->setCachedPage(nullptr); oldestItem->m_pruningReason = pruningReason; RELEASE_LOG(BackForwardCache, "BackForwardCache::prune removing item: %s, size: %u / %u", oldestItem->identifier.string.utf8.data, pageCount, maxSize); } } 缓存时机源码如下
bool WebPageProxy::suspendCurrentPageIfPossible(...) { ...// If the source and the destination back / forward list items are the same, then this is a client-side redirect. In this case, // there is no need to suspend the previous page as there will be no way to get back to it. if (fromItem && fromItem == m_backForwardList->currentItem) { RELEASE_LOG_IF_ALLOWED(ProcessSwapping, "suspendCurrentPageIfPossible: Not suspending current page for process pid %i because this is a client-side redirect", m_process->processIdentifier); return false; }... //创建 SuspendedPageProxy 变量,此时 m_suspendedPageCount 的值会加一 auto suspendedPage = makeUnique(*this, m_process.copyRef, *mainFrameID, shouldDelayClosingUntilFirstLayerFlush); m_lastSuspendedPage = makeWeakPtr(*suspendedPage); //添加进历史栈缓存 backForwardCache.addEntry(*fromItem, WTFMove(suspendedPage)); }
可以看到,如果 WKWebView 切换页面时,
发现 cross-site 且为 client-side redirect 时会清理当前
WebProgressProxy 关联的所有历史栈缓存,
后续切换到这些历史栈时都需要重新请求网络。而其他类型都会正常存储,
因此可以基于前进后退相关操作的页面性能考虑,可以减少前端重定向,
多依赖后端进行重定向功能
2.2 缓存清理方式
处于内存中的缓存,会随着进程的结束而消亡。而处于磁盘中的缓存, 则可以通过如下方法进行手动清理,避免磁盘占用增长过大。
2.2.1 文件目录清理方式
webkit磁盘中的较多数据都是通过域名做为文件名的一部分,因此也可以通过域名、日期等方式匹配,进行文件删除:
NSString *libraryDir = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,NSUserDomainMask, YES)[0];
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"];
NSString *webkitFolderInLib = [NSString stringWithFormat:@"%@/WebKit",libraryDir];
NSString *webKitFolderInCaches = [NSString stringWithFormat:@"%@/Caches/%@/WebKit",libraryDir,bundleId];NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:webKitFolderInCaches error:&error];
[[NSFileManager defaultManager] removeItemAtPath:webkitFolderInLib error:nil];
HTTP 内存缓存
WKWebView 与 app 处于不同进程中,且内存与磁盘缓存也在不同进程中,其中,内存缓存位于 WebContentProcess 进程中,而磁盘缓存位于 NetworkProcess 进程中。且每个memoryCache 对应一个 webContent 进程,如图所示。
如上图所示,一个页面对应一个WebContentProcess 进程】、
当页面销毁时,其对应的内存缓存也被销毁。
3.1 内存缓存大小
虽然 WebKit 进程独立与 app 进程,但内存占用过大依旧会影响到
app 进程的性能,因此内存缓存根据手机当前缓存大小进行分配。
手机可用内存a | 页面内存分配 |
---|---|
a >= 2G | 128M |
2G > a >= 1.5G | 96M |
1.5G > a >= 1G | 64M |
1G > a >= 0.5G | 32M |
other | 16M |
缓存大小计算策略源码如下
case CacheModel::PrimaryWebBrowser: { // back/forward cache capacity (in pages) if (memorySize >= 512) backForwardCacheCapacity = 2; else if (memorySize >= 256) backForwardCacheCapacity = 1; else backForwardCacheCapacity = 0; // Object cache capacities (in bytes) // (Testing indicates that value / MB depends heavily on content and // browsing pattern. Even growth above 128MB can have substantial // value / MB for some content / browsing patterns.) if (memorySize >= 2048) cacheTotalCapacity = 128 * MB; else if (memorySize >= 1536) cacheTotalCapacity = 96 * MB; else if (memorySize >= 1024) cacheTotalCapacity = 64 * MB; else if (memorySize >= 512) cacheTotalCapacity = 32 * MB; else cacheTotalCapacity = 16 * MB; cacheMinDeadCapacity = cacheTotalCapacity / 4; cacheMaxDeadCapacity = cacheTotalCapacity / 2; // This code is here to avoid a PLT regression. We can remove it if we // can prove that the overall system gain would justify the regression. cacheMaxDeadCapacity = std::max(24u, cacheMaxDeadCapacity); deadDecodedDataDeletionInterval = 60_s; break; }
3.2 内存缓存策略 使用 map 字典,在内存中使用 url 为 key,resource 资源为 value,对当前页面的所有 HTTP 网络请求资源进行存储。3.2.1 内存缓存添加
bool MemoryCache::add(CachedResource& resource) {
if (disabled) return false;
if (resource.resourceRequest.httpMethod != "GET")
return false;
ASSERT(WTF::isMainThread);
auto key = std::make_pair(resource.url, resource.cachePartition);
ensureSessionResourceMap(resource.sessionID).set(key, &resource);
resource.setInCache(true);
resourceAccessed(resource);
LOG(ResourceLoading, "MemoryCache::add Added '%.255s', resource %p\n", resource.url.string.latin1.data, &resource);
return true; }
3.2.2 内存缓存读取
CachedResource* MemoryCache::resourceForRequest(
const ResourceRequest& request, PAL::SessionID sessionID) {
// FIXME: Change all clients to make sure HTTP(s) URLs have no fragment identifiers before calling here.
// CachedResourceLoader is now doing this. Add an assertion once all other clients are doing it too.
auto* resources = sessionResourceMap(sessionID);
if (!resources)
return nullptr;
return resourceForRequestImpl(request, *resources);
}
CachedResource* MemoryCache::resourceForRequestImpl(const ResourceRequest& request, CachedResourceMap& resources) {ASSERT(WTF::isMainThread); URL url = removeFragmentIdentifierIfNeeded(request.url); auto key = std::make_pair(url, request.cachePartition); return resources.get(key); }
** 3.2.3HTTP 内存缓存读取策略**
HTTP 内存缓存读取时机不同于磁盘缓存,它并不完全遵守 HTTP 标准协议,
而是根据浏览器所加载的资源策略来进行的。例如:
-
前进后退历史中的页面的资源请求,可以直接读取内存缓存。
-
图片资源使用同一 url 加载的时候,后续的资源会 block 住, 等待首个图片资源的返回,直接使用缓存。
-
preload 资源可直接读取缓存,不必进行任何判断。
****// 网络请求加载是否使用内存缓存有如下策略:
enum RevalidationPolicy {
Use,
// 直接使用Revalidate, // 需要经过 HTTP 缓存协议校验Reload, // 重新加载,清理内存缓存,并重新请求Load // 直接从网络加载};
RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get, forPreload, imageLoading);
HTTP 磁盘缓存
磁盘缓存的设计完全遵循 HTTP 标准缓存协议。
所有的网络请求都经过 NetWorkProcess 进程发出,请求在发出之前,
则会经过缓存协议检验,根据 HTTP 协议进行相应操作(读取缓存/协商检验/不使用缓存等)。
当服务端返回请求内容后,NetworkProcess 模块也会做出对应的判断,
决定内容是否进行缓存或更新,如下所示。
4.1 HTTP 缓存处理流程图
4.2 HTTP 磁盘缓存大小、
磁盘缓存存入到指定的文件目录中,其中默认为:
Library/Caches/WebKit/NetworkCache。可以通过如下方法进行指定:
case CacheModel::PrimaryWebBrowser: {
// Disk cache capacity (in bytes)
if (diskFreeSize >= 16384)
urlCacheDiskCapacity = 1 * GB;
else if (diskFreeSize >= 8192)
urlCacheDiskCapacity = 500 * MB;
else if (diskFreeSize >= 4096)
urlCacheDiskCapacity = 250 * MB;
else if (diskFreeSize >= 2048)
urlCacheDiskCapacity = 200 * MB;
else if (diskFreeSize >= 1024)
urlCacheDiskCapacity = 150 * MB;
else urlCacheDiskCapacity = 100 * MB;
break;
}
default: ASSERT_NOT_REACHED;
};
4.3 HTTP 存入缓存校验 本部分主要根据请求和响应来判断是否需要存储到缓存中。主要判断 scheme、method 以及资源的缓存策略。
if (originalRequest.httpMethod != "GET")return StoreDecision::NoDueToHTTPMethod;
auto requestDirectives = WebCore::parseCacheControlDirectives(originalRequest.httpHeaderFields);if (requestDirectives.noStore)return StoreDecision::NoDueToNoStoreRequest;
if (response.cacheControlContainsNoStore)return StoreDecision::NoDueToNoStoreResponse;
if (!WebCore::isStatusCodeCacheableByDefault(response.httpStatusCode)) {// http://tools.ietf.org/html/rfc7234#section-4.3.2bool hasExpirationHeaders = response.expires || response.cacheControlMaxAge;bool expirationHeadersAllowCaching = WebCore::isStatusCodePotentiallyCacheable(response.httpStatusCode) && hasExpirationHeaders;if (!expirationHeadersAllowCaching)return StoreDecision::NoDueToHTTPStatusCode;}
bool isMainResource = originalRequest.requester == WebCore::ResourceRequest::Requester::Main;bool storeUnconditionallyForHistoryNavigation = isMainResource || originalRequest.priority == WebCore::ResourceLoadPriority::VeryHigh; if (!storeUnconditionallyForHistoryNavigation) {auto now = WallTime::now;Seconds allowedStale { 0_ms };#if ENABLE(NETWORK_CACHE_STALE_WHILE_REVALIDATE)if (auto value = response.cacheControlStaleWhileRevalidate)allowedStale = value.value;#endifbool hasNonZeroLifetime = !response.cacheControlContainsNoCache && (WebCore::computeFreshnessLifetimeForHTTPFamily(response, now) > 0_ms || allowedStale > 0_ms);bool possiblyReusable = response.hasCacheValidatorFields || hasNonZeroLifetime;if (!possiblyReusable)return StoreDecision::NoDueToUnlikelyToReuse;}
// Media loaded via XHR is likely being used for MSE streaming (YouTube and Netflix for example).// Streaming media fills the cache quickly and is unlikely to be reused.// FIXME: We should introduce a separate media cache partition that doesn't affect other resources.// FIXME: We should also make sure make the MSE paths are copy-free so we can use mapped buffers from disk effectively.auto requester = originalRequest.requester;bool isDefinitelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::Media;bool isLikelyStreamingMedia = requester == WebCore::ResourceRequest::Requester::XHR && isMediaMIMEType(response.mimeType);if (isLikelyStreamingMedia || isDefinitelyStreamingMedia)return StoreDecision::NoDueToStreamingMedia;
return StoreDecision::Yes;}
4.4 HTTP 读取缓存校验 本部分主要根据请求来判断是否去缓存中读取缓存。主要判断 scheme、method 以及资源的缓存策略
static RetrieveDecision makeRetrieveDecision(const WebCore::ResourceRequest& request) {
ASSERT(request.cachePolicy != WebCore::ResourceRequestCachePolicy::DoNotUseAnyCache);
// FIXME: Support HEAD requests.
if (request.httpMethod != "GET")
return RetrieveDecision::NoDueToHTTPMethod;
if (request.cachePolicy == WebCore::ResourceRequestCachePolicy::ReloadIgnoringCacheData && !request.isConditional)
return RetrieveDecision::NoDueToReloadIgnoringCache;
return RetrieveDecision::Yes; }
4.5 HTTP 使用缓存校验
本部分主要根据请求和响应来判断缓存是否可以直接使用。主要根据缓存字段计算当前的资源是否过期。
// 校验变化的请求头 | verifyVaryingRequestHeaders if (!WebCore::verifyVaryingRequestHeaders(networkProcess.storageSession(sessionID), entry.varyingRequestHeaders, request)) return UseDecision::NoDueToVaryingHeaderMismatch;
// We never revalidate in the case of a history navigation. // 校验缓存是否过期 | cachePolicyAllowsExpired if (cachePolicyAllowsExpired(request.cachePolicy)) return UseDecision::Use;
// 验证请求是否过期 auto decision = responseNeedsRevalidation(*networkProcess.networkSession(sessionID), entry.response, request, entry.timeStamp); if (decision != UseDecision::Validate) return decision;
// 验证缓存有效字端(Etag等) | bool ResourceResponseBase::hasCacheValidatorFields if (!entry.response.hasCacheValidatorFields) return UseDecision::NoDueToMissingValidatorFields;
return entry.redirectRequest ? UseDecision::NoDueToExpiredRedirect : UseDecision::Validate; }
4.5.1HTTP 缓存新鲜度计算
本部分主要根据缓存字段计算当前的资源的新鲜度。
// Freshness Lifetime: // http://tools.ietf.org/html/rfc7234#section-4.2.1 auto maxAge = response.cacheControlMaxAge;if (maxAge) return *maxAge;
auto date = response.date; auto effectiveDate = date.valueOr(responseTime); if (auto expires = response.expires) return *expires - effectiveDate;
// Implicit lifetime. switch (response.httpStatusCode) { case 301: // Moved Permanently case 410: // Gone // These are semantically permanent and so get long implicit lifetime. return 24_h * 365; default: // Heuristic Freshness: // http://tools.ietf.org/html/rfc7234#section-4.2.2 if (auto lastModified = response.lastModified) return (effectiveDate - *lastModified) * 0.1; return 0_us; }}