WebView优化

[toc]

脑图与概览

点击查看 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

解决:加载自定义图片(Flutter现有的webview_flutter暂时不支持,如要需要自己修改)

参考文档:

h5中

1
<h3 id="photo"> 自定义图片:canineschool-avatar://photo  </h3>

app中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let configuration = WKWebViewConfiguration()
configuration.setURLSchemeHandler(MyCustomSchemeHandler(), forURLScheme: "myScheme")
let webView = WKWebView(frame: .zero, configuration: configuration)
// 当WKWebView在加载一个使用"myScheme://"的资源时,就会调用我们的 MyCustomSchemeHandler 来获取数据。


class MyCustomSchemeHandler: NSObject, WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
var data: Data?
var mimeType = "application/octet-stream" // 默认MIME类型

let url = urlSchemeTask.request.url!
// 判断URL的文件扩展名,如果是图片才进行特殊处理,即将webView的图片数据改为app的图片数据
if (["png", "jpg", "jpeg"].contains(url.pathExtension.lowercased()) {
data = SDWebImageManager.shared.imageData(forKey: url.absoluteString, options: [])
mimeType = url.pathExtension.lowercased() == "png" ? "image/png" : "image/jpeg"
} else { // 对于非图片类型,使用默认响应
}

// 创建URL响应,向WKWebView提供响应和数据
let response = URLResponse(url: url, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil)
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data: validData)
urlSchemeTask.didFinish()
}

func webView(_ webView: WKWebView, stop urlSchemTask: WKURLSchemeTask) {

}
}

WKURLSchemeHandler 拦截https时引起网络请求也被拦截了,如何重定向

方案2: 下载并解压zip,app加载本地静态文件

在WebView中加载本地资源:使用file协议在WebView中加载沙盒目录中的资源。

跨域问题:

方法1:在后端设置CORS(跨源资源共享)策略,允许来自特定来源的请求。

方法2:资源通过js来app中获取再回到web中显示。

方案3:在app中内置一个 WebServer

其他离线方案:

iOS离线静态资源包技术方案分析

Flutter下实现WebView拦截加载离线资源

三、打开后,白屏

1、白屏的原因及原理

  • 深入理解WKWebView白屏

    iOS设备上造成白屏的真正原因只有两个:内存使用过度、进程通信机制出现错误。

2、白屏的解决

webview_blank_flow

理想情况(不一定白屏都会调用到):

1
2
3
4
//进程终止(内存消耗过大导致白屏)
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
[webView reload];
}

其他处理方式:

白屏的像素判断法:iOS WKWebView白屏的像素检测方法iOS WKWebview 白屏的像素检测实现

webview截图 ==> 缩放图片,为了减少待会遍历的像素点 ==> 遍历像素点,判断白色像素占比超过95%则认定为白屏

1
2
3
4
5
totalCount++;
if (red == 255 && green == 255 && blue == 255) {
whiteCount++;
}
// 占比 proportion = (float)whiteCount/totalCount; 如果 占比 >0.95 则认为白屏

白屏的 webView的子视图WKCompositingView 不存在的判断法:白屏时 WKCompositingView 为空

附:WKCompositingView 也是我们在做 WebView同层渲染 的时候经常会遇到的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 判断是否白屏
- (BOOL)isBlankView:(UIView*)view { // YES:blank
Class wkCompositingView = NSClassFromString(@"WKCompositingView");
if ([view isKindOfClass:[wkCompositingView class]]) {
return NO;
}
for(UIView*subView inview.subviews) {
if (![self isBlankView:subView]) {
return NO;
}
}
return YES;
}

其他解决方案:

