背景
越来越多的App安装到用户的手机上,导致iPhone的磁盘不足。笔者就经常需要去删除一些缓存、卸载一些不用的App,来减少我们磁盘空间的占用,这个时候,就会去通用设置界面,去把一些磁盘占用大的App删除掉。来释放一些磁盘空间。各个App厂商都在研究如何减少磁盘缓存占用,降低App被卸载的风险。
WKWebView磁盘管理
本章仅讨论的范围是WKWebView的磁盘管理,如果需要了解WKWebView的其他技术点,不在本文章讨论范围。WKWebView从iOS9之后提供了一个新的类WKWebsiteDataStore来表示选中网站的各种类型的数据,包括
- Cookies
- Disk and Memory caches
- Persistent data such as WebSQL
- IndexedDB databases
- Local storage
对于Disk Cache,WKWebView内部有一套机制管理。通过阅读WebKit的源码可以发现:
void Storage::shrinkIfNeeded()
{
ASSERT(RunLoop::isMain());
// Avoid randomness caused by cache shrinks.
if (m_mode == Mode::AvoidRandomness)
return;
if (approximateSize() > m_capacity)
shrink();
}
可以看到,每次写入某个需要缓存的内容,都会判断是否需要清理内存,其中m_capacity的设置是根据当前手机的磁盘可用容量初始化的时候设置的
uint64_t calculateURLCacheDiskCapacity(CacheModel cacheModel, uint64_t diskFreeSize)
{
uint64_t urlCacheDiskCapacity;
switch (cacheModel) {
//...
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 ;
}
};
return urlCacheDiskCapacity;
}
可以看到 Webkit的磁盘缓存大小定在 100M ~ 1G之间。虽然内部有一个磁盘管理,但是对于大多数App来说,Webkit缓存超过300M也是不希望的。因此,自然想到是否可以通过某种方式来设置磁盘缓存的空间
WK_CLASS_AVAILABLE(macos(10.11), ios(9.0))
@interface WKWebsiteDataStore : NSObject <NSSecureCoding>
+ (WKWebsiteDataStore *)defaultDataStore;
+ (WKWebsiteDataStore *)nonPersistentDataStore;
- (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
@property (nonatomic, readonly, getter=isPersistent) BOOL persistent;
+ (NSSet<NSString *> *)allWebsiteDataTypes;
- (void)fetchDataRecordsOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(NSArray<WKWebsiteDataRecord *> *))completionHandler WK_SWIFT_ASYNC_NAME(dataRecords(ofTypes:));
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;
@property (nonatomic, readonly) WKHTTPCookieStore *httpCookieStore WK_API_AVAILABLE(macos(10.13), ios(11.0));
@end
通过查看WKWebsiteDataStore提供的接口,并没有发现有关于设置磁盘缓存最大容量的相关接口。看来WKWebView并不希望我们来自主控制Disk Cache的最大磁盘大小。
但是其中找到了如下2个接口,是用来移除某个数据类型的缓存大小。
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;
前问有提到过,WKWebView的几种数据类型,包括如下:
- Cookies
- Disk and Memory caches
- Persistent data such as WebSQL
- IndexedDB databases
- Local storage
目前Webkit占用的磁盘分为有几个目录,并且我们通过线上80、90分位拉取到的数据,发现占用最大的两个目录分别为:
-
IndexedDB databases --- appdata/Libraray/WebKit/WebsiteData/IndexedDB
-
Disk caches --- appdata/Libraray/Caches/WebKit/NetworkCache
如果通过 removeDataOfTypes:modifiedSince:completionHandler接口来删除,modifiedSince是一个从某个时间到现在时间的disk cache,大部分时候,我们想要删除的是那些之前的资源。
这个时候,我们通过下面一些方式来做一些磁盘清理逻辑
IndexedDB databases的删除
该部分的删除可以直接通过读取文件价的创建时间:appdata/Libraray/WebKit/WebsiteData/IndexedDB 例如该部分下面的数据为:https_google.game.hello,如果文件夹的创建或修改时间大于某个阈值,我们可以通过拿到https_google,再截取出域名google,然后通过fetchDataRecordsOfTypes:completionHandler获取所有的WKWebsiteDataTypeIndexedDBDatabases数据,根据每一个数据:WKWebsiteDataRecord的displayName判断是否包含域名来进行删除
NetworkCache的删除
WebKit的大部分缓存数据都是存储在NetworkCache里面,有效的管理NetworkCache,对于磁盘管理十分有帮助。
既然WebViedw提供了以下接口,我们就可以合理的使用来达到删除的目的
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;
笔者设计的方案是,对NetworkCache每次启动检测,由后台下发一个该目录的阈值,如果超过阈值,就会按照LRU的方式进行删除。
具体方式为:
- 首先获取appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/Records下的所有文件
- 读取下面所有文件的创建或修改时间,按修改时间排序
- 按照40%的删除策略,我们会对前40%的文件,修改文件的修改时间为未来3天
- 接着使用removeDataOfTypes:modifiedSince:completionHandler来删除未来2天的数据
这样,即使用了WKWebsiteDataStore的系统删除接口,防止因为多线程操作文件引起的文件读写IO崩溃,也对Webkit的NetworkCache进行了有效的管理
这里给大家留个问题,appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/下的目录包括了2个目录
-
Records
-
Blobs
为什么只删除一个Records,就可以了,而Blobs目录的清除工作,又谁来做?
我们依然回归到源码
void Storage::clear(String&& type, WallTime modifiedSinceTime, CompletionHandler<void()>&& completionHandler)
{
ASSERT(RunLoop::isMain());
LOG(NetworkCacheStorage, "(NetworkProcess) clearing cache");
if (m_recordFilter)
m_recordFilter->clear();
if (m_blobFilter)
m_blobFilter->clear();
m_approximateRecordsSize = 0;
ioQueue().dispatch([this, protectedThis = Ref { *this }, modifiedSinceTime, completionHandler = WTFMove(completionHandler), type = WTFMove(type).isolatedCopy()] () mutable {
auto recordsPath = this->recordsPathIsolatedCopy();
traverseRecordsFiles(recordsPath, type, [modifiedSinceTime](const String& fileName, const String& hashString, const String& type, bool isBlob, const String& recordDirectoryPath) {
auto filePath = FileSystem::pathByAppendingComponent(recordDirectoryPath, fileName);
if (modifiedSinceTime > -WallTime::infinity()) {
auto times = fileTimes(filePath);
if (times.modification < modifiedSinceTime)
return;
}
FileSystem::deleteFile(filePath);
});
deleteEmptyRecordsDirectories(recordsPath);
// This cleans unreferenced blobs.
m_blobStorage.synchronize();
RunLoop::main().dispatch(WTFMove(completionHandler));
});
}
从源码中可以看到,系统的clear接口 只会首先去删除appdata/Libraray/Caches/WebKit/NetworkCache/Version 16/Records下的ModifySince之后的文件,然后通过m_blobStorage.synchronize()去删除没有hard-link的文件Blobs的文件,从而Blobs目录也被管理起来了。
希望大家能够通过阅读Webkit的源码了解更多的Webkit知识,然后在公屏留言回复您的意见。
结语
Webkit的磁盘管理,只是我们优化磁盘空间占用的一个方面,还有很多磁盘管理的策略,会在接下来的文章和大家一起分享。
我们也开源了一个动画库:https://github.com/yylive/yyeva ,希望大家能够跳转改github和我们交流,顺便点个star感谢大家的支持
网友评论