脑图与概览
点击查看 webView.xmind
参考文章:
概念
1、当App首次打开时,默认是并不初始化浏览器内核的;
只有当创建WebView实例的时候,才会创建WebView的基础框架。
所以与浏览器不同,App中打开WebView的第一步并不是建立连接,而是启动浏览器内核。
先上结论
| 阶段 | 优化方案 | |
|---|---|---|
| (初次/重复)打开时,加载慢 | webview提前(尽可能多的)初始化+全局化 | |
| 加载中,加载慢 | 拦截加载自定义图片(webview_flutter暂不支持) | |
一、(初次/重复)打开时,加载慢:webview提前(尽可能多的)初始化+全局化
背景:有一款基于 cocos2d 的app游戏,其每次启动都很慢。(游戏端说是需要加载游戏引擎,而且引擎本身又比较消耗时间。)
请问1:如何提高初次打开的加载速度?
请问2:如何做到频繁退出进入游戏,不用都走一遍重新加载的流程呢?
| 序号 | 问题 | 解决 |
|---|---|---|
| 1 | 初次打开,加载慢 | 1、webview的在冷启动过后不久的提前初始化 2、初始化的时候多多进行可以初始化的部分,如尝试游戏引擎的加载。 |
| 2 | 重复打开,加载慢 | webview设为全局变量,重复使用 |
优化前后对比小结:
| 方法 | 存在的问题 VS 改进的好处 | |
|---|---|---|
| 未优化 | 每次进入游戏都开启页面,退出游戏都销毁页面。 | 使用上述方法,每次退出再进入该页面都会因为需要重新加载引擎,而导致游戏主页内容的显示很慢,从而严重影响app的使用体验。 |
| 优化后 | 1、游戏使用一个全局GameWebView(其他WebView不用全局) 2、游戏显示使用将webView添加到vc或者window上。 |
全局GameWebView不会被销毁,能够重复使用,提升加载速度。 ①加载过的游戏,下次再进入不用重新重新加载引擎,有效的跳过loading的过程,极大的改善了用户体验。 |
附1:游戏引擎能否直接在原生上提前加载?
答:常见做法:构建出含Cocos的原生工程,然后将自己的功能添加上去。见 Cocos Creator 上原生平台二次开发指南
存在问题:不能自己在原生上添加游戏引擎。 Cocos Creator 引擎定制工作流程 也只是将其自己编译器创建的项目的引擎定制。
最终结论:游戏引擎没办法在原生上提前自己加载。
背景:如何将 Cocos Creator 构建出来的工程用正确的姿势接入Flutter 工程
附2:一个视图实例被添加后,再被添加到其他地方后的效果
此需求背景:《页面跳转相关(路由)》中【四、游戏与app跳转交互优化调研】
测试过程见附录 附1:测试一个视图实例被添加后,再被添加到其他地方后的效果
| 被使用的方式 | 使用的结果 | ||
|---|---|---|---|
| 1 | 一个视图实例添加到一个vc上的两个container上 | 视图只显示在后面设置的位置 | |
| 2 | 一个视图实例添加到不同viewController | 视图只显示在后面设置的位置。则代表回来的时候需要重新更新视图位置。 |
二、加载中,加载慢
方案1: 进行 Scheme 拦截,利用 WKURLSchemeHandler
解决:加载自定义图片,将webView的图片数据改为app的图片数据(Flutter现有的webview_flutter暂时不支持,如要需要自己修改)
参考文档:
「拒绝踩坑」唯一一种拦截 WKWebView 资源请求的方式 => 「最佳实践」WebView 预加载与资源缓存 => WKWebView实现请求拦截,http/https/file等 => WKURLSchemeHandler 的能与不能 代码示例见 ViewController.m
h5中
1 | <h3 id="photo"> 自定义图片:canineschool-avatar://photo </h3> |
app中
1 | let configuration = WKWebViewConfiguration() |
[WKURLSchemeHandler 拦截https时引起网络请求也被拦截了,如何重定向](#WKURLSchemeHandler 拦截https时引起网络请求也被拦截了,如何重定向)
方案2: 下载并解压zip,app加载本地静态文件
在WebView中加载本地资源:使用file协议在WebView中加载沙盒目录中的资源。
跨域问题:
方法1:在后端设置CORS(跨源资源共享)策略,允许来自特定来源的请求。
方法2:资源通过js来app中获取再回到web中显示。
方案3:在app中内置一个 WebServer
其他离线方案:
思考:小程序
小程序并不是一个简单的、优化过的网页,而是一个混合架构,可以理解为:
1 | 小程序 = Native原生容器 + WebView渲染 + JS引擎 + 离线包机制 |
小程序的页面栈管理,像小程序一样支持多页面:
- 每个页面独立的WebView
- 前进/后退时切换WebView而不是重新加载
- 保存每个页面的滚动位置和表单状态
小程序容器的核心精髓:
核心是要先下载离线包zip,然后用webview加载远程Url映射成用webview加载本地离线包中的html。html中的css、js、图片等资源也映射成本地?然后变成一个路由栈管理加载多个本地的html了?
三、打开后,白屏
1、白屏的原因及原理
-
iOS设备上造成白屏的真正原因只有两个:内存使用过度、进程通信机制出现错误。
2、白屏的解决
理想情况(不一定白屏都会调用到):
1 | //进程终止(内存消耗过大导致白屏) |
其他处理方式:
白屏的像素判断法:iOS WKWebView白屏的像素检测方法 或 iOS WKWebview 白屏的像素检测实现
webview截图 ==> 缩放图片,为了减少待会遍历的像素点 ==> 遍历像素点,判断白色像素占比超过95%则认定为白屏
1 | totalCount++; |
白屏的 webView的子视图WKCompositingView 不存在的判断法:白屏时 WKCompositingView 为空
附:WKCompositingView 也是我们在做 WebView同层渲染 的时候经常会遇到的类。
1 | // 判断是否白屏 |
其他解决方案:
| 白屏判断点 | 参考文章 | |
|---|---|---|
webView的 url 、 title 、 document.body.innerHTML 为空 |
WKWebView 白屏处理 |
四、使用中的交互
1、常规
WKWebView 注入js代码: 001-UIKit-CQDemo-Flutter 中的 flutter_webview_kit
2、全局webView下如何正确返回前一页
见 《webview的一生.graffle》中的【全局webView下如何正确返回前一页】版面
五、WebView拦截请求的问题
阿里云官方文档: WKWebView使用私有API进行注册拦截请求
1、WKURLSchemeHandler 拦截https时引起网络请求也被拦截了,如何重定向
当使用WKURLSchemeHandler拦截https的图片时候,其自然的也会把网络请求也拦截了,需要将那些网络请求重定向。
为了区分出不同的https,可以通过获取url地址的扩展名,如 jpg、png、txt、MP4等。
1 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { |
六、WebView同层渲染
原理:
同层组件的目标是将原生组件渲染在与其他 Web 组件同一层级中。
在 iOS 中,我们使用 WKWebView 来创建 Web 视图,通常 WKWebView 内核会将多个组件共同渲染到同一个 WKCompositingView 上,这个 View 是一个原生的 UIView 子类。但是如果某个 HTML 标签的 style 设置了 overflow: scroll 属性,并且内容超出容器的大小,WKWebView 就会为其单独的创建一个 WKChildScrollView,因此我们可以找到这些 View,并和对应的 Web 组件一一关联起来,就可以将原生的组件渲染到这个 View 中,从而实现同层渲染。
1、同层渲染需求背景:
Webview 资源离线化之后不需要从后端拉取资源,省去了资源加载耗时减少等待时间,用户体验得到了明显提升,但受限于 Webview 的天生限制在某些场景下仍然能感受到与原生明显的体验落差。比如输入框弹出键盘时的页面卡顿、图片较多的页面内存占用过从后台回来前台时页面被重新加载、WebP 图片支持碎片化、图片不能跨页共享内存、视频播放组件体验不佳等问题。在 Webview 本身受限的情况下,如何在体验上向原生靠近是我们不得不面对的问题。如果能将影响用户体验的关键可交互组件用原生组件进行替换可以从根本上解决体验不如原生的问题。这部分内容我称为: WebView原生化。
Webview 原生化是指把 Webview 内部分占位 DOM 元素用原生组件进行替换,原生组件与原始 Webview 混合便得到了用户看到的最终界面。
组件替换有两种方式,
蒙层方案:在 Webview 的直接上层添加原生视图蒙层,把原生组件添加到蒙层上占位 DOM 对应的位置。缺点:当一个原生组件被添另到 Webview 上时它永远处顶层,不会被 Webview 内的弹窗覆盖,在滚动时原生组件会盖住原本应被显示的区域。
同层渲染方案:另一种是将原生组件添加到 Webiview 渲染时与占位 DOM 对应的合成层(独立出来的原生视图)上,下面会对比两种方案的差异。
以上引用内容摘自:微信小程序『同层渲染』技术是怎么回事? 强烈建议点开原文再看一遍。
2、同层渲染实现示例
关键问题:如何映射 DOM 到原生视图?如何在原生视图树中查找 「DOM」
「同层渲染」则是将原生组件直接渲染到 WebView 层级上。
2.1、入门简单实现
-
具体Demo示例可见SameLayerRender
实现步骤:
1、创建一个可以生成WKChildScrollView原生组件的 DOM 节点
2、传递给客户端查找到该 DOM 节点对应的 WKChildScrollView 原生组件的必要信息
3、客户端根据传来的信息找到对应的原生组件,并将原生组件挂载到该 WKChildScrollView 节点上
2.2、微信小程序实现
( 微信 Skyline 渲染引擎类似 Flutterr 的 SkyEngine)


3、其他参考文档
容器 URL 统一化建设(一个 URL 地址可按需动态配置渲染成 H5、Native 等页面),小程序容器建设,Flutter 容器建设,容器的快照缓存、预渲染、混合渲染等优化与能力相关建设
其他
优化待处理的文章
iOS WebView/H5 调试新姿势
附1:测试一个视图实例被添加后,再被添加到其他地方后的效果
测试代码:详见 AppCommonJSCollect 中的 SharedNormalView 、 SharedWebView
1.1、测试一个视图实例添加到一个vc上的两个container上
1.2、测试一个视图实例添加到不同viewController
阶段1:进入新页面的时候,使用之前共享的视图
阶段1结果:虽然第二页复用了第一页中的视图,但是从第二页回来的时候,第一页的视图不见了。
阶段2:在阶段1的基础上,补充回来的时候(viewWillAppear),需要重新更新视图的布局
阶段2结果:视图显示正确了