白屏判断点 参考文章
webView的 urltitledocument.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
2
3
4
5
6
7
8
9
10
11
12
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
// 调用 _didPerformRedirection:newRequest: 执行重定向
// 拆成 _didPerf ormRe dire ctio n:n ewRe que st: 后倒序得到如下
NSArray *privateSelStrArr = @[@"st:", @"que", @"ewRe", @"n:n", @"ctio", @"dire", @"ormRe", @"_didPerf"];
NSString *selName = [[[privateSelStrArr reverseObjectEnumerator] allObjects] componentsJoinedByString:@""];
SEL sel = NSSelectorFromString(selName);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.schemeTask performSelector:sel withObject:response withObject:request];
#pragma clang diagnostic pop
completionHandler(request);
}

六、WebView同层渲染

1、同层渲染需求背景:

Webview 资源离线化之后不需要从后端拉取资源,省去了资源加载耗时减少等待时间,用户体验得到了明显提升,但受限于 Webview 的天生限制在某些场景下仍然能感受到与原生明显的体验落差。比如输入框弹出键盘时的页面卡顿、图片较多的页面内存占用过从后台回来前台时页面被重新加载、WebP 图片支持碎片化、图片不能跨页共享内存、视频播放组件体验不佳等问题。在 Webview 本身受限的情况下,如何在体验上向原生靠近是我们不得不面对的问题。如果能将影响用户体验的关键可交互组件用原生组件进行替换可以从根本上解决体验不如原生的问题。这部分内容我称为: WebView原生化

Webview 原生化是指把 Webview 内部分占位 DOM 元素用原生组件进行替换,原生组件与原始 Webview 混合便得到了用户看到的最终界面。

组件替换有两种方式,
蒙层方案:在 Webview 的直接上层添加原生视图蒙层,把原生组件添加到蒙层上占位 DOM 对应的位置。

缺点:当一个原生组件被添另到 Webview 上时它永远处顶层,不会被 Webview 内的弹窗覆盖,在滚动时原生组件会盖住原本应被显示的区域。

同层渲染方案:另一种是将原生组件添加到 Webiview 渲染时与占位 DOM 对应的合成层(独立出来的原生视图)上,下面会对比两种方案的差异。

以上引用内容摘自:微信小程序『同层渲染』技术是怎么回事? 强烈建议点开原文再看一遍。

2、同层渲染实现示例

关键问题:如何映射 DOM 到原生视图?如何在原生视图树中查找 「DOM」

「同层渲染」则是将原生组件直接渲染到 WebView 层级上。

2.1、入门简单实现

实现步骤:

1、创建一个可以生成WKChildScrollView原生组件的 DOM 节点

2、传递给客户端查找到该 DOM 节点对应的 WKChildScrollView 原生组件的必要信息

3、客户端根据传来的信息找到对应的原生组件,并将原生组件挂载到该 WKChildScrollView 节点上

2.2、微信小程序实现

Skyline 渲染引擎 /概览 /介绍

( 微信 Skyline 渲染引擎类似 Flutterr 的 SkyEngine)

微信小程序 原生组件说明

image-20240903234945043

image-20240903234704466

3、其他参考文档

【必读】前端需要懂的 APP 容器原理

容器 URL 统一化建设(一个 URL 地址可按需动态配置渲染成 H5、Native 等页面),小程序容器建设,Flutter 容器建设,容器的快照缓存、预渲染、混合渲染等优化与能力相关建设

其他

优化待处理的文章

APP中WebView性能优化

iOS WebView/H5 调试新姿势

iOS WebView/H5 调试新姿势

附1:测试一个视图实例被添加后,再被添加到其他地方后的效果

测试代码:详见 AppCommonJSCollect 中的 SharedNormalView 、 SharedWebView

1.1、测试一个视图实例添加到一个vc上的两个container上

ViewAddInOneViewController

1.2、测试一个视图实例添加到不同viewController

阶段1:进入新页面的时候,使用之前共享的视图

阶段1结果:虽然第二页复用了第一页中的视图,但是从第二页回来的时候,第一页的视图不见了。

ViewAddInDiffViewController1

阶段2:在阶段1的基础上,补充回来的时候(viewWillAppear),需要重新更新视图的布局

阶段2结果:视图显示正确了

ViewAddInDiffViewController2

End