精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

WKWebview 秒開的實(shí)踐及踩坑之路

開發(fā) 前端
一般情況下,只要對(duì)照這個(gè)列表,對(duì)比差異就基本能搞定絕大部分前端性能問(wèn)題了。不過(guò)我們?cè)诶锩孀屑?xì)再分析下,對(duì)首屏啟動(dòng)速度影響最大的就是網(wǎng)絡(luò)請(qǐng)求,包括請(qǐng)求 HTML、css、image 等靜態(tài)資源和展示數(shù)據(jù)的請(qǐng)求。

[[413803]]

本文轉(zhuǎn)載自微信公眾號(hào)「網(wǎng)羅開發(fā)」,作者傷心的Easyman 。轉(zhuǎn)載本文請(qǐng)聯(lián)系網(wǎng)羅開發(fā)公眾號(hào)。

優(yōu)化背景

  • 眾所周知,H5 的部分優(yōu)勢(shì)(開發(fā)快,迭代快,熱更新)是很明顯的,公司客戶端的部分業(yè)務(wù)都是由 H5 來(lái)實(shí)現(xiàn)的,網(wǎng)絡(luò)好的情況下體驗(yàn)也是很不錯(cuò)的
  • 但是其實(shí) H5 的體驗(yàn)是比原生差的,這就需要想辦法如何提高 H5 加載速度,優(yōu)化體驗(yàn),首屏的加載速度還是很影響體驗(yàn)的

加載速度

關(guān)于加載速度慢有很多文章都已經(jīng)詳細(xì)解釋了,h5在加載工作中做了很多事

初始化 webview -> 請(qǐng)求頁(yè)面 -> 下載數(shù)據(jù) -> 解析HTML -> 請(qǐng)求 js/css 資源 -> dom 渲染 -> 解析 JS 執(zhí)行 -> JS 請(qǐng)求數(shù)據(jù) -> 解析渲染 -> 下載渲染圖片

一般頁(yè)面在 dom 渲染后才能展示,可以發(fā)現(xiàn),H5 首屏渲染白屏問(wèn)題的原因關(guān)鍵在于,如何優(yōu)化減少?gòu)恼?qǐng)求下載頁(yè)面到渲染之間這段時(shí)間的耗時(shí)。

前后端優(yōu)化

這其中可做的優(yōu)化特別多,前后端能夠做的是:

  • 降低請(qǐng)求量:減少 HTTP 請(qǐng)求數(shù), 合并資源,minify / gzip 壓縮,webP,lazyLoad。
  • 因?yàn)槭謾C(jī)瀏覽器同時(shí)響應(yīng)請(qǐng)求是 4 個(gè),4 個(gè)的請(qǐng)求數(shù)也許不是特別靠譜,沒有查到出處,但是肯定是越少越好。
  • HTTP 協(xié)議緩存請(qǐng)求,離線緩存 manifest,離線數(shù)據(jù)緩存 localStorage。
  • 加快請(qǐng)求速度:預(yù)解析 DNS,減少域名數(shù),并行加載,CDN 分發(fā)。
  • 渲染:JS/CSS 優(yōu)化,加載順序,服務(wù)端渲染模板直出。

一般情況下,只要對(duì)照這個(gè)列表,對(duì)比差異就基本能搞定絕大部分前端性能問(wèn)題了。不過(guò)我們?cè)诶锩孀屑?xì)再分析下,對(duì)首屏啟動(dòng)速度影響最大的就是網(wǎng)絡(luò)請(qǐng)求,包括請(qǐng)求 HTML、css、image 等靜態(tài)資源和展示數(shù)據(jù)的請(qǐng)求。所以客戶端內(nèi),優(yōu)化最關(guān)鍵的其實(shí)就是如何緩存這些網(wǎng)絡(luò)資源,也就是離線包緩存方案。

離線包方案的實(shí)踐

方案選型是兩種

  • 基于 LocalWebServer 實(shí)現(xiàn) WKWebView 離線資源加載
  • 使用 WKURLSchemeHandler 實(shí)現(xiàn) WKWebView 離線資源加載

LocalWebServer

基于 iOS 的 local web server,目前大致有以下幾種較為完善的框架:

  • CocoaHttpServer (支持 iOS、macOS 及多種網(wǎng)絡(luò)場(chǎng)景)
  • GCDWebServer (基于 iOS,不支持 https 及 webSocket)
  • Telegraph (Swift 實(shí)現(xiàn),功能較上面兩類更完善)

當(dāng)時(shí)采用的是 GCDWebServer,在打開 APP 后直接啟動(dòng)Webserver,H5 的鏈接直接替換成本地 localhost + 端口號(hào)鏈接的地址。

本來(lái)的方案是本地服務(wù)器和遠(yuǎn)端h5服務(wù)器同步下載資源,下載后客戶端請(qǐng)求本地服務(wù)器的路徑,如未找到相應(yīng)的資源再請(qǐng)求遠(yuǎn)端服務(wù)器的資源文件。

測(cè)試過(guò)程中碰到很多奇怪的問(wèn)題(暫不一一舉例),也有提到以下問(wèn)題并且時(shí)間緊急所以并未做進(jìn)一步的深入:

  • 資源訪問(wèn)權(quán)限安全問(wèn)題
  • APP 前后臺(tái)切換時(shí),服務(wù)重啟性能耗時(shí)問(wèn)題
  • 服務(wù)運(yùn)行時(shí),電量及 CPU 占有率問(wèn)題
  • 多線程及磁盤 IO 問(wèn)題

