背景
为了提升APP的留存和日活,除了需要产品做业务上的调整,同时更需要提高APP的用户体验。承载了众多业务h5的页面往往会成为性能瓶颈。本文内容致力于解决webview在实际使用中的各种问题,包括性能优化、提高响应速度、静态资源预加载等问题。
目标
- APP首次冷启动首页白屏时间缩短至300ms内
- APP热启动直接加载缓存,不再重新reload
性能瓶颈
APP首页是加载的H5页面,所以通过对WebView加载页面的每个阶段进行性能监控。得到每个阶段性能对用户体验的影响。
如上图所示,从WebView初始化至WebView开始渲染这段时间,整个屏幕处于一种白屏的loading状态,此时用户无法得到任何响应,这段时间越长用户体验越差。
用户体验有一个2-5-8原则:
就是当用户能够在2秒以内得到响应时,会感觉系统的响应很快;当用户在2-5秒之间得到响应时,会感觉系统的响应速度还可以;当用户在5-8秒以内得到响应时,会感觉系统的响应速度很慢,但是还可以接受;而当用户在超过8秒后仍然无法得到响应时,会感觉系统糟透了,或者认为系统已经失去响应,而选择离开。
现在我们的白屏时长在WIFI状态下平均为2-5秒左右,如果要继续提高用户体验,提高WebView响应速度、减少静态资源加载时间就是这次优化的重点。
解决方案:
一、WebView预加载优化WebView初始化时间
当App首次打开时,默认是并不初始化浏览器内核的;只有当创建WebView实例的时候,才会创建WebView的基础框架。
所以与浏览器不同,App中打开WebView的第一步并不是建立连接,而是启动浏览器内核。
分析:
针对WebView的初始化时间,我们可以定义两个指标:
- 首次初始化时间:客户端冷启动后,第一次打开WebView,从开始创建WebView到开始建立网络连接之间的时间。
- 二次初始化时间:在打开过WebView后,退出WebView,再重新打开WebView,从开始创建WebView到开始建立网络连接之间的时间。
测试数据:
测试系统1: iOS模拟器,Titans 10.0.7
测试系统2: OPPO R829T Android 4.2.2
测试方式:测试10次取平均值
测试App:开课吧
单位:ms
系统 | 首次初始化时间 | 二次初始化时间 |
---|---|---|
iOS(UIWebView) | 306 | 76 |
iOS(WKWebView) | 736 | 457 |
Android | 192 | 142 |
根据测试数据,我们就可以看出WebView因为有初始化时间导致加载速度比浏览器慢:
- 在浏览器中,我们输入地址时(甚至在之前),浏览器就可以开始加载页面。
- 而在客户端中,客户端需要先花费时间初始化WebView完成后,才开始加载。
这段时间内,由于WebView还不存在,所有后续的过程是完全阻塞的。所以解决方案就是在APP启动时预加载WebView,并且将预加载好的WebView放入重用池中,等待使用时,从重用池中取出即可直接使用。由于重用池中的WebView都是预先加载的,这段初始化的时间就可以全部节约下来。
预加载WebView示例代码:
@property (nonatomic, strong) NSMutableDictionary *preloadedViews;
@property (nonatomic, strong) NSMutableArray *urls;
+ (instancetype)sharedInstance {
static WebViewReusePool *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
/**
根据URL 预初始化若干WebView
*/
- (void)addPreloadingView:(NSArray *)urls {
for (NSString *url in urls) {
if (![self.urls containsObject:url]) {
[self.urls addObject: url];
ReuseWebView *webView = [self createWebView];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
[self.preloadedViews addEntriesFromDictionary:@{url:webView}];
}
}
}
- (void)removePreloadingView:(NSArray *)urls {
for (NSString *url in urls) {
if ([self.urls containsObject:url]) {
[self.preloadedViews removeObjectForKey:url];
}
}
}
/**
从池中获取一个WebView
@return WebView
*/
- (ReuseWebView *)getWebView {
return [self getWebView:nil];
}
- (ReuseWebView *)getWebView:(NSString *)url {
if (url && [self.urls containsObject:url] && self.preloadedViews.count) {
ReuseWebView *webView = [self.preloadedViews objectForKey:url];
return webView;
} else {
return [self createWebView];
}
}
/**
创建一个WebView
@return WebView
*/
- (ReuseWebView *)createWebView {
ReuseWebView *webView = [[ReuseWebView alloc] initWithFrame:CGRectZero];
return webView;
}
#pragma mark - lazy load
- (NSMutableArray *)urls {
if (!_urls) {
_urls = [NSMutableArray array];
}
return _urls;
}
- (NSMutableDictionary *)preloadedViews {
if (!_preloadedViews) {
_preloadedViews = [NSMutableDictionary dictionary];
}
return _preloadedViews;
}
二、静态资源缓存
静态资源主要是一些比较大的图片以及一些大的js文件,这部分资源通过预加载器在预加载WebView时同步缓存,缓存策略使用WKWebView的默认缓存策略。
流程如下:
扩展性
本次优化是通过本地的静态URL实现的预加载,后期可以通过服务器配置静态资源URL列表,实现动态URL列表预加载。
流程如下:
测试报告
- 测试环境:iPhone X 、 iOS 13.4.1 、wkwebview
- WiFi网络环境:Huike-Group
- 测试方式:开课吧APP首页首次加载,测试10次
- 测试单位:ms
优化前 | 优化后 |
---|---|
2247 | 136 |
4677 | 406 |
3524 | 63 |
2517 | 254 |
2867 | 220 |
2692 | 30 |
2242 | 436 |
2476 | 302 |
2873 | 369 |
2435 | 271 |
平均时长:
优化前 | 优化后 | 优化比率 |
---|---|---|
2855 | 248.7 | 11.48倍 |
总结
由于时间限制,所以本次优化只优化了iOS端首页的加载速度,下期迭代时,可以把这种优化技术应用到Android端。
作为一种思路,所以不管是iOS、Android还是Flutter开发,都可以使用这种方案用来分析并且提高页面的加载速度,最后实现提高用户体验的目标。
你好请问这个有git链接,或者demo吗?