iOS WebView提高加载速度的几种优化方案

西门桃桃 2021-06-10 PM 11020℃ 1条

背景

为了提升APP的留存和日活,除了需要产品做业务上的调整,同时更需要提高APP的用户体验。承载了众多业务h5的页面往往会成为性能瓶颈。本文内容致力于解决webview在实际使用中的各种问题,包括性能优化、提高响应速度、静态资源预加载等问题。

目标

  1. APP首次冷启动首页白屏时间缩短至300ms内
  2. APP热启动直接加载缓存,不再重新reload

性能瓶颈

APP首页是加载的H5页面,所以通过对WebView加载页面的每个阶段进行性能监控。得到每个阶段性能对用户体验的影响。

WebView加载过程.png

如上图所示,从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)30676
iOS(WKWebView)736457
Android192142

根据测试数据,我们就可以看出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的默认缓存策略。
流程如下:

WebView预加载策略.png

扩展性

本次优化是通过本地的静态URL实现的预加载,后期可以通过服务器配置静态资源URL列表,实现动态URL列表预加载。
流程如下:

WebView预加载扩展性.png

测试报告

  • 测试环境:iPhone X 、 iOS 13.4.1 、wkwebview
  • WiFi网络环境:Huike-Group
  • 测试方式:开课吧APP首页首次加载,测试10次
  • 测试单位:ms
优化前优化后
2247136
4677406
352463
2517254
2867220
269230
2242436
2476302
2873369
2435271

平均时长:

优化前优化后优化比率
2855248.711.48倍

总结

由于时间限制,所以本次优化只优化了iOS端首页的加载速度,下期迭代时,可以把这种优化技术应用到Android端。

作为一种思路,所以不管是iOS、Android还是Flutter开发,都可以使用这种方案用来分析并且提高页面的加载速度,最后实现提高用户体验的目标。

标签: 性能优化

非特殊说明,本博所有文章均为博主原创。

评论啦~



唉呀 ~ 仅有一条评论


  1. 小破孩
    小破孩

    你好请问这个有git链接,或者demo吗?

    回复 2023-04-18 14:04