背景及问题
APP Hybrid h5 页面,h5 页面使用 Vue3、Vue Router。
APP 使用 WebView 实现的 jsbridge 进行通信,因为 APP <-> 服务端之间本来就可以通信,进而实现了:Web - APP - 服务端 之间的通信。
PC 版本出现一个问题,当打开页面的时候,前端无法获取数据。 测试环境刷新可以。但是我本地刷新不行。之前,安卓、iOS 版本均没有这个问题。
过程分析
一开始,前端基本确定是 WebView 没有返回响应数据。通过附录代码 Web 中的使用就知道,jsBridge 的实现在 WebView SDK,前端只是负责发起请求,然后通过 callback 拿到响应数据。前端也确实记录到发起了请求,但是没有得到响应数据,说明 WebView 出问题了。
前端 Vue 页面的流程是:首先进入首页,请求接口 A,获取菜单数据,然后跳转到第一个页面,使用 router.replace
,进行跳转,进入页面之后请求接口 B、C。
但是刷新时,应该不会进行路由跳转,会请求接口 A、B。
每一次打开页面,A 接口数据返回成功,B、C 未成功,说明对于 A 接口,postMsgToNative、UniSDKNativeCallback 有可能都在页面未判断加载完成之前,所以链路是通的。对于 B 接口,可能在页面加载完成之前进行了 postMsgToNative,然后 WebView 判断页面加载完成,然后 UniSDKNativeCallback 无法返回数据。
最终查到的原因是,WebView 会在页面 load 完成时进行初始化,导致 NTCallBackList 被清空。
前端页面会进行 2 次 load 完成的判断,但是页面中也没有嵌入子页面,先分析一下现有的现象是否能解释的通?
首先,页面 load 完成会进行初始化,导致 NTCallBackList 清空。
打开页面两次初始化,在首页时,第一次加载完成才进行 A 的 postMsgToNative、UniSDKNativeCallback,跳转页面之后,接口 B 的 postMsgToNative,然后加载完成,于是 UniSDKNativeCallback 出现问题。
刷新时,两次初始化都在 postMsgToNative 之前,不会有问题。
页面为什么会加载完成 2 次?
WebView 是如何判断页面加载完成的?
刷新时为什么 A、B 的链路都通了,也就是两次加载完成的判断都在发起请求之前?
问了一下 ChatGPT:
- 初始加载和异步组件: 在 Vue 应用中,页面的初始加载和后续的组件异步加载可能导致多次触发“加载完成”的事件。特别是在使用懒加载或动态组件时,Vue 会在初始页面加载和每个动态组件加载完成时触发事件。
- 路由导航: Vue Router 的路由导航可能引起额外的页面加载事件。特别是在使用
mode: 'history'
的情况下,有时会导致页面被重新渲染,从而看似多次加载。- 页面资源加载: 某些外部资源(如脚本、样式表或图像)的异步加载或错误加载重试,也可能导致页面被误认为多次加载完成。
- Cef 的加载检测: 在 CEF 中,
OnLoadingStateChange
和OnLoadEnd
事件可能会在页面的不同阶段被多次触发。例如,页面的主文档加载完成可能触发一次,而所有的子资源(如图片、脚本)加载完成又可能触发一次。- 刷新或重复请求: 如果页面上有脚本或逻辑在特定事件中刷新或重新加载页面,这也可能导致多次加载完成的事件。
附代码
Web 中的使用:
window.UniSDKJSBridge.postMsgToNative({
methodId : "openBrowser",
reqData : {"webURL":"https://www.baidu.com"},
callback :
{
nativeCallback : function(respJSONString)
{
document.getElementById('Result_Show').innerHTML = respJSONString;
},
}
});
jsBridge 的实现部分代码:
window.NTCallBackList = {};
window.UniSDKJSBridge = {
postMsgToNative:postMsgToNative,
};
String.prototype.hashCode = function() {
var hash = 0;
if (this.length == 0) return hash;
for (var index = 0; index < this.length; index++) {
var charactor = this.charCodeAt(index);
hash = ((hash << 5) - hash) + charactor;
hash = hash & hash;
}
return hash;
};
window.UniSDKNativeCallback = function(identifier, respJSONString) {
console.log('window.NTCallBackList: ', window.NTCallBackList);
if (window.NTCallBackList[identifier] && window.NTCallBackList[identifier].nativeCallback) {
window.NTCallBackList[identifier].nativeCallback(respJSONString);
window.NTCallBackList[identifier] = null;
delete window.NTCallBackList[identifier];
}
};
function postMsgToNative(bridgeObjc) {
var dataString = encodeURIComponent(JSON.stringify(bridgeObjc.reqData));
var timestamp = Date.parse(new Date());
var identifier = (bridgeObjc.methodId + dataString + timestamp).hashCode().toString();
if (identifier.length > 0 && bridgeObjc.callback)
{
window.NTCallBackList[identifier] = bridgeObjc.callback;
bridgeObjc['identifier'] = identifier;
bridgeObjc['callback_id'] = identifier;
}
window.mwsInvoke({
request:'unisdk_js_native_call:' + JSON.stringify(bridgeObjc),
onSuccess: function(response) {},
onFailure: function(error_code, error_message) {}
});
}