使用WKWebView进行性能调优
From: http://xibhe.com/2018/02/03/WKWebView-disabuse/
使用WKWebView进行性能调优
最近一周,用户频繁反应一个问题:切换到某个功能页面后,加载H5页面相应时间过长,当H5页面未展示出来时,此时,再切换到其他页面,App会卡死。我们试着在公司的网络环境下复现这个问题,但并未复现。
错误的尝试
最开始时并没有意识到是webView的原因,反而因为前几天刚解决了一个UI线程的bug,将这个卡顿问题主观上当做线程问题去解决。基于此做了以下操作:
- 增加webView加载失败的代理方法;
- 在加载完成和加载失败时,取消加载进度动画的展示;
- 在将项目中的页面替换为 WKWebView 后,发现在访问下个H5页面时,无法共享 Cookie 的问题(下面会详细说下这个问题是如何解决的),导致无法获取到已经验证成功的用户登录信息。
先期采用方法1和方法2,但测试时还是会造成卡顿。后期替换为 WKWebView 后,亟待解决 Cookie 无法共享的问题,想着能不能在每次加载H5页面时,都在请求链接后面拼上用户信息的各种参数,经测试,这样做仍然无法解决页面跳转后读取用户信息的bug。而且还因每次访问页面频繁与服务器进行验证,给服务器带来了性能压力。
问题的复现
这时考虑到用户应该是在弱网环境下进行操作,遇到的问题。于是,使用网络封包分析工具Charles模拟慢速网络。选择Throttle present:56 kbps Modem。此时,再切换页面,先切换到那个加载H5的页面,然后再来回切换其他几个页面,就会出现APP卡死的情况。(这里需要说明的是其他切换的页面有4个同样是加载H5页面,一共有8个主界面)。
现在问题基本可以明确了,每次加载H5页面时都要初始化webView导致了程序内存消耗过大,造成APP卡死。
控制台报错
调试时,在程序频繁切换刷新页面直至卡死阶段,控制台一直报错,主要报错如下:
1. Domain=NSURLErrorDomain Code=-999
<LZoutsourceViewController.m : 226> -[LZoutsourceViewController webView:didFailProvisionalNavigation:withError:]
2018-01-31 21:02:22.084257+0800 CloudOfficeTest[9230:4603782] error:Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSErrorFailingURLStringKey=http://test.net/h5/zskt/spkt.html? _WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x1c0822d20>}
2. NSURLConnection finished with error - code -1002
2018-01-31 21:35:56.144596+0800 CloudOfficeTest[9301:4618465] NSURLConnection finished with error - code -1002
2018-01-31 21:36:02.742996+0800 CloudOfficeTest[9301:4618815] TIC TCP Conn Failed [14:0x1c41702c0]: 3:-9802 Err(-9802)
3. failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode
2018-02-01 11:09:01.952689+0800 CloudOfficeTest[614:68129] void SendDelegateMessage(NSInvocation *): delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode
其中,前两个错误都有错误码,分别对应
Code=-999,NSURLErrorCancelled
code -1002,NSURLErrorUnsupportedURL
-999的错误,是因为webView在之前的请求还没有加载完成,就发起了下一个请求,此时webView会取消之前的请求,因此会回调的请求失败这里。
这里使用的是WKWebView,因此,需要在WKWebView加载失败的代理方法里拦截掉被取消的请求。
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
{
// code = -999,被取消什么也不干
if ([error code] == NSURLErrorCancelled) {
return;
}
NSLog(@"error:%@",error);
// 失败后的后续处理.....
}
第3个错误中看到了main run loop的字样,感觉很有可能是造成卡顿的元凶了。又在项目中全局搜了一下报错的这个方法,发现是使用的js与oc交互框架—WebViewJavaScriptBridge中的方法。
// WebViewJavascriptBridge.m
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
这个方法是框架WebViewJavascriptBridge中的方法,主要用于处理UIWebView与JS交互。到目前为止,仍然不能定位到究竟是UIWebView与JS交互时发生了什么?才导致报这个错误。只是隐隐的感觉到可能和初始化UIWebView时的内存消耗有关,毕竟WKWebView的内存消耗相比UIWebView低了一个数量级。于是,将加载会卡顿的页面替换为WKWebView来加载H5页面,通过降低频繁初始化消耗的内存,减少页面卡死的概率。但在替换后遇到一些比较棘手的问题。
具体替换步骤
引入WKWebView的代理,生成WKWebViewJavascriptBridge桥接对象
#import “WKWebViewJavascriptBridge.h”
#import “LZWKWebKitSupport.h”@interface LZPartnerMainViewController ()
@property WKWebViewJavascriptBridge jsBridge;
/WKWebView/
@property (nonatomic, strong) WKWebView wkWebView;
初始化WKWebView
- (void)viewDidLoad
{
_wkWebView = [LZWKWebKitSupport createSharableWKWebView:YES isShowNav:YES];
[_wkWebView addObserver:self forKeyPath:@”estimatedProgress” options:NSKeyValueObservingOptionNew context:nil];
[self.view addSubview:_wkWebView];
self.jsBridge = [WKWebViewJavascriptBridge bridgeForWebView:_wkWebView];
[self.jsBridge setWebViewDelegate:self];// 使用WKWebViewJavascriptBridge进行桥接,OC端注册方法,由js端进行调用
[_jsBridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) { NSLog(@"data:%@",data); NSString *urlStr = nil; NSString *processIsTop = nil; if ([data isKindOfClass:[NSString class]]) { urlStr = data; }else{ NSDictionary *dic = data; urlStr = dic[@"url"]; processIsTop = dic[@"processIsTop"]; } responseCallback(@"Response from testObjcCallback"); }];
}
- (void)viewDidLoad
注意,这里通过LZWKWebKitSupport来初始化一个WkWebView是为了同步Cookie,后面会具体说到为什么要同步Cookie及如何同步。
设置WkWebView的代理方法
#pragma mark - WKNavigationDelegate
// 开始加载- (void)webView:(WKWebView )webView didCommitNavigation:(WKNavigation )navigation
{
self.loadingView.hidden = NO;
NSLog(@”didCommitNavigation”);
}
// 加载完成
- (void)webView:(WKWebView )webView didFinishNavigation:(WKNavigation )navigation
{
self.loadingView.hidden = YES;
NSLog(@”didFinishNavigation”);
}
// 加载失败
- (void)webView:(WKWebView )webView didFailProvisionalNavigation:(WKNavigation )navigation withError:(NSError *)error
{
// code = -999
if ([error code] == NSURLErrorCancelled) {
}return;
NSLog(@”didFailProvisionalNavigation error.code = %ld”,error.code);
}
#pragma mark - wkwebviewDelegate
- (void)webView:(WKWebView )webView decidePolicyForNavigationAction:(WKNavigationAction )navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
decisionHandler(WKNavigationActionPolicyAllow);
}
//接收到服务器响应 后决定是否允许跳转,主要用来处理请求失败的情况。
(void)webView:(WKWebView )webView decidePolicyForNavigationResponse:(WKNavigationResponse )navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
decisionHandler(WKNavigationResponsePolicyAllow);
NSHTTPURLResponse response = (NSHTTPURLResponse )navigationResponse.response;// 读取cookies
NSArray cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
for (NSHTTPCookie cookie in cookies) {[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
if (response.statusCode && response.statusCode != 200) {
LZErrorHintType type = LZErrorHintType404; if (![[Singleton shareInstance] hasNet]) { type = LZErrorHintTypeNet; } __weak typeof(self) weakSelf = self; if (!_errorView) { //弹出错误界面,点击刷新按钮刷新界面 LZErrorHintView *errorView = [[LZErrorHintView alloc] initWithFrame:self.view.bounds type:type refreshBlock:^{ [weakSelf loadHTMLPage]; }]; weakSelf.errorView = errorView; [self.view addSubview:errorView]; } return;
}
if ([[Singleton shareInstance] hasNet]) {if (_errorView) { [_errorView removeFromSuperview]; _errorView = nil; }
}else{
if(!_errorView){ __weak typeof(self) weakSelf = self; LZErrorHintView *errorView = [[LZErrorHintView alloc] initWithFrame:self.view.bounds type:LZErrorHintTypeNet refreshBlock:^{ [weakSelf viewWillAppear:YES]; }]; _errorView = errorView; errorView.tag = 2200; [self.view addSubview:errorView]; }
}
}
- (void)webView:(WKWebView )webView didCommitNavigation:(WKNavigation )navigation
替换UIWebView为WKWebView后遇到的问题及解决方法
1. 使用WKWebViewJavascriptBridge进行桥接时,加载H5页面闪退。
这里需要更新WebViewJavaScriptBridge桥接框架中WKWebView的桥接方法,
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
// 对比之前的方法,这个地方多了一个return
}
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
2. WKWebView加载完网页后,点击里面的按钮,不跳转的问题。
设置WKWebView的另一个代理WKUIDelegate,从名称能看出它是webView在user interface上的代理,
// 创建新的webView
// 可以指定配置对象、导航动作对象、window特性。如果没用实现这个方法,不会加载链接,如果返回的是原webview会崩溃。
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
if (!navigationAction.targetFrame.isMainFrame) {
[webView loadRequest:navigationAction.request];
}
return nil;
}
要调用下面的方法是有条件的,WKNavigationDelegate中的该方法是用户点击网页上的链接,需打开新页面时,将先调,是否允许跳转到链接。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
WKFrameInfo *sFrame = navigationAction.sourceFrame;//navigationAction的出处
WKFrameInfo *tFrame = navigationAction.targetFrame;//navigationAction的目标
//只有当 tFrame.mainFrame == NO;时,表明这个 WKNavigationAction 将会新开一个页面。
// 才会调用createWebViewWithConfiguration这个代理方法。
}
这样就新开一个webView,如果我们只是显示网页,这样会消耗性能,没有必要。
3. 如何同步WKWebView的Cookie
在将UIWebView替换为WKWebView后加载速度提高了,页面卡死的问题基本没有再出现过。但遇到了一个更加棘手的问题,之前使用的是UIWebView,它会对首次加载H5页面后的用户登录信息进行同步,这样我由当前的H5页面跳转到一个新的UIWebView进行请求时,会自动找到上个页面同步的用户信息,从而加载当前用户对应的内容。
WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。
因此,如何实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。是决定能否继续使用WKWebView的关键。如果不能解决这个问题,就只能再继续使用之前的 UIWebView 了,之前所做的一切都没有用处了。
解决多个 WKWebView 之间共享 Cookie 的问题,首先要弄明白三个问题?
- WKWebView 与 webView 在 Cookie 设置,读取上有什么不同?
- WKWebView 会将对应的 Cookie 存在什么地方?
- 如何取到 WKWebView 的 Cookie 并将其注入到要访问的下一个 WKWebView 中?
结合以上三个问题,在网上搜索很多关于 WKWebView 的 Cookie 存储在什么地方? 这些资料普遍认为 WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中。但在实际项目中,却发现 WKWebView 实例可以读取到存储于 NSHTTPCookieStorage 中的 Cookie。最后,看到了腾讯Bugly的一篇技术文章 —- WKWebView 那些坑,也印证了我的观点。
实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中。
看来以后搜索技术文章,不能太片面了,一定要结合一些大厂的权威技术文章来具体分析。
下一步,就是如何在发起请求时注入 通过 NSHTTPCookieStorage 获取的Cookie。网上关于 WKWebView 的 Cookie 注入方法有以下几种:
- JS注入 —- 在初始化 WKWebView 的时候,通过 WKUserScript 设置,使用javascript 注入 Cookie,一开始发送 NSMutableURLRequest 请求的时候也要加上 Cookie,并且保证两个地方的设置的cookie一致。参考 — Can I set the cookies to be used by a WKWebView?
- WKHTTPCookieStore —- 利用 iOS11 API WKHTTPCookieStore 解决 WKWebView 首次请求不携带 Cookie 的问题。参考 — iOS WebView 中的 Cookie 处理业务场景“IP直连”方案说明
- 利用 iOS11 之前的 API 解决 WKWebView 首次请求不携带 Cookie 的问题。参考 — iOS WebView 中的 Cookie 处理业务场景“IP直连”方案说明
- 通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie) 数据。不过 WKWebView WKProcessPool 实例在 app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookie、session Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。
方法1,经过测试行不通,可能是后台读取 Cookie 的方式有问题;方法2,是 iOS 11 的 API ,不具有普适性;方法3,在测试时无法通过 url 匹配到 Cookie;最后,只剩下方法4了,需要注意在特殊场景下 Cookie 丢失的情况:
app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookie、session Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。
但以我们的应用为例,哪怕是主动杀进程,重新打开应用;还是应用突然闪退,重新打开应用。首次加载某个含有用户登录验证的H5页面时,需要在发起请求的地方拼上用户特定信息的参数,因此,即使之前存储的 Cookie 数据丢失了,也会在首次加载时重新获取。如下:
Singleton *sin = [Singleton shareInstance];
NSString *baseIpPort = [LZUserDefaults objectForKey:PreferenceKey_SystemInit_ZyPartnerIPPort];
NSString *urlString = [[NSString stringWithFormat:@"%@/test1/test2?Id=%@&Name=%@&Pid=%@",baseIpPort,sin.clinicId,[LZUserDefaults objectForKey:PreferenceKey_Name],[LZUserDefaults objectForKey:PreferenceKey_Pid]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
request.HTTPMethod = @"POST";
request.timeoutInterval = 15.0f;
[_wkWebView loadRequest:request];
因此,对于 APP 重启后 Cookie 数据可能丢失的情况,难道不可以在首次加载H5页面时,重新获取一下用户登录信息的 Cookie 吗?对我而言,现在的项目就是这样做的。
通过 WKProcessPool 实现多个 WKWebView 之间共享 Cookie
1. 新建一个名为 LZWKWebKitSupport 的类,用于生成一个统一的,全局使用同一个 WKProcessPool 的 WKWebView 对象。
// LZWKWebKitSupport.h
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@interface LZWKWebKitSupport : NSObject
@property (nonatomic, strong,readonly) WKProcessPool *processPool;
+ (instancetype)sharedSupport;
+ (WKWebView *)createSharableWKWebView:(BOOL)isFullScreen isShowNav:(BOOL)showNav;
@end
// LZWKWebKitSupport.m
#import "LZWKWebKitSupport.h"
@interface LZWKWebKitSupport()
@end
@implementation LZWKWebKitSupport
+ (instancetype)sharedSupport {
static LZWKWebKitSupport *_instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [LZWKWebKitSupport new];
});
return _instance;
}
- (instancetype)init {
if (self = [super init]) {
self.processPool = [WKProcessPool new];
}
return self;
}
+ (WKWebView *)createSharableWKWebView:(BOOL)isFullScreen isShowNav:(BOOL)showNav
{
WKUserContentController* userContentController = [WKUserContentController new];
NSMutableString *cookies = [NSMutableString string];
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[cookies copy] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
// 一下两个属性是允许H5视频自动播放,并且全屏,可忽略
configuration.allowsInlineMediaPlayback = YES;
configuration.mediaPlaybackRequiresUserAction = NO;
// 全局使用同一个processPool
configuration.processPool = [[LZWKWebKitSupport sharedSupport] processPool];
configuration.userContentController = userContentController;
// 考虑到左侧菜单栏,需要设置webView的不同frame
WKWebView *wk_webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, y, width, height) configuration:configuration];
return wk_webView;
}
@end
2. 在加载H5的地方初始化 LZWKWebKitSupport,并在 WKNavigationDelegate 中获取 cookie,并设置到本地。
// 初始化LZWKWebKitSupport
- (void)viewDidLoad{
_wkWebView = [LZWKWebKitSupport createSharableWKWebView:YES isShowNav:YES];
[self.view addSubview:_wkWebView];
}
#pragma mark - wkwebviewDelegate
//接收到服务器响应 后决定是否允许跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
decisionHandler(WKNavigationResponsePolicyAllow);
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
// 读取cookie,并设置到本地
NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
for (NSHTTPCookie *cookie in cookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
}
3. 在从第一个H5页面跳转至第二个H5页面时,在发起请求时注入Cookie。
这里以跳转到 LZDetailViewController 页面为例,先是通过LZWKWebKitSupport 初始化一个 WKWebView
// LZDetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
//初始化视图
[self setUpSubViews];
}
- (void)setUpSubViews{
_wkWebView = [LZWKWebKitSupport createSharableWKWebView:YES isShowNav:NO];
_wkWebView.UIDelegate = self;
[self.view addSubview:_wkWebView];
_jsBridge = [WKWebViewJavascriptBridge bridgeForWebView:_wkWebView];
[_jsBridge setWebViewDelegate:self];
}
然后在加载请求时,注入之前设置的 Cookie
- (void)loadUrl{
if (!_urlStr) {
return;
}
NSURL *url = [NSURL URLWithString:_urlStr];
NSMutableString *cookies = [NSMutableString string];
NSMutableURLRequest *requestObj = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
// 一般都只需要同步BJSESSIONID,可视不同需求自己做更改
NSString * BJSESSIONID;
// 获取本地所有的Cookie
NSArray *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
for (NSHTTPCookie * cookie in tmp) {
if ([cookie.name isEqualToString:@"BJSESSIONID"]) {
BJSESSIONID = cookie.value;
break;
}
}
if (BJSESSIONID.length) {
// 格式化Cookie
[cookies appendFormat:@"BJSESSIONID=%@;",BJSESSIONID];
}
// 注入Cookie
[requestObj setValue:cookies forHTTPHeaderField:@"Cookie"];
// 加载请求
[self.wkWebView loadRequest:requestObj];
}
通过以上三步就可以达到同步 Cookie 的目的,现在看来之前通过 JS脚本 注入 Cookie 失败,可能是由于后台需要同步 BJSESSIONID,而BJSESSIONID 是 HtppOnly,不允许通过js脚本修改。
最后,需要特别注意的一点是:考虑在加载H5页前,是否需要清除某些H5页面的 Cookie ?
这里对于我们的项目而言,加载的需要验证用户身份信息的H5页面,是需要清除 Cookie 的,因为用户的权限不同,所看到的界面就不同,在同一台设备下切换不同的用户时,如果不清除之前的 Cookie,所展示的就是上一个用户的信息。
- (void)deleteWKCookies
{
// 清除WKWebView缓存的cookie(根据ip)
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0){
NSString *iPPort = [LZUserDefaults objectForKey:PreferenceKey_SystemInit_ZyIPPort];
NSArray *iPPortArray = [iPPort componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]];
NSString *recordIP;
if ([iPPortArray count] > 2) {
recordIP = partnerIPPortArray[2];
}
WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
[dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
for (WKWebsiteDataRecord *record in records)
{
// 以www.baidu.com为例,是否包含baidu.com
if ([recordIP containsString:record.displayName])
{
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{
NSLog(@"Cookies for %@ deleted successfully",record.displayName);
}];
}
}
}];
}
}
WebView性能优化总结
一个加载网页的过程中,native、网络、后端处理、CPU都会参与,各自都有必要的工作和依赖关系;让他们相互并行处理而不是相互阻塞才可以让网页加载更快:
- WebView初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。
- 后端处理慢,可以让服务器分trunk输出,在后端计算的同时前端也加载网络静态资源。
- 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
- 同时,合理的预加载、预缓存可以让加载速度的瓶颈更小。
- WebView初始化慢,就随时初始化好一个WebView待用。
- DNS和链接慢,想办法复用客户端使用的域名和链接。
- 脚本执行慢,可以把框架代码拆分出来,在请求页面之前就执行好。
上面是美团点评技术团队关于WebView性能优化的总结。
对比我们项目中有哪些页面用到了 UIWebView,哪些用到了 WKWebView,发现当前程序中一共有8个主要模块,其中,一共有4个主要模块是通过加载H5页面展示的,还有一个模块中部分嵌套了H5页面。这些页面中,有三个页面使用 WkWebView 加载,剩下的使用的是 UIWebView 加载页面,发生卡顿的页面多是频繁初始化 UIWebView 加载H5时发生的。
这里我们的项目中使用UIWebView 和 WKWebView 的地方有很多,没有一个管理类去居中调控的话,后期维护起来会很耗时,而且很容易出现bug。下一步的优化就是要构建这样一种集构建,配置,分发,操控为一身的通用类。
参考资料
webView:decidePolicyForNavigationAction:decisionHandler:
iOS WebView 中的 Cookie 处理业务场景“IP直连”方案说明
使用WKWebView进行性能调优
install_url
to use ShareThis. Please set it in _config.yml
.