WKURLSchemeHandler

關(guān)于離線包

前端項(xiàng)目的靜態(tài)資源直接打包成 zip 包,APP 在啟動(dòng)時(shí)開始下載該包并解壓到本地。WKWebview 通過(guò) WKURLSchemeHandler 攔截并加載本地資源文件。關(guān)于離線包的分發(fā),就是普通的 zip 離線包和一個(gè)版本控制的 json 文件,每次打離線包會(huì)修改 json 文件里的版本號(hào),并附有離線包下載地址。此處可以優(yōu)化的更好,但暫時(shí)并不需要太復(fù)雜。

離線包的下載和解壓

只是簡(jiǎn)單的下載并解壓到本地資源路徑,關(guān)于版本比對(duì)的代碼這里沒有展示出來(lái),自行注意,避免每次都全量更新。

  1. /* 創(chuàng)建網(wǎng)絡(luò)下載對(duì)象 */ 
  2. AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; 
  3. /* 下載地址 */ 
  4. NSURL *url = [NSURL URLWithString:request.urlParameters.path]; 
  5. NSURLRequest *request = [NSURLRequest requestWithURL:url]; 
  6. /* 下載路徑 */ 
  7. //獲取Document文件 
  8. NSString * docsdir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 
  9. NSString * zipFilePath = [docsdir stringByAppendingPathComponent:@"zip"];//將需要?jiǎng)?chuàng)建的串拼接到后面 
  10. NSString * H5FilePath = [docsdir stringByAppendingPathComponent:@"H5"]; 
  11. NSFileManager *fileManager = [NSFileManager defaultManager]; 
  12. BOOL zipIsDir = NO
  13. BOOL H5IsDir = NO
  14. // fileExistsAtPath 判斷一個(gè)文件或目錄是否有效,isDirectory判斷是否一個(gè)目錄 
  15. BOOL zipexisted = [fileManager fileExistsAtPath:zipFilePath isDirectory:&zipIsDir]; 
  16. BOOL H5Existed = [fileManager fileExistsAtPath:H5FilePath isDirectory:&H5IsDir]; 
  17. if ( !(zipIsDir == YES && zipexisted == YES) ) {//如果文件夾不存在 
  18.     [fileManager createDirectoryAtPath:zipFilePath withIntermediateDirectories:YES attributes:nil error:nil]; 
  19. if (!(H5IsDir == YES && H5Existed == YES) ) { 
  20.     [fileManager createDirectoryAtPath:H5FilePath withIntermediateDirectories:YES attributes:nil error:nil]; 
  21. //刪除 
  22. //        [[NSFileManager defaultManager] removeItemAtPath:zipFilePath error:nil]; 
  23. NSString *filePath = [zipFilePath stringByAppendingPathComponent:url.lastPathComponent]; 
  24. /* 開始請(qǐng)求下載 */ 
  25. NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) { 
  26.     NSLog(@"下載進(jìn)度:%.0f%", downloadProgress.fractionCompleted * 100); 
  27. } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { 
  28.     /* 設(shè)定下載到的位置 */ 
  29.     return [NSURL fileURLWithPath:filePath]; 
  30. } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) { 
  31.     NSTimeInterval delta = CACurrentMediaTime() - self->start; 
  32.      NSLog(@"下載完成,耗時(shí):%f",delta); 
  33.     // filePath就是你下載文件的位置,你可以解壓,也可以直接拿來(lái)使用 
  34.     NSString *imgFilePath = [filePath path];// 將NSURL轉(zhuǎn)成NSString 
  35.     NSString *zipPath = imgFilePath; 
  36.     //刪除  
  37. //            [[NSFileManager defaultManager] removeItemAtPath:H5FilePath error:nil]; 
  38.     [fileManager createDirectoryAtPath:H5FilePath withIntermediateDirectories:YES attributes:nil error:nil]; 
  39.     //解壓 
  40.     [SSZipArchive unzipFileAtPath:zipPath toDestination:H5FilePath]; 
  41.     //清理緩存 
  42.     [DLCommenHelper clearWebCache]; 
  43. }]; 
  44. [downloadTask resume]; 

WKWebview 緩存池

