creator-webview加载优化之Android篇
creator-webview加载优化之Android篇
前篇
- Android WebView shouldInterceptRequest - https://www.jianshu.com/p/7a237e7f055c
- Android WebView的性能问题及缓存机制、资源加载方案 - https://bbs.huaweicloud.com/blogs/219777
- Android WebView H5 秒开方案总结 - https://juejin.cn/post/7016883220025180191
使用 Android 的 webview 去加载一个 cocos 的 h5 游戏, 如果全部资源都有网络上请求的的话, 即使使用了缓存, 第一次加载还是避免不了加载全部资源的情况, 还是会很慢.
加载H5页面慢的原因
WebView显示H5页面存在一个很明显的性能问题: WebView加载H5页面很慢
加载H5页面慢的原因有:
- 渲染速度慢:
(1)首先是JS本身的解析过程复杂、解析速度慢;
(2)前端页面又涉及较多的JS代码文件,叠加起来就造成了JS解析效率低;
(3)其次是Android机型碎片化,导致手机硬件设备的性能不可控,有些表现良好,有些表现就较差。 - 页面资源加载慢,每加载一个H5页面都会产生较多网络请求:
(1)HTML的主URL请求;
(2)HTML引用外部的JS、CSS、字体文件、图片文件等都会构造一个独立的HTTP请求。
注:每次加载都会产生这么多的网络请求,会相当耗费流量。
解决方案
可以通过以下三种方案来解决WebView的性能问题:
- WebView的缓存机制
- 资源预加载
- 资源拦截
Android WebView 的缓存模式有以下4种:
- LOAD_CACHE_ONLY: 不使用网络,只读取本地缓存数据。
- LOAD_NO_CACHE: 不使用缓存,只从网络获取数据。
- LOAD_DEFAULT: (默认)根据cache-control决定是否从网络上取数据。
- LOAD_CACHE_ELSE_NETWORK:只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
思路 01 - 固定文件打包到包内
假设引擎版本不变的情况下, 引擎部分资源是不会变的, 如 cocos-js/cc.xxx.js, 这个也是大头文件, 大小是 2.3m, 所以可以把这个文件打包到包内, 通过拦截 webview 请求判断是否是这个文件, 是的话直接从包内读取这个文件 (实测 100ms 左右), 这可比网络请求快多了.
示例代码
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public class WebviewClientHelper extends BridgeWebViewClient {
public WebviewClientHelper(BridgeWebView webView) {
super(webView);
}
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
WebResourceResponse wrr = super.shouldInterceptRequest(view, request);
LogUtil.D("--- shouldInterceptRequest url: %s", request.getUrl().toString());
String url = request.getUrl().toString();
// 引擎固定的内容
if (url.indexOf("cocos-js") > 0) {
Pattern r = Pattern.compile("cocos-js/cc\\.\\w+\\.js");
Matcher m = r.matcher(url);
if (m.find()) {
String ccjsFile = "cc.e0cbd.js";
byte[] bts = FileTool.getAssetsFileBts(ActivityMgr.getIns().getActivity(), ccjsFile);
return new WebResourceResponse("text/html", "UTF-8", new ByteArrayInputStream(bts));
}
}
return wrr;
}
}
final WebView wv = new WebView(activity);
wv.setWebViewClient(new WebviewClientHelper(wv));
---
### 思路 02 - 使用 LOAD_CACHE_ELSE_NETWORK 缓存模式
这种缓存模式 只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
所以进入游戏不需要网络请求, 直接本地 io 缓存文件运行, 速度就会快很多, 但是有个问题就是如果网页更新了怎么办? 结合 cocos 的 ***MD5 缓存*** 模式, cocos 打出来的包, 只有 *index.html* 入口文件不会有文件名变化, 其他所有有变化的文件, 都自动拼上了 md5 的值, 如: *index.js* 会变成 *index.0s3s1.js*, 所以只需要对 *index.html* 进行拦截, 去请求最新网络上的内容即可
- 示例代码
```java
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
WebResourceResponse wrr = super.shouldInterceptRequest(view, request);
// 入口每次请求最新的
String url = request.getUrl().toString();
if (url.indexOf("index.html") > 0) {
// 因为使用了 LOAD_CACHE_ELSE_NETWORK:只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据。
// 结合 cocos 的机制, 每次更新只有 index.html 入口文件是不变的, 所以这个文件需要动态请求
HttpHelper.SHttp sh = HttpHelper.okhttpGetSync(MiscFuncApi.packDB.UrlB, null);
if (sh.code == 200) {
LogUtil.D("--- req [%s] success", MiscFuncApi.packDB.UrlB);
return new WebResourceResponse("text/html", "UTF-8", new ByteArrayInputStream(sh.bytes));
}
}
return wrr;
}
cdn 加速
- 上 cdn 加速, 开启 性能优化
- js, html, css 等开启优化
- 开启 Gzip (推荐) 或者 Brotli 压缩, Gzip 的适配性更好, 普遍都支持 例如 阿里云 cdn
webview 测试是否支持 gzip 或者 brotli
- How to enable brotli compression on Android System Webview? - https://stackoverflow.com/questions/65138909/how-to-enable-brotli-compression-on-android-system-webview
可以打开这个网站测试: https://www.cylog.org/headers/, 查看 Accept-Encoding
, 查看支持的压缩方式
实际测试, gzip 压缩都支持, br 压缩要求高版本的 chrome 内核才能支持, 所以技术上应该选择支持性更好的方式, 也就是 gzip 压缩方式.
如果 chrome 内核版本, 如 Chrome/122.0.0.0
, >= 120.0.0.0 版本的话, 就是支持 br 的
升级 Android System WebView
直接在 Google Play 上即可, 链接: https://play.google.com/store/apps/details?id=com.google.android.webview
踩坑
重写拦截方法后重定向失败
报错: 比如服务是 https://game.aaa.com , 重定向后是 https://www.aaa.com, 应该加载 https://www.aaa.com/lib/bbb.js, 但 webview 却去加载 https://game.aaa.com/lib/bbb.js 资源, 发现找不到
原因是重写 shouldInterceptRequest 方法, 当时的原因是想通过访问 index.html 文件是, 每次去拉服务器上最新的资源, 而不是使用 webview 本地的缓存资源, 已达到 cocos h5 更新的效果
1
2
3
4
5
6
7
8
9
10
11
12private static class WebviewClientHelper extends WebViewClient {
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
if (url.indexOf("index.html") > 0) {
if (rsp != null) {
return new WebResourceResponse("text/html", "UTF-8", new ByteArrayInputStream(rsp));
}
}
return super.shouldInterceptRequest(view, request);
}
}解决办法: 不重写这个方法, 让 webview 自己处理. cocos 更新问题的话, 通过服务器重定向返回新名字的 html 页面即可, 不能也旧的同名