wk清空缓存

171 阅读9分钟
        //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/WebKitIndexedDB
LocalStorage
MediaKeys
ResourceLoadStatistics
CacheStorage
NetworkCache
Library/Caches/WebKitofflineWebApplicationCache
ServiceWorkers
前进后退缓存 – pageCache

在 WebKit 中,pageCache 其实就是对 WebBackForwardCache – 前进后退缓存的封装,本质上是浏览历史的一种记录,不属于上述标准缓存。前进后退缓存,将整个页面快照存入到内存中,下一次使用的时候,不用进行各类资源加载,甚至不用进行渲染工作。

通过源码查看,pageCache 大小会随着可使用内存大小动态变化:

手机可用内存a可缓存page页数
a >= 512M
512M > a >= 256M1
other0
/ 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 进程,如图所示。

image.png

如上图所示,一个页面对应一个WebContentProcess 进程】、
当页面销毁时,其对应的内存缓存也被销毁。

3.1 内存缓存大小

虽然 WebKit 进程独立与 app 进程,但内存占用过大依旧会影响到 
app 进程的性能,因此内存缓存根据手机当前缓存大小进行分配。

    
手机可用内存a页面内存分配
a >= 2G128M
2G > a >= 1.5G96M 
1.5G > a >= 1G64M
1G > a >= 0.5G32M
other16M

缓存大小计算策略源码如下

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 标准协议,
而是根据浏览器所加载的资源策略来进行的。例如:
  1. 前进后退历史中的页面的资源请求,可以直接读取内存缓存。

  2. 图片资源使用同一 url 加载的时候,后续的资源会 block 住, 等待首个图片资源的返回,直接使用缓存。

  3. 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 缓存处理流程图

image.png

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; }}

原文查看