美團(tuán)有篇文章提到,在使用 iOS 10 的模擬器測(cè)試 WKWebView 的加載速度,首次初始化的時(shí)間耗時(shí)有 700 多毫秒。其實(shí)本人用 iOS 13 的真機(jī),發(fā)現(xiàn)初始化的時(shí)間約在 200 毫秒左右甚至更短。雖然只占整個(gè)加載時(shí)間的特別小的一部分,但是本著能優(yōu)則優(yōu)的原則還是做了處理,也就是預(yù)加載 Webview。

  • 新建了一個(gè)單例類 SDIWKWebViewPool,默認(rèn)緩存池里的數(shù)量是 10 個(gè)
  1. + (instancetype)sharedInstance { 
  2.     static dispatch_once_t onceToken; 
  3.     static SDIWKWebViewPool *instance = nil; 
  4.     dispatch_once(&onceToken,^{ 
  5.         instance = [[super allocWithZone:NULL] init]; 
  6.     }); 
  7.     return instance; 
  8.   
  9. + (id)allocWithZone:(struct _NSZone *)zone{ 
  10.     return [self sharedInstance]; 
  11.   
  12. - (instancetype)init 
  13.     self = [super init]; 
  14.     if (self) { 
  15.         self.initialViewsMaxCount = 10; 
  16.         self.preloadedViews = [NSMutableArray arrayWithCapacity:self.initialViewsMaxCount]; 
  17.     } 
  18.     return self; 
  • 在合適的地方提前調(diào)用 //預(yù)加載wkwebview [[SDIWKWebViewPool sharedInstance] prepareWithCount:10];,自行選擇在 delegate 或主頁(yè)面初始化的時(shí)候調(diào)用。
  1. /** 
  2.  預(yù)初始化若干WKWebView 
  3.   
  4.  @param count 個(gè)數(shù) 
  5.  */ 
  6. - (void)prepareWithCount:(NSUInteger)count { 
  7.     NSTimeInterval start = CACurrentMediaTime(); 
  8.     // Actually does nothing, only initialization must be called. 
  9.     while (self.preloadedViews.count < MIN(count,self.initialViewsMaxCount)) { 
  10.         id preloadedView = [self createPreloadedView]; 
  11.         if (preloadedView) { 
  12.             [self.preloadedViews addObject:preloadedView]; 
  13.         } else { 
  14.             break; 
  15.         } 
  16.     } 
  17.     NSTimeInterval delta = CACurrentMediaTime() - start; 
  18.     NSLog(@"=======初始化耗時(shí):%f",  delta); 
  19.   
  20. /** 
  21.  從池中獲取一個(gè)WKWebView 
  22.  @return WKWebView 
  23.  */ 
  24. - (WKWebView *)getWKWebViewFromPool { 
  25.     if (!self.preloadedViews.count) { 
  26.         NSLog(@"不夠啦!"); 
  27.         return [self createPreloadedView]; 
  28.     } else { 
  29.         id preloadedView = self.preloadedViews.firstObject; 
  30.         [self.preloadedViews removeObject:preloadedView]; 
  31.         return preloadedView; 
  32.     } 
  • 創(chuàng)建 webview 的方法如下,需要注意的是 kWKWebViewReuseScheme,WKWebView 需要注冊(cè)這個(gè) scheme 才能實(shí)現(xiàn)攔截,這個(gè)是 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 以上才處理。其實(shí) iOS 12 一下的用戶量特別少,所以不需要太擔(dān)心。

  1. //scheme定義 
  2. #define kWKWebViewReuseScheme    @"kwebview" 
  3.  
  4. /** 
  5.  創(chuàng)建一個(gè)WKWebView 
  6.  @return WKWebView 
  7.  */ 
  8. - (WKWebView *)createPreloadedView { 
  9.       WKUserContentController *userContentController = WKUserContentController.new; 
  10.      WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init]; 
  11.       NSString *cookieSource = [NSString stringWithFormat:@"document.cookie = 'API_SESSION=%@';",  [SAMKeychain usertoken]]; 
  12.       WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:cookieSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; 
  13.       [userContentController addUserScript:cookieScript]; 
  14.      // 賦值userContentController 
  15.      configuration.userContentController = userContentController; 
  16.      configuration.preferences.javaScriptEnabled = YES; 
  17.      configuration.suppressesIncrementalRendering = YES; // 是否支持記憶讀取 
  18.      [configuration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];//支持跨域 
  19. //    WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init]; 
  20. //    WKUserContentController *wkUController = [[WKUserContentController alloc] init]; 
  21. //    wkWebConfig.userContentController = wkUController; 
  22.  
  23.     if (@available(iOS 12.0, *)) { 
  24.         [configuration setURLSchemeHandler:[[SDICustomURLSchemeHandler alloc] init] forURLScheme:kWKWebViewReuseScheme]; 
  25.     } else { 
  26.         // Fallback on earlier versions 
  27.     } 
  28.     WKWebView *wkWebView = [[WKWebView alloc]initWithFrame:CGRectZero configuration:configuration]; 
  29.     //根據(jù)自己的業(yè)務(wù) 
  30.     wkWebView.allowsBackForwardNavigationGestures = YES; 
  31.     return wkWebView; 

替換 url scheme

  1. if (@available(iOS 12.0, *)) { 
  2.     if([urlString hasPrefix:@"http"] && [urlString containsString:@"ui-h5"]){ 
  3.         urlString = [urlString stringByReplacingOccurrencesOfString:@"https" withString:kWKWebViewReuseScheme]; 
  4.     } 

這里是通過(guò)規(guī)則直接把 https 替換為 kWKWebViewReuseScheme,也就是替換 url scheme http(s) 為自定義協(xié)議,完成這一步后,攔截生效。

需要注意的有兩點(diǎn):

  • 前端這邊加載 js 等資源都是用相對(duì)路徑,前端的 ajax 請(qǐng)求,像 post 請(qǐng)求,scheme 使用 http(s) 不使用自定義協(xié)議,這樣native 不會(huì)攔截,完全交給 H5 與服務(wù)器交互,就不會(huì)發(fā)生發(fā)送 post 請(qǐng)求,body 丟失的情況。
  • 在我的項(xiàng)目里,H5 對(duì)服務(wù)器的請(qǐng)求都是通過(guò) native 端來(lái)轉(zhuǎn)發(fā)的,所以也不存在攔截 post 請(qǐng)求,body 丟失的情況。所以上面這樣的改動(dòng)對(duì) H5 端是無(wú)侵入式的,不需要修改業(yè)務(wù)代碼。

最最重要的自定義 SDICustomURLSchemeHandler 類

  1. - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask 
  2. API_AVAILABLE(ios(12.0)){ 
  3.     dispatch_sync(self.serialQueue, ^{ 
  4.         [_taskVaildDic setValue:@(YES) forKey:urlSchemeTask.description]; 
  5.     }); 
  6.      
  7.     NSDictionary *headers = urlSchemeTask.request.allHTTPHeaderFields; 
  8.     NSString *accept = headers[@"Accept"]; 
  9.      
  10.     //當(dāng)前的requestUrl的scheme都是customScheme 
  11.     NSString *requestUrl = urlSchemeTask.request.URL.absoluteString; 
  12.     NSString *fileName = [[requestUrl componentsSeparatedByString:@"?"].firstObject componentsSeparatedByString:@"ui-h5/"].lastObject; 
  13.     NSString *replacedStr = [requestUrl stringByReplacingOccurrencesOfString:kWKWebViewReuseScheme withString:@"https"]; 
  14.     self.replacedStr = replacedStr; 
  15.     //Intercept and load local resources. 
  16.     if ((accept.length >= @"text".length && [accept rangeOfString:@"text/html"].location != NSNotFound)) {//html 攔截 
  17.         [self loadLocalFile:fileName urlSchemeTask:urlSchemeTask]; 
  18.     } else if ([self isMatchingRegularExpressionPattern:@"\\.(js|css)" text:requestUrl]) {//js、css 
  19.         [self loadLocalFile:fileName urlSchemeTask:urlSchemeTask]; 
  20.     } else if (accept.length >= @"image".length && [accept rangeOfString:@"image"].location != NSNotFound) {//image 
  21.       NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:[NSURL URLWithString:replacedStr]]; 
  22.         [[SDWebImageManager sharedManager].imageCache queryImageForKey:key options:SDWebImageRetryFailed context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) { 
  23.             if (image) { 
  24.                 NSData *imgData = UIImageJPEGRepresentation(image, 1); 
  25.                 NSString *mimeType = [self getMIMETypeWithCAPIAtFilePath:fileName] ?: @"image/jpeg"
  26.                 [self resendRequestWithUrlSchemeTask:urlSchemeTask mimeType:mimeType requestData:imgData]; 
  27.             } else { 
  28.                 [self loadLocalFile:fileName urlSchemeTask:urlSchemeTask]; 
  29.             } 
  30.         }]; 
  31.     } else { 
  32.         //return an empty json. 
  33.         NSData *data = [NSJSONSerialization dataWithJSONObject:@{ } options:NSJSONWritingPrettyPrinted error:nil]; 
  34.         [self resendRequestWithUrlSchemeTask:urlSchemeTask mimeType:@"text/html" requestData:data]; 
  35.     } 
  36.  
  37. -(BOOL)isMatchingRegularExpressionPattern:(NSString *)pattern text:(NSString *)text{ 
  38.     NSError *error = NULL
  39.     NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error]; 
  40.     NSTextCheckingResult *result = [regex firstMatchInString:text options:0 range:NSMakeRange(0, [text length])]; 
  41.     return MHObjectIsNil(result)?NO:YES; 
  • 上面的代碼是攔截資源請(qǐng)求后的處理代碼。收到攔截請(qǐng)求后,先獲取本地資源包對(duì)應(yīng)的資源,轉(zhuǎn)換成 data 回傳給 webView 進(jìn)行渲染處理;若本地沒有,則 customScheme 替換成 https 的 url 重發(fā)請(qǐng)求通知 webview,這就是基本流程。
  • 以下就是加載本地資源和重發(fā)請(qǐng)求的代碼
  1.   //Load local resources, eg: html、js、css... 
  2. - (void)loadLocalFile:(NSString *)fileName urlSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(11.0)){ 
  3.     if(![self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] || !urlSchemeTask || fileName.length == 0){ 
  4.                    return
  5.     } 
  6.      
  7.     NSString * docsdir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; 
  8.     NSString * H5FilePath = [[docsdir stringByAppendingPathComponent:@"H5"] stringByAppendingPathComponent:@"h5"]; 
  9.     //If the resource do not exist, re-send request by replacing to http(s). 
  10.     NSString *filePath = [H5FilePath stringByAppendingPathComponent:fileName]; 
  11.      
  12.     if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) { 
  13.         NSLog(@"開始重新發(fā)送網(wǎng)絡(luò)請(qǐng)求"); 
  14.         if ([self.replacedStr hasPrefix:kWKWebViewReuseScheme]) { 
  15.  
  16.             self.replacedStr =[self.replacedStr stringByReplacingOccurrencesOfString:kWKWebViewReuseScheme withString:@"https"]; 
  17.                      
  18.             NSLog(@"請(qǐng)求地址:%@",self.replacedStr); 
  19.              
  20.         } 
  21.      
  22.         self.replacedStr = [NSString stringWithFormat:@"%@?%@",self.replacedStr,[SAMKeychain h5Version]?:@""]; 
  23.         start = CACurrentMediaTime();//開始加載時(shí)間 
  24.         NSLog(@"web請(qǐng)求開始地址:%@",self.replacedStr); 
  25.          
  26.         @weakify(self) 
  27.         NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.replacedStr]]; 
  28.         NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; 
  29.         NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 
  30.             @strongify(self) 
  31.             if([self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] == NO || !urlSchemeTask){ 
  32.                 return
  33.             } 
  34.             [urlSchemeTask didReceiveResponse:response]; 
  35.             [urlSchemeTask didReceiveData:data]; 
  36.             if (error) { 
  37.                 [urlSchemeTask didFailWithError:error]; 
  38.             } else { 
  39.                 NSTimeInterval delta = CACurrentMediaTime() - self->start; 
  40.                 NSLog(@"=======web請(qǐng)求結(jié)束地址%@:::%f", self.replacedStr, delta); 
  41.                 [urlSchemeTask didFinish]; 
  42.             } 
  43.         }]; 
  44.         [dataTask resume]; 
  45.         [session finishTasksAndInvalidate]; 
  46.     } else { 
  47.         NSLog(@"filePath:%@",filePath); 
  48.         if(![self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] || !urlSchemeTask || fileName.length == 0){ 
  49.             NSLog(@"return"); 
  50.             return
  51.         } 
  52.          
  53.         NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:nil]; 
  54.         [self resendRequestWithUrlSchemeTask:urlSchemeTask mimeType:[self getMIMETypeWithCAPIAtFilePath:filePath] requestData:data]; 
  55.     } 
  56.  
  57. - (void)resendRequestWithUrlSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask 
  58.                               mimeType:(NSString *)mimeType 
  59.                            requestData:(NSData *)requestData  API_AVAILABLE(ios(11.0)) { 
  60.     if(![self->_taskVaildDic boolValueForKey:urlSchemeTask.description default:NO] || !urlSchemeTask|| !urlSchemeTask.request || !urlSchemeTask.request.URL){ 
  61.         return
  62.     } 
  63.  
  64.     NSString *mimeType_local = mimeType ? mimeType : @"text/html"
  65.     NSData *data = requestData ? requestData : [NSData data]; 
  66.     NSURLResponse *response = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL 
  67.                                                         MIMEType:mimeType_local 
  68.                                            expectedContentLength:data.length 
  69.                                                 textEncodingName:nil]; 
  70.     [urlSchemeTask didReceiveResponse:response]; 
  71.     [urlSchemeTask didReceiveData:data]; 
  72.     [urlSchemeTask didFinish]; 

