WKWebview 秒開的實踐及踩坑之路
本文轉(zhuǎn)載自微信公眾號「網(wǎng)羅開發(fā)」,作者傷心的Easyman 。轉(zhuǎn)載本文請聯(lián)系網(wǎng)羅開發(fā)公眾號。
優(yōu)化背景
- 眾所周知,H5 的部分優(yōu)勢(開發(fā)快,迭代快,熱更新)是很明顯的,公司客戶端的部分業(yè)務(wù)都是由 H5 來實現(xiàn)的,網(wǎng)絡(luò)好的情況下體驗也是很不錯的
- 但是其實 H5 的體驗是比原生差的,這就需要想辦法如何提高 H5 加載速度,優(yōu)化體驗,首屏的加載速度還是很影響體驗的
加載速度
關(guān)于加載速度慢有很多文章都已經(jīng)詳細(xì)解釋了,h5在加載工作中做了很多事
初始化 webview -> 請求頁面 -> 下載數(shù)據(jù) -> 解析HTML -> 請求 js/css 資源 -> dom 渲染 -> 解析 JS 執(zhí)行 -> JS 請求數(shù)據(jù) -> 解析渲染 -> 下載渲染圖片
一般頁面在 dom 渲染后才能展示,可以發(fā)現(xiàn),H5 首屏渲染白屏問題的原因關(guān)鍵在于,如何優(yōu)化減少從請求下載頁面到渲染之間這段時間的耗時。
前后端優(yōu)化
這其中可做的優(yōu)化特別多,前后端能夠做的是:
- 降低請求量:減少 HTTP 請求數(shù), 合并資源,minify / gzip 壓縮,webP,lazyLoad。
- 因為手機(jī)瀏覽器同時響應(yīng)請求是 4 個,4 個的請求數(shù)也許不是特別靠譜,沒有查到出處,但是肯定是越少越好。
- HTTP 協(xié)議緩存請求,離線緩存 manifest,離線數(shù)據(jù)緩存 localStorage。
- 加快請求速度:預(yù)解析 DNS,減少域名數(shù),并行加載,CDN 分發(fā)。
- 渲染:JS/CSS 優(yōu)化,加載順序,服務(wù)端渲染模板直出。
一般情況下,只要對照這個列表,對比差異就基本能搞定絕大部分前端性能問題了。不過我們在里面仔細(xì)再分析下,對首屏啟動速度影響最大的就是網(wǎng)絡(luò)請求,包括請求 HTML、css、image 等靜態(tài)資源和展示數(shù)據(jù)的請求。所以客戶端內(nèi),優(yōu)化最關(guān)鍵的其實就是如何緩存這些網(wǎng)絡(luò)資源,也就是離線包緩存方案。
離線包方案的實踐
方案選型是兩種
- 基于 LocalWebServer 實現(xiàn) WKWebView 離線資源加載
- 使用 WKURLSchemeHandler 實現(xiàn) WKWebView 離線資源加載
LocalWebServer
基于 iOS 的 local web server,目前大致有以下幾種較為完善的框架:
- CocoaHttpServer (支持 iOS、macOS 及多種網(wǎng)絡(luò)場景)
- GCDWebServer (基于 iOS,不支持 https 及 webSocket)
- Telegraph (Swift 實現(xiàn),功能較上面兩類更完善)
當(dāng)時采用的是 GCDWebServer,在打開 APP 后直接啟動Webserver,H5 的鏈接直接替換成本地 localhost + 端口號鏈接的地址。
本來的方案是本地服務(wù)器和遠(yuǎn)端h5服務(wù)器同步下載資源,下載后客戶端請求本地服務(wù)器的路徑,如未找到相應(yīng)的資源再請求遠(yuǎn)端服務(wù)器的資源文件。
測試過程中碰到很多奇怪的問題(暫不一一舉例),也有提到以下問題并且時間緊急所以并未做進(jìn)一步的深入:
- 資源訪問權(quán)限安全問題
- APP 前后臺切換時,服務(wù)重啟性能耗時問題
- 服務(wù)運(yùn)行時,電量及 CPU 占有率問題
- 多線程及磁盤 IO 問題
WKURLSchemeHandler
關(guān)于離線包
前端項目的靜態(tài)資源直接打包成 zip 包,APP 在啟動時開始下載該包并解壓到本地。WKWebview 通過 WKURLSchemeHandler 攔截并加載本地資源文件。關(guān)于離線包的分發(fā),就是普通的 zip 離線包和一個版本控制的 json 文件,每次打離線包會修改 json 文件里的版本號,并附有離線包下載地址。此處可以優(yōu)化的更好,但暫時并不需要太復(fù)雜。
離線包的下載和解壓
只是簡單的下載并解壓到本地資源路徑,關(guān)于版本比對的代碼這里沒有展示出來,自行注意,避免每次都全量更新。
- /* 創(chuàng)建網(wǎng)絡(luò)下載對象 */
- AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
- /* 下載地址 */
- NSURL *url = [NSURL URLWithString:request.urlParameters.path];
- NSURLRequest *request = [NSURLRequest requestWithURL:url];
- /* 下載路徑 */
- //獲取Document文件
- NSString * docsdir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
- NSString * zipFilePath = [docsdir stringByAppendingPathComponent:@"zip"];//將需要創(chuàng)建的串拼接到后面
- NSString * H5FilePath = [docsdir stringByAppendingPathComponent:@"H5"];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- BOOL zipIsDir = NO;
- BOOL H5IsDir = NO;
- // fileExistsAtPath 判斷一個文件或目錄是否有效,isDirectory判斷是否一個目錄
- BOOL zipexisted = [fileManager fileExistsAtPath:zipFilePath isDirectory:&zipIsDir];
- BOOL H5Existed = [fileManager fileExistsAtPath:H5FilePath isDirectory:&H5IsDir];
- if ( !(zipIsDir == YES && zipexisted == YES) ) {//如果文件夾不存在
- [fileManager createDirectoryAtPath:zipFilePath withIntermediateDirectories:YES attributes:nil error:nil];
- }
- if (!(H5IsDir == YES && H5Existed == YES) ) {
- [fileManager createDirectoryAtPath:H5FilePath withIntermediateDirectories:YES attributes:nil error:nil];
- }
- //刪除
- // [[NSFileManager defaultManager] removeItemAtPath:zipFilePath error:nil];
- NSString *filePath = [zipFilePath stringByAppendingPathComponent:url.lastPathComponent];
- /* 開始請求下載 */
- NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
- NSLog(@"下載進(jìn)度:%.0f%", downloadProgress.fractionCompleted * 100);
- } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
- /* 設(shè)定下載到的位置 */
- return [NSURL fileURLWithPath:filePath];
- } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
- NSTimeInterval delta = CACurrentMediaTime() - self->start;
- NSLog(@"下載完成,耗時:%f",delta);
- // filePath就是你下載文件的位置,你可以解壓,也可以直接拿來使用
- NSString *imgFilePath = [filePath path];// 將NSURL轉(zhuǎn)成NSString
- NSString *zipPath = imgFilePath;
- //刪除
- // [[NSFileManager defaultManager] removeItemAtPath:H5FilePath error:nil];
- [fileManager createDirectoryAtPath:H5FilePath withIntermediateDirectories:YES attributes:nil error:nil];
- //解壓
- [SSZipArchive unzipFileAtPath:zipPath toDestination:H5FilePath];
- //清理緩存
- [DLCommenHelper clearWebCache];
- }];
- [downloadTask resume];
WKWebview 緩存池
美團(tuán)有篇文章提到,在使用 iOS 10 的模擬器測試 WKWebView 的加載速度,首次初始化的時間耗時有 700 多毫秒。其實本人用 iOS 13 的真機(jī),發(fā)現(xiàn)初始化的時間約在 200 毫秒左右甚至更短。雖然只占整個加載時間的特別小的一部分,但是本著能優(yōu)則優(yōu)的原則還是做了處理,也就是預(yù)加載 Webview。
- 新建了一個單例類 SDIWKWebViewPool,默認(rèn)緩存池里的數(shù)量是 10 個
- + (instancetype)sharedInstance {
- static dispatch_once_t onceToken;
- static SDIWKWebViewPool *instance = nil;
- dispatch_once(&onceToken,^{
- instance = [[super allocWithZone:NULL] init];
- });
- return instance;
- }
- + (id)allocWithZone:(struct _NSZone *)zone{
- return [self sharedInstance];
- }
- - (instancetype)init
- {
- self = [super init];
- if (self) {
- self.initialViewsMaxCount = 10;
- self.preloadedViews = [NSMutableArray arrayWithCapacity:self.initialViewsMaxCount];
- }
- return self;
- }
- 在合適的地方提前調(diào)用 //預(yù)加載wkwebview [[SDIWKWebViewPool sharedInstance] prepareWithCount:10];,自行選擇在 delegate 或主頁面初始化的時候調(diào)用。
- /**
- 預(yù)初始化若干WKWebView
- @param count 個數(shù)
- */
- - (void)prepareWithCount:(NSUInteger)count {
- NSTimeInterval start = CACurrentMediaTime();
- // Actually does nothing, only initialization must be called.
- while (self.preloadedViews.count < MIN(count,self.initialViewsMaxCount)) {
- id preloadedView = [self createPreloadedView];
- if (preloadedView) {
- [self.preloadedViews addObject:preloadedView];
- } else {
- break;
- }
- }
- NSTimeInterval delta = CACurrentMediaTime() - start;
- NSLog(@"=======初始化耗時:%f", delta);
- }
- /**
- 從池中獲取一個WKWebView
- @return WKWebView
- */
- - (WKWebView *)getWKWebViewFromPool {
- if (!self.preloadedViews.count) {
- NSLog(@"不夠啦!");
- return [self createPreloadedView];
- } else {
- id preloadedView = self.preloadedViews.firstObject;
- [self.preloadedViews removeObject:preloadedView];
- return preloadedView;
- }
- }
- 創(chuàng)建 webview 的方法如下,需要注意的是 kWKWebViewReuseScheme,WKWebView 需要注冊這個 scheme 才能實現(xiàn)攔截,這個是 WKWebview 攔截需要的準(zhǔn)備工作。
- SDICustomURLSchemeHandler 是我的自定義攔截類
關(guān)于這里的版本為什么設(shè)置成 iOS 12 以上,WKURLSchemeHandler 是蘋果 iOS 11 就已推出,但是有發(fā)現(xiàn)某款機(jī)型在 iOS 11.2 上攔截失效,導(dǎo)致產(chǎn)生 Webview 白屏。所以這里一刀切,直接 12 以上才處理。其實 iOS 12 一下的用戶量特別少,所以不需要太擔(dān)心。
- //scheme定義
- #define kWKWebViewReuseScheme @"kwebview"
- /**
- 創(chuàng)建一個WKWebView
- @return WKWebView
- */
- - (WKWebView *)createPreloadedView {
- WKUserContentController *userContentController = WKUserContentController.new;
- WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
- NSString *cookieSource = [NSString stringWithFormat:@"document.cookie = 'API_SESSION=%@';", [SAMKeychain usertoken]];
- WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:cookieSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
- [userContentController addUserScript:cookieScript];
- // 賦值userContentController
- configuration.userContentController = userContentController;
- configuration.preferences.javaScriptEnabled = YES;
- configuration.suppressesIncrementalRendering = YES; // 是否支持記憶讀取
- [configuration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];//支持跨域
- // WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
- // WKUserContentController *wkUController = [[WKUserContentController alloc] init];
- // wkWebConfig.userContentController = wkUController;
- if (@available(iOS 12.0, *)) {
- [configuration setURLSchemeHandler:[[SDICustomURLSchemeHandler alloc] init] forURLScheme:kWKWebViewReuseScheme];
- } else {
- // Fallback on earlier versions
- }
- WKWebView *wkWebView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:configuration];
- //根據(jù)自己的業(yè)務(wù)
- wkWebView.allowsBackForwardNavigationGestures = YES;
- return wkWebView;
- }
替換 url scheme
- if (@available(iOS 12.0, *)) {
- if([urlString hasPrefix:@"http"] && [urlString containsString:@"ui-h5"]){
- urlString = [urlString stringByReplacingOccurrencesOfString:@"https" withString:kWKWebViewReuseScheme];
- }
- }
這里是通過規(guī)則直接把 https 替換為 kWKWebViewReuseScheme,也就是替換 url scheme http(s) 為自定義協(xié)議,完成這一步后,攔截生效。
需要注意的有兩點:
- 前端這邊加載 js 等資源都是用相對路徑,前端的 ajax 請求,像 post 請求,scheme 使用 http(s) 不使用自定義協(xié)議,這樣native 不會攔截,完全交給 H5 與服務(wù)器交互,就不會發(fā)生發(fā)送 post 請求,body 丟失的情況。
- 在我的項目里,H5 對服務(wù)器的請求都是通過 native 端來轉(zhuǎn)發(fā)的,所以也不存在攔截 post 請求,body 丟失的情況。所以上面這樣的改動對 H5 端是無侵入式的,不需要修改業(yè)務(wù)代碼。
最最重要的自定義 SDICustomURLSchemeHandler 類
- - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
- API_AVAILABLE(ios(12.0)){
- dispatch_sync(self.serialQueue, ^{
- [_taskVaildDic setValue:@(YES) forKey:urlSchemeTask.description];
- });
- NSDictionary *headers = urlSchemeTask.request.allHTTPHeaderFields;
- NSString *accept = headers[@"Accept"];
- //當(dāng)前的requestUrl的scheme都是customScheme
- NSString *requestUrl = urlSchemeTask.request.URL.absoluteString;
- NSString *fileName = [[requestUrl componentsSeparatedByString:@"?"].firstObject componentsSeparatedByString:@"ui-h5/"].lastObject;
- NSString *replacedStr = [requestUrl stringByReplacingOccurrencesOfString:kWKWebViewReuseScheme withString:@"https"];
- self.replacedStr = replacedStr;
- //Intercept and load local resources.
- if ((accept.length >= @"text".length && [accept rangeOfString:@"text/html"].location != NSNotFound)) {//html 攔截
- [self loadLocalFile:fileName urlSchemeTask:urlSchemeTask];
- } else if ([self isMatchingRegularExpressionPattern:@"\\.(js|css)" text:requestUrl]) {//js、css
- [self loadLocalFile:fileName urlSchemeTask:urlSchemeTask];
- } else if (accept.length >= @"image".length && [accept rangeOfString:@"image"].location != NSNotFound) {//image
- NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:[NSURL URLWithString:replacedStr]];
- [[SDWebImageManager sharedManager].imageCache queryImageForKey:key options:SDWebImageRetryFailed context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
- if (image) {
- NSData *imgData = UIImageJPEGRepresentation(image, 1);
- NSString *mimeType = [self getMIMETypeWithCAPIAtFilePath:fileName] ?: @"image/jpeg";
- [self resendRequestWithUrlSchemeTask:urlSchemeTask mimeType:mimeType requestData:imgData];
- } else {
- [self loadLocalFile:fileName urlSchemeTask:urlSchemeTask];
- }
- }];
- } else {
- //return an empty json.
- NSData *data = [NSJSONSerialization dataWithJSONObject:@{ } options:NSJSONWritingPrettyPrinted error:nil];
- [self resendRequestWithUrlSchemeTask:urlSchemeTask mimeType:@"text/html" requestData:data];
- }
- }
- -(BOOL)isMatchingRegularExpressionPattern:(NSString *)pattern text:(NSString *)text{
- NSError *error = NULL;
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
- NSTextCheckingResult *result = [regex firstMatchInString:text options:0 range:NSMakeRange(0, [text length])];
- return MHObjectIsNil(result)?NO:YES;
- }
- 上面的代碼是攔截資源請求后的處理代碼。收到攔截請求后,先獲取本地資源包對應(yīng)的資源,轉(zhuǎn)換成 data 回傳給 webView 進(jìn)行渲染處理;若本地沒有,則 customScheme 替換成 https 的 url 重發(fā)請求通知 webview,這就是基本流程。
- 以下就是加載本地資源和重發(fā)請求的代碼
- //Load local resources, eg: html、js、css...
- - (void)loadLocalFile:(NSString *)fileName urlSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(11.0)){
- if(![self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] || !urlSchemeTask || fileName.length == 0){
- return;
- }
- NSString * docsdir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
- NSString * H5FilePath = [[docsdir stringByAppendingPathComponent:@"H5"] stringByAppendingPathComponent:@"h5"];
- //If the resource do not exist, re-send request by replacing to http(s).
- NSString *filePath = [H5FilePath stringByAppendingPathComponent:fileName];
- if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
- NSLog(@"開始重新發(fā)送網(wǎng)絡(luò)請求");
- if ([self.replacedStr hasPrefix:kWKWebViewReuseScheme]) {
- self.replacedStr =[self.replacedStr stringByReplacingOccurrencesOfString:kWKWebViewReuseScheme withString:@"https"];
- NSLog(@"請求地址:%@",self.replacedStr);
- }
- self.replacedStr = [NSString stringWithFormat:@"%@?%@",self.replacedStr,[SAMKeychain h5Version]?:@""];
- start = CACurrentMediaTime();//開始加載時間
- NSLog(@"web請求開始地址:%@",self.replacedStr);
- @weakify(self)
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.replacedStr]];
- NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
- NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
- @strongify(self)
- if([self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] == NO || !urlSchemeTask){
- return;
- }
- [urlSchemeTask didReceiveResponse:response];
- [urlSchemeTask didReceiveData:data];
- if (error) {
- [urlSchemeTask didFailWithError:error];
- } else {
- NSTimeInterval delta = CACurrentMediaTime() - self->start;
- NSLog(@"=======web請求結(jié)束地址%@:::%f", self.replacedStr, delta);
- [urlSchemeTask didFinish];
- }
- }];
- [dataTask resume];
- [session finishTasksAndInvalidate];
- } else {
- NSLog(@"filePath:%@",filePath);
- if(![self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] || !urlSchemeTask || fileName.length == 0){
- NSLog(@"return");
- return;
- }
- NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:nil];
- [self resendRequestWithUrlSchemeTask:urlSchemeTask mimeType:[self getMIMETypeWithCAPIAtFilePath:filePath] requestData:data];
- }
- }
- - (void)resendRequestWithUrlSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
- mimeType:(NSString *)mimeType
- requestData:(NSData *)requestData API_AVAILABLE(ios(11.0)) {
- if(![self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] || !urlSchemeTask|| !urlSchemeTask.request || !urlSchemeTask.request.URL){
- return;
- }
- NSString *mimeType_local = mimeType ? mimeType : @"text/html";
- NSData *data = requestData ? requestData : [NSData data];
- NSURLResponse *response = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL
- MIMEType:mimeType_local
- expectedContentLength:data.length
- textEncodingName:nil];
- [urlSchemeTask didReceiveResponse:response];
- [urlSchemeTask didReceiveData:data];
- [urlSchemeTask didFinish];
- }
整個過程中遇到的一些踩坑點
1. 'The task has already been stopped'崩潰問題
- _taskVaildDic 是一個 NSMutableDictionary,它里面存的是以當(dāng)前的 urlSchemeTask做 key,攔截開始時設(shè)置 YES,收到停止通知時設(shè)置 NO。這是由于在快速切換 webview 時,之前的 urlSchemeTask 已經(jīng)停止但是后面再次調(diào)用了它的方法就會產(chǎn)生該崩潰。
- 在實際使用過程中,用 bugly 監(jiān)控到還是會有該崩潰發(fā)生,只不過次數(shù)特別少,一天約四五條左右。還在尋找問題的原因中。
- - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
- API_AVAILABLE(ios(12.0)){
- dispatch_sync(self.serialQueue, ^{
- [_taskVaildDic setValue:@(YES) forKey:urlSchemeTask.description];
- });
- }
- - (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(12.0)){
- NSError *error = [NSError errorWithDomain:urlSchemeTask.request.URL.absoluteString code:0 userInfo:NULL];
- NSLog(@"weberror:%@",error);
- dispatch_sync(self.serialQueue, ^{
- [self->_taskVaildDic setValue:@(NO) forKey:urlSchemeTask.description];
- });
- }
2. WKWebview 的默認(rèn)緩存策略問題
之前未考慮到 WKWebview 的默認(rèn)緩存策略(WKWebView 默認(rèn)緩存策略完全遵循 HTTP 緩存協(xié)議)。
在 h5 打包上線并更新離線包后,H5 的資源文件修改是變更 md5 文件名的。由于緩存策略默認(rèn)時間是一個小時,會導(dǎo)致緩存的 url 加載不到修改后的 js,css 等文件(無論是本地離線包和遠(yuǎn)端服務(wù)器都已經(jīng)沒有這個 md5 文件)。
簡單的解決方案是通過資源鏈接加版本號后綴,每次更新資源的時候變更版本號,在上面的代碼中有做這部分處理。既保證了實時的更新,又保證了加載速度。
3. uni-app 圖片 CDN 問題
做完上述的離線包優(yōu)化后,發(fā)現(xiàn)新下載 APP 的情況,會偶發(fā)加載很慢問題。iOS 出現(xiàn),但是 android 并未出現(xiàn)。
H5 部分是用 uni-app 開發(fā)的,所以發(fā)現(xiàn)這個問題后由前端同事修復(fù)后恢復(fù)正常。
4. chunk-vendors.js 文件過大
這個問題也是抓包發(fā)現(xiàn)的,在未打開離線包緩存開關(guān)時,發(fā)現(xiàn)h5的加載速度過慢,發(fā)現(xiàn)加載的 chunk-vendors.js 文件過大約 1.7M。 stopURLSchemeTask 方法里會報 error 錯誤信息 Error Domain= 的錯誤信息。也由前端同事處理了這個問題。
最終效果
統(tǒng)計了 APP 在不開離線包方案時,webview 平均加載時長在 1.5-2 秒的范圍內(nèi)(這里是計算的 webview開始加載到導(dǎo)航完成的時間),在上述優(yōu)化完成后,打開的時長在 0.25-0.3 秒之間。
所以效果還是很顯著的,用戶的直觀感受就是接近于秒開的體驗。
總結(jié)
上面的優(yōu)化過程中踩了很多坑,但是也重新梳理了 Webview 的加載過程,默認(rèn)緩存策略機(jī)制等內(nèi)容。上面的方案肯定不是最優(yōu)的,只是一個快速達(dá)到 WKWebview 接近秒開效果的一個方案。
有什么更好的解決方案或者上述文中有不對的地方,希望大家指出,歡迎共同討論~