内容简介:从用户角度,相比Native页面,H5页面的体验问题主要有两点:通过Webview打开H5页面,请求并得到 HTML、CSS 和 JavaScript 等资源并对其进行处理从而渲染出 Web 页面。对H5页面的渲染,主要包括:渲染树构建、布局及绘制,具体可分为:
从用户角度,相比Native页面,H5页面的体验问题主要有两点:
- 页面打开时间慢 :打开一个 H5 页面需要做一系列处理,会有一段白屏时间,体验糟糕。
- 响应流畅度较差 :由于 WebKit 的渲染机制,单线程,历史包袱等原因,页面刷新/交互的性能体验不如原生。
这里讨论的是:第一点,怎样减少白屏时间。
二、Webview打开H5
通过Webview打开H5页面,请求并得到 HTML、CSS 和 JavaScript 等资源并对其进行处理从而渲染出 Web 页面。
1、加载流程
-
初始化Webview
->请求页面
->下载数据
->解析HTML
->请求 js/css 资源
->DOM 渲染
->解析 JS 执行
->JS 请求数据
->解析渲染
->下载渲染图片
->页面完整展示
- DOM渲染之前耗时主要在两部分: 初始化Webview 和 数据请求 ,一般Webview首次初始化在400ms这个量级,二次加载能少一个量级。
- 数据请求依赖网络,网络请求一般经过:DNS查询、TCP 连接、HTTP 请求和响应。数据包括HTML、JS和CSS资源,这些都是在webview在loadRequest:之后做的,这一阶段,用户所见到的都是白屏。(虽然4G已经成为主流,但是4G延迟明显高于Wifi)。
2、H5页面渲染
对H5页面的渲染,主要包括:渲染树构建、布局及绘制,具体可分为:
-
处理 HTML 标记并构建 DOM 树。
-
处理 CSS 标记并构建 CSSOM(CSS Object Model) 树。
-
将 DOM 与 CSSOM 合并成一个渲染树。
-
根据渲染树来布局,以计算每个节点的几何信息。
-
将各个节点绘制到屏幕上。
说明:这五个步骤并不一定一次性顺序完成。如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM。具体参考: DOM渲染机制与常见性能优化
3、总结
- 分析Webview打开H5打开的过程,我们发现,在H5优化中,前端重任在肩;
降低请求量:合并资源,减少 HTTP 请求数,minify / gzip 压缩,webP,lazyLoad。 加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发。 缓存:HTTP 协议缓存请求,离线缓存 manifest,离线数据缓存localStorage。 渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline。 复制代码
- 但是客户端也很重要,主要优化DOM渲染之前这些事情,可以做有: 减少DNS时间 、 预初始化WebView 以及 HTML、JS、CSS等资源离线下载 。
- 列举在某业务中笔者实践过的比较trick的优化方案,然后再引出笔者认为理想的方案。
二、WebView的客户端优化(trick版)
由于是接入第三方的H5页面,接入离线包方案,需要比较繁杂的商务沟通和技术挑战(业务逻辑和代码超级诡异),临时采用如下优化方案 。
1、预加载资源
- 将首页面需要的
JS文件
和CSS文件
等资源放在一个URL地址( 和业务url同域名 ); - 启动App后,间隔X秒去加载;加载的策略是,检查当前和上一次间隔时间,超时则加载,有效期忽略预加载请求。
2、预初始化Webview
-
首次初始化Webview,需要初始化浏览器内核,需要的时间在400ms这个量级;二次初始化时间在几十ms这个量级;
-
根据此特征:选择在APP 启动后X秒,预创建(初始化)一个 Webview 然后释放,这样等使用到 H5 模块,再加载 Webview时,加载时间也少了不少。
-
结合步骤一中预加载公共资源,也需要Webview,所以选择在加载公共资源包时候,首次初始化Webview,加载资源,然后释放。
3、最终方案(迫不得已)
由于第三方业务H5很多问题,和人力上不足;不得不需要客户端强行配合优化,在产品的要求下,不得不采用如下方案,方案的前提是:业务H5尽可能少修改,甚至不修改,客户端还要保证首屏加载快;
- 预加载资源
- 预创建Webview并加载首页H5,驻留在内存中,需要的时候,立刻显示。
4、方案的后遗症
- 我不建议这种trick做法,因为自从开了这个口子,后续很多H5需求不走之前既定的离线包方案,在内存中预创建多个Webview (最多4个),加载H5时候不用新建Webview,从Webview池中获取;
- 此种Webview池方案带来诸多隐患:内存压力、诡异的白屏、JS造成的内存泄露,页面的清空等等问题( 填坑填到掉头发 )。
三、离线包方案
1、概述
-
离线包方案才是业务主流的H5加载优化方案,非常建议在客户端团队和前端团队推广,类似预创建Webview加载H5不应该成为主流。
-
将每个独立的H5功能模块,相关HTML、Javascript、CSS 等页面内 静态资源 打包到一个压缩包内,客户端可以下载该离线包到本地,然后打开Webview,直接从本地加载离线包,从而最大程度地摆脱网络环境对 H5 页面的影响。
-
离线包可以 提升用户体验 (页面加载更快),还可以 实现动态更新 (在推出新版本或是紧急发布的时候,可以把修改的资源放入离线包,通过更新配置让应用自动下载更新)
2、方案描述
引用bang的离线包方案,简单描述如下 :
-
后端使用构建 工具 把同一个业务模块相关的页面和资源打包成一个文件,同时对文件加密/签名。
-
客户端根据配置表,在自定义时机去把离线包拉下来,做解压/解密/校验等工作。
-
根据配置表,打开某个业务时转接到打开离线包的入口页面。
-
拦截网络请求,对于离线包已经有的文件,直接读取离线包数据返回,否则走 HTTP 协议缓存逻辑。
-
离线包更新时,根据版本号后台下发两个版本间的 diff 数据,客户端合并,增量更新。
说明:目前WKWebView已经能成为主流,但是WKWebView在实现离线包方案时,拦截网络请求有坑。
3、WKWebView拦截网络请求的坑
- 虽然NSURLProtocol可以拦截监听每一个 URL Loading System 中发出 request 请求,记住是URL Loading System中那些类发出的请求,也支持AFNetwoking,UIWebView发出的request,NSURLProtocol都可以拦截和监听。
- 因为WKWebView 在 独立进程里 执行网络请求。一旦注册 http(s) scheme 后,网络请求将从 Network Process 发送到 App Process,这样 NSURLProtocol 才能拦截网络请求。
- 但是在 WebKit2 的设计里使用 MessageQueue 进行进程之间的通信,Network Process 会将请求 encode 成一个 Message,然后通过 IPC(进程间通信) 发送给 App Process。出于性能的原因,encode 的时候 将HTTPBody 和 HTTPBodyStream 这两个字段丢弃掉( 坑 )
- 因此,如果通过 registerSchemeForCustomProtocol 注册了 http(s) scheme, 那么由 WKWebView 发起的所有 http(s)请求都会通过 IPC 传给主进程 NSURLProtocol 处理, 导致 post 请求 body 被清空 ;
//苹果开源的 WebKit2 源码暴露了私有API: + [WKBrowsingContextController registerSchemeForCustomProtocol:] //通过注册 http(s) scheme 后 WKWebView 将可以使用 NSURLProtocol 拦截 http(s) 请求: Class cls = NSClassFromString(@"WKBrowsingContextController”); SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:"); if ([(id)cls respondsToSelector:sel]) { // 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理 [(id)cls performSelector:sel withObject:@"http"]; [(id)cls performSelector:sel withObject:@"https"]; } 复制代码
**说明1:**名目张胆使用私有API,是过不了AppStore审核的,具体使用什么办法,想来你也懂(hun xiao)。
说明2:一旦打开ATS开关: Allow Arbitrary Loads 选项设置为NO ,通过 registerSchemeForCustomProtocol 注册了 http(s) scheme,WKWebView 发起的所有 http(s) 网络请求将被阻塞(即便将 Allow Arbitrary Loads in Web Content 选项设置为YES );
说明3:iOS11之后可以通过 WKURLSchemeHandler 去完成对 WKWebView
的请求拦截,不需要再调用私有API解决上述问题了。
4、WKWebView自定义资源scheme
- 向WKWebView 注册 customScheme, 比如 dynamic:// , 而不是https或http,避免对https或http请求的影响
- 保证使用离线包功能的请求,没有post方式,遇到customScheme请求,比如
dynamic://www.dynamicalbumlocalimage.com/
,通过 NSURLProtocol 拦截这个请求并加载离线数据。 - iOS 11上, WebKit 提供的WKURLSchemeHandler可实现拦截,需要注意的只允许开发者拦截自定义 Scheme 的请求,不允许拦截 “http”、“https”、“ftp”、“file” 等的请求,否则会crash。
四、其他
1、LocalWebServer
- 离线包方案中,除了拦截请求加载资源的方式,还有种在项目中搭建local web server,用以获得本地资源。市面有比较完善的框架
CocoaHttpServer (支持iOS、macOS及多种网络场景) GCDWebServer (基于iOS,不支持 https 及 webSocket) Telegraph (Swift实现,功能较上面两类更完善) 复制代码
- 具体可参考 基于 LocalWebServer 实现 WKWebView 离线资源加载 , 之前团队有过实践,采用的是GCDWebServer
2、WKWebView loadRequest 问题
- 在 WKWebView 上通过 loadRequest 发起的 post 请求 body 数据会丢失:
//同样是由于进程间通信性能问题,HTTPBody字段被丢弃 [request setHTTPMethod:@"POST"]; [request setHTTPBody:[@"bodyData" dataUsingEncoding:NSUTF8StringEncoding]]; [wkwebview loadRequest: request]; 复制代码
解决:假如想通过-[WKWebView loadRequest:]加载 post 请求 (原始请求)request1: h5.nanhua.com/order/list ,可以通过以下步骤实现:
- 替换请求 scheme,生成新的 post 请求 request2: post://h5.nanhua.com/order/list , 同时将 request1 的 body 字段复制到 request2 的 header 中 (WebKit 不会丢弃 header 字段);
- 通过-[WKWebView loadRequest:] 加载新的 post 请求 request2;
- 并且通过 +[WKBrowsingContextController registerSchemeForCustomProtocol:]注册 scheme: post:// ;
- 注册 NSURLProtocol 拦截请求 post://h5.nanhua.com/order/list ,替换请求 scheme, 生成新的请求 request3: h5.nanhua.com/order/list ,将 request2 header的body 字段复制到 request3 的 body 中 ,并使用 NSURLSession 加载 request3,最后将加载结果返回 WKWebView;
以上所述就是小编给大家介绍的《Webview加载H5优化小记》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
现代信息检索
(西班牙) Ricardo Baeza-Yates、(巴西)Berthier Ribeiro-Neto / 机械工业出版社 / 2011-3 / 78.00元
本书不仅详细介绍了信息检索的所有主要概念和技术,以及有关信息检索面的所有新变化,而且其组织使读者既可以对现代信息检索有一个全面的了解,又可以获取现代信息检索所有关键主题的详细知识。本书的主要内容由信息检索领域的代表人物Baeza-Yates和Ribeiro-Neto编写,对于那些希望深入研究关键领域的读者,书中还提供了由其他主要研究人员编写的关于特殊主题的发展现状。 与上一版相比,本版在内容......一起来看看 《现代信息检索》 这本书的介绍吧!