记一次 WebView 问题排查

136 阅读3分钟

背景及问题

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:

  1. 初始加载和异步组件: 在 Vue 应用中,页面的初始加载和后续的组件异步加载可能导致多次触发“加载完成”的事件。特别是在使用懒加载或动态组件时,Vue 会在初始页面加载和每个动态组件加载完成时触发事件。
  2. 路由导航: Vue Router 的路由导航可能引起额外的页面加载事件。特别是在使用 mode: 'history' 的情况下,有时会导致页面被重新渲染,从而看似多次加载。
  3. 页面资源加载: 某些外部资源(如脚本、样式表或图像)的异步加载或错误加载重试,也可能导致页面被误认为多次加载完成。
  4. Cef 的加载检测: 在 CEF 中,OnLoadingStateChange 和 OnLoadEnd 事件可能会在页面的不同阶段被多次触发。例如,页面的主文档加载完成可能触发一次,而所有的子资源(如图片、脚本)加载完成又可能触发一次。
  5. 刷新或重复请求: 如果页面上有脚本或逻辑在特定事件中刷新或重新加载页面,这也可能导致多次加载完成的事件。

附代码

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) {}
                     });
}