整個(gè)過(guò)程中遇到的一些踩坑點(diǎn)

1. 'The task has already been stopped'崩潰問(wèn)題

  • _taskVaildDic 是一個(gè) NSMutableDictionary,它里面存的是以當(dāng)前的 urlSchemeTask做 key,攔截開始時(shí)設(shè)置 YES,收到停止通知時(shí)設(shè)置 NO。這是由于在快速切換 webview 時(shí),之前的 urlSchemeTask 已經(jīng)停止但是后面再次調(diào)用了它的方法就會(huì)產(chǎn)生該崩潰。
  • 在實(shí)際使用過(guò)程中,用 bugly 監(jiān)控到還是會(huì)有該崩潰發(fā)生,只不過(guò)次數(shù)特別少,一天約四五條左右。還在尋找問(wèn)題的原因中。
  1. - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask 
  2. API_AVAILABLE(ios(12.0)){ 
  3.     dispatch_sync(self.serialQueue, ^{ 
  4.         [_taskVaildDic setValue:@(YES) forKey:urlSchemeTask.description]; 
  5.     }); 
  6.  
  7. - (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask  API_AVAILABLE(ios(12.0)){ 
  8.     NSError *error = [NSError errorWithDomain:urlSchemeTask.request.URL.absoluteString code:0 userInfo:NULL]; 
  9.     NSLog(@"weberror:%@",error); 
  10.     dispatch_sync(self.serialQueue, ^{ 
  11.          [self->_taskVaildDic setValue:@(NO) forKey:urlSchemeTask.description]; 
  12.     }); 

2. WKWebview 的默認(rèn)緩存策略問(wèn)題

之前未考慮到 WKWebview 的默認(rèn)緩存策略(WKWebView 默認(rèn)緩存策略完全遵循 HTTP 緩存協(xié)議)。

在 h5 打包上線并更新離線包后,H5 的資源文件修改是變更 md5 文件名的。由于緩存策略默認(rèn)時(shí)間是一個(gè)小時(shí),會(huì)導(dǎo)致緩存的 url 加載不到修改后的 js,css 等文件(無(wú)論是本地離線包和遠(yuǎn)端服務(wù)器都已經(jīng)沒有這個(gè) md5 文件)。

簡(jiǎn)單的解決方案是通過(guò)資源鏈接加版本號(hào)后綴,每次更新資源的時(shí)候變更版本號(hào),在上面的代碼中有做這部分處理。既保證了實(shí)時(shí)的更新,又保證了加載速度。

3. uni-app 圖片 CDN 問(wèn)題

做完上述的離線包優(yōu)化后,發(fā)現(xiàn)新下載 APP 的情況,會(huì)偶發(fā)加載很慢問(wèn)題。iOS 出現(xiàn),但是 android 并未出現(xiàn)。

H5 部分是用 uni-app 開發(fā)的,所以發(fā)現(xiàn)這個(gè)問(wèn)題后由前端同事修復(fù)后恢復(fù)正常。

4. chunk-vendors.js 文件過(guò)大

這個(gè)問(wèn)題也是抓包發(fā)現(xiàn)的,在未打開離線包緩存開關(guān)時(shí),發(fā)現(xiàn)h5的加載速度過(guò)慢,發(fā)現(xiàn)加載的 chunk-vendors.js 文件過(guò)大約 1.7M。 stopURLSchemeTask 方法里會(huì)報(bào) error 錯(cuò)誤信息 Error Domain= 的錯(cuò)誤信息。也由前端同事處理了這個(gè)問(wèn)題。

最終效果

統(tǒng)計(jì)了 APP 在不開離線包方案時(shí),webview 平均加載時(shí)長(zhǎng)在 1.5-2 秒的范圍內(nèi)(這里是計(jì)算的 webview開始加載到導(dǎo)航完成的時(shí)間),在上述優(yōu)化完成后,打開的時(shí)長(zhǎng)在 0.25-0.3 秒之間。

所以效果還是很顯著的,用戶的直觀感受就是接近于秒開的體驗(yàn)。

總結(jié)

上面的優(yōu)化過(guò)程中踩了很多坑,但是也重新梳理了 Webview 的加載過(guò)程,默認(rèn)緩存策略機(jī)制等內(nèi)容。上面的方案肯定不是最優(yōu)的,只是一個(gè)快速達(dá)到 WKWebview 接近秒開效果的一個(gè)方案。 

有什么更好的解決方案或者上述文中有不對(duì)的地方,希望大家指出,歡迎共同討論~

 

責(zé)任編輯:武曉燕 來(lái)源: 網(wǎng)羅開發(fā)
相關(guān)推薦

2018-12-18 14:13:30

SDKAndroid開發(fā)

2019-04-18 14:06:35

MySQL分庫(kù)分表數(shù)據(jù)庫(kù)

2024-04-09 09:29:22

NginxOSS資源

2017-12-05 12:44:57

Android沉浸式狀態(tài)欄APP

2024-03-13 13:10:48

JavaInteger緩存

2018-07-13 09:38:54

2023-02-20 08:11:04

2023-04-26 11:29:58

Jenkins版本Java 11

2020-08-20 10:10:43

Prometheus架構(gòu)監(jiān)控

2024-04-10 08:39:56

BigDecimal浮點(diǎn)數(shù)二進(jìn)制

2023-01-18 23:20:25

編程開發(fā)

2024-04-01 08:05:27

Go開發(fā)Java

2017-07-17 15:46:20

Oracle并行機(jī)制

2020-09-15 08:46:26

Kubernetes探針服務(wù)端

2017-06-16 14:35:09

FM 測(cè)試Docker實(shí)踐

2021-12-28 08:17:41

循環(huán) forgo

2025-10-27 01:11:00

2024-11-26 08:20:53

程序數(shù)據(jù)歸檔庫(kù)

2018-01-10 13:40:03

數(shù)據(jù)庫(kù)MySQL表設(shè)計(jì)

2017-05-05 08:12:51

Spark共享變量
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

你懂的国产精品永久在线| 黄色日韩网站| 91丝袜呻吟高潮美腿白嫩在线观看| 97热精品视频官网| 丰腴饱满的极品熟妇| 欧美日韩视频免费观看| 中文字幕色av一区二区三区| 国产成人精品自拍| 久久精品视频5| 91九色精品| 日韩www在线| 污污的网站免费| 僵尸再翻生在线观看| 国产欧美日韩在线看| 91pron在线| 日日夜夜狠狠操| 欧美成人一区二免费视频软件| 91精品国产aⅴ一区二区| 国产a级片免费看| 亚洲 欧美 精品| 国产一区二区三区在线观看免费 | 日本片在线观看| 久久久不卡网国产精品一区| 99视频在线播放| 中文字幕二区三区| 国产精品视频| 欧美黄色成人网| 肉色超薄丝袜脚交69xx图片 | 亚洲国产欧美久久| 激情文学亚洲色图| 欧美三区四区| 精品国产乱码久久久久久虫虫漫画| 自拍偷拍亚洲色图欧美| 九色视频在线观看免费播放| 丁香婷婷综合网| 成人性生交大片免费观看嘿嘿视频| 日本免费精品视频| 国产亚洲永久域名| 久久久久久高潮国产精品视| 国产精品免费人成网站酒店| 成人网18免费网站| 亚洲欧美国产精品久久久久久久| 西西大胆午夜视频| 精品少妇一区| 精品日韩在线观看| gogo亚洲国模私拍人体| 麻豆国产精品| 日韩午夜av电影| 黑人无套内谢中国美女| 国产精品igao视频网网址不卡日韩| 在线观看免费一区| 毛葺葺老太做受视频| 亚洲欧美韩国| 日韩欧美亚洲国产一区| 免费看又黄又无码的网站| 性欧美ⅴideo另类hd| 亚洲人成在线播放网站岛国 | 精品人妻伦一二三区久| 欧美三级一区| 欧美videossexotv100| 中文字幕无码毛片免费看| 久久中文字幕一区二区| 91精品福利在线一区二区三区| 99精品视频国产| 九九九九九九精品任你躁| 欧美一区二区在线免费播放| 日本黄色www| 国产精品qvod| 亚洲欧美福利视频| 大吊一区二区三区| 91精品天堂福利在线观看| 成年无码av片在线| 久久精品欧美一区二区| 国产精品久久久免费| 国产91精品网站| 在线免费观看一级片| 韩国精品久久久| 98国产高清一区| 香港一级纯黄大片| 国产精品午夜在线观看| 一级特黄妇女高潮| 2020国产在线| 欧美少妇xxx| 中文字幕在线视频一区二区三区| eeuss国产一区二区三区四区| 日韩成人网免费视频| 亚洲精品视频网址| 亚洲激情中文| 91产国在线观看动作片喷水| 亚洲天堂aaa| 成人高清免费观看| 日本在线观看一区| 国产一区久久精品| 欧美日韩午夜视频在线观看| 99热手机在线| 精品资源在线| 色爱av美腿丝袜综合粉嫩av| 日韩精品――中文字幕| 久久国产欧美日韩精品| 国产亚洲欧美一区二区| 中文字幕在线免费| 激情久久av一区av二区av三区| 污网站免费在线| 福利电影一区| 久久久999国产| 区一区二在线观看| 成人永久免费视频| 亚洲人成77777| 欧美伦理91| 日韩精品最新网址| 一级黄色片网址| 99在线精品视频在线观看| 91精品免费视频| 国产三级在线| 婷婷国产v国产偷v亚洲高清| 欧美人与性动交α欧美精品| 欧美日韩一二三四| 欧美性受xxxx黑人猛交| 午夜精品久久久久久久99热黄桃| 国产蜜臀97一区二区三区| 国产极品尤物在线| 麻豆国产精品| 久久精彩免费视频| www.久久网| 久久综合狠狠综合久久综合88| 精品人妻人人做人人爽| 精品福利在线| 中文字幕日韩欧美在线视频| 天天射天天干天天| 2021国产精品久久精品| 久久久久久久中文| 91国内精品| 插插插亚洲综合网| 国产又黄又大又粗的视频| 国产日本欧美一区二区| 国产一区亚洲二区三区| 牛牛精品成人免费视频| 欧美精品www| 亚洲av无码国产精品久久不卡| 中文字幕佐山爱一区二区免费| 男操女免费网站| 欧美在线观看视频一区| 国产精品久久91| 免费在线黄色电影| 91激情在线视频| 久久美女免费视频| 日韩精品一二三区| 日本一区美女| 国产在线|日韩| 中文字幕久久久av一区| 亚洲熟妇无码久久精品| 国产精品欧美精品| 不卡的av中文字幕| 图片小说视频色综合| 91精品中文在线| 18+激情视频在线| 日韩精品一区二区三区中文不卡| 久久久久久免费观看| 国v精品久久久网| 六月婷婷在线视频| 日韩三级毛片| 国产精品成人一区二区三区吃奶| 粉嫩一区二区三区国产精品| 欧美日韩国产一级| 亚洲综合视频网站| 国产成人高清在线| 日韩中文字幕在线视频观看 | 国产偷亚洲偷欧美偷精品| 狠狠人妻久久久久久综合| 久久久久国产精品免费免费搜索| 九色porny91| 亚洲欧美日韩高清在线| 国产成人精品一区二区三区福利| 狠狠躁少妇一区二区三区| 亚洲欧美在线看| 91精品中文字幕| 亚洲一区影音先锋| 无码熟妇人妻av| 免费高清在线一区| 国产青草视频在线观看| 欧美1区二区| 国产精品免费久久久久影院| 最新超碰在线| 精品视频偷偷看在线观看| 精品乱码一区内射人妻无码| 一区二区三区欧美日| 在线免费观看日韩av| 国产一区视频在线看| 少妇高潮毛片色欲ava片| 成人羞羞在线观看网站| 97人人澡人人爽| 国产精品毛片久久久久久久久久99999999| 久久精品99国产精品酒店日本| 国精品人妻无码一区二区三区喝尿| 欧美日韩亚洲天堂| 国产老头老太做爰视频| 久久品道一品道久久精品| 色综合五月婷婷| 久久久久久自在自线| 黄色影视在线观看| 欧美伦理影院| 国产在线视频欧美一区二区三区| 国内精品伊人| 18性欧美xxxⅹ性满足| 黄色av免费在线| 亚洲欧洲在线视频| 亚洲va欧美va| 欧美日韩国产在线播放网站| 久久国产精品系列| 亚洲免费大片在线观看| 91激情视频在线观看| av中文字幕亚洲| 色综合久久久无码中文字幕波多| 免费久久99精品国产| 久在线观看视频| 狠久久av成人天堂| av电影一区二区三区| 国产一区二区电影在线观看| 国产伦精品一区二区三区照片| 精品午夜视频| 国产精品女人网站| 345成人影院| 性日韩欧美在线视频| 国产原厂视频在线观看| 中文字幕av一区中文字幕天堂| 亚洲欧洲视频在线观看| 精品国产乱码久久久久久1区2区 | 正在播放一区| 国产日韩视频在线| 免费看成人av| 风间由美中文字幕在线看视频国产欧美 | 91麻豆免费视频网站| 国产精品麻豆视频| 非洲一级黄色片| 91免费在线视频观看| 东京热av一区| 国产91精品欧美| 亚洲国产日韩在线一区| 国产一区二区三区四区五区美女| 一区二区三区欧美精品| 青青草一区二区三区| 久久久久久久久久久久久国产精品| 影音先锋日韩资源| 福利在线一区二区| 欧美成人嫩草网站| 777久久精品一区二区三区无码| 中文字幕一区二区三区在线视频 | 国产黄色在线观看| 久久久黄色av| 性欧美videos高清hd4k| 欧美激情视频在线观看| 肉肉视频在线观看| 欧美大片大片在线播放| 色爱综合区网| 97在线视频国产| 澳门成人av网| 国产精品mp4| 激情小说亚洲| 91精品国产一区二区三区动漫| 999久久精品| 狠狠色噜噜狠狠色综合久| 日韩丝袜视频| 日韩一区二区电影在线观看| 成人免费电影网址| 国产在线拍揄自揄拍无码| 国户精品久久久久久久久久久不卡| 妞干网在线观看视频| 亚洲一区国产| www.精品在线| 国产精品亚洲综合一区在线观看| www.555国产精品免费| 91丨九色丨尤物| 一级二级黄色片| 亚洲一区二区成人在线观看| 中国一级特黄毛片| 欧美性大战久久久久久久蜜臀| 国产乱码精品一区二三区蜜臂| 精品精品国产高清a毛片牛牛 | 久久影院视频免费| 特黄一区二区三区| 亚洲一二三区视频在线观看| 日韩久久中文字幕| 91.com视频| 天天干,夜夜爽| 中文字幕亚洲无线码a| 欧洲中文在线| 国产精品pans私拍| 亚洲精品一二三**| 蜜桃视频在线观看成人| 91精品国产乱码久久久久久| 日韩欧美视频网站| 蜜桃一区二区三区四区| 精品熟女一区二区三区| 国产精品毛片高清在线完整版 | 亚洲区欧美区| www.精品在线| 91视频com| 国产十六处破外女视频| 91精品福利在线| 亚洲国产精品18久久久久久| 中文字幕精品在线视频| www成人免费观看| 亚洲qvod图片区电影| 一区二区三区四区在线看| 水蜜桃在线免费观看| 日韩精品一二三| 亚洲精品在线视频免费观看| 亚洲欧美激情插 | 国产资源在线观看入口av| 91精品久久久久久久久中文字幕| 日本久久成人网| 黄色一级大片免费| 另类小说综合欧美亚洲| 全黄一级裸体片| 亚洲国产精品久久久男人的天堂| 91福利在线观看视频| 亚洲精品一区中文| 国产精品探花在线| 成人在线国产精品| 成人一区而且| 欧美两根一起进3p做受视频| 99免费精品在线| 久久免费小视频| 日韩一区二区三区高清免费看看| seseavlu视频在线| 欧美与欧洲交xxxx免费观看| 国产精品毛片久久久| 国产情侣第一页| 国产精品一区二区黑丝| 国产精品免费人成网站酒店| 欧美日韩国产影片| 在线观看av黄网站永久| 国产精品久久久91| 亚洲自拍都市欧美小说| 亚洲精品无码久久久久久| 成人sese在线| 日韩免费一级片| 亚洲国产天堂网精品网站| 久草在线资源站资源站| 99国精产品一二二线| 亚洲一区二区三区| 天天干天天曰天天操| 亚洲人123区| 成人黄色免费视频| 欧美日韩成人免费| 久久九九精品视频| 日韩 欧美 自拍| 国产精品99精品久久免费| 久久精品第一页| 精品成人a区在线观看| www在线看| 久久久婷婷一区二区三区不卡| 国产视频一区免费看| 中文字幕一区二区人妻在线不卡| 色欧美日韩亚洲| av小片在线| 国产在线久久久| 欧美精品三区| 国产麻豆天美果冻无码视频 | 国产手机精品视频| 久久色精品视频| 天堂va欧美ⅴa亚洲va一国产| 国产一区二区四区| av激情综合网| 日韩av免费播放| 久久激情视频久久| 99久热这里只有精品视频免费观看| 99在线免费视频观看| www国产精品av| 国产情侣呻吟对白高潮| 久久久国产精品免费| 国产精品高潮呻吟久久久久 | 理论电影国产精品| 黄色一级视频免费| 日韩极品精品视频免费观看| 主播大秀视频在线观看一区二区| 免费看av软件| 北岛玲一区二区三区四区| av大全在线观看| xxxx性欧美| 香蕉久久99| 91精品999| 五月婷婷欧美视频| 超碰国产在线观看| 国产91亚洲精品一区二区三区| 老司机精品久久| 国产a免费视频| 亚洲欧美国产精品久久久久久久| 国产精品久久久久久av公交车| 欧美三级在线观看视频| 国产精品免费av| 熟妇高潮一区二区三区| 国产精品视频内| 夜夜夜久久久| 91高清免费看| 亚洲网站在线播放| 成人激情自拍| 奇米视频888| 色哦色哦哦色天天综合| 污污网站在线看|