一、引言
微信小程序生态发展现状
在当今数字化的时代浪潮中,微信小程序已然占据了极为重要的地位。微信作为国内拥有庞大用户基数的社交平台,其活跃用户数量超 10 亿,而小程序依托微信这一强大载体,受众范围极为广泛。几乎各行各业的 APP 都会去拓展小程序端的应用,从生活服务类的美团外卖、饿了么,到金融理财领域的诸多应用,再到电商购物平台等,小程序都随处可见。
据第三方专业数据机构 QuestMobile 发布的《2024 微信小程序年度报告》显示,截至 10 月份,微信小程序用户规模达到 9.49 亿,月人均使用时长达 1.7 小时,月人均使用次数近 70 次,同比分别增长了 15.1%、5.2%。并且,小程序覆盖的生活、消费、娱乐场景还在持续增加,比如在生活服务方面,月活跃用户规模能达到 8.9 亿;移动购物领域,月活跃用户规模达到 8.78 亿;金融理财方面,月活跃用户规模也达到 8.64 亿,同比均有显著增长。同时,像移动视频、医疗服务等领域的用户量更是处于高速增长阶段。
小程序之所以如此受欢迎,得益于它自身诸多的优势。它无需安装,用户只需扫一扫或者搜一搜即可使用,还具有即用即走的特点,符合现代人快速的生活节奏。而且相对传统的手机应用开发来说,小程序开发成本和周期更低,开发者基于微信平台,共享微信的用户数据和社交关系链,能更快速地开发出满足需求的应用,同时还能实现跨平台的兼容性,一次编写代码可在多平台运行。另外,小程序还可以充分利用微信的社交关系链进行传播,例如通过微信好友、朋友圈、公众号等渠道分享,让其应用场景不断拓展。
然而,在小程序的开发与应用过程中,也会面临一些技术方面的问题需要去攻克,其中小程序与 webview 的 html 通信就是值得深入探讨的重要环节。由于小程序和 webview 在不同的环境下运行,各自有着相应的机制和规则,二者之间如何实现高效、准确的通信,关乎着很多功能能否顺利实现,以及用户体验能否得到保障,所以对这一通信问题的研究和探索是十分必要的。
二、微信小程序与 webview 通信基础
(一)webview 组件介绍
在微信小程序中,webview 组件扮演着极为重要的角色,它是实现小程序与 html 通信的关键元素之一。
首先从 dom 布局来讲,webview 组件在小程序页面的布局上有着自身特点,它通常会自动铺满整个小程序页面。例如,在常见的小程序页面代码结构中,像下面这样使用 webview 组件:
<view class='container'>
<web-view wx:if="{{url}}" class='webview' src="{{url}}" bindload="bindload" binderror="binderror" bindmessage="bindmessage"></web-view>
</view>
可以看到,webview 组件根据条件(这里通过wx:if判断url是否有值来决定是否渲染)展示在对应的容器内,并占满可用空间。
然后再看看它的相关属性,src属性是一个很关键的属性,其类型为 String,用于指定要加载的网页链接,也就是一个网站的 url,默认值是 none,它明确了 webview 要指向的网页内容。比如我们想在小程序中展示某个具体网页,可以将对应的合法 https 链接赋值给src属性,像<web-view src="Example Domain"></web-view>这样的代码,就能让小程序加载展示Example Domain这个网页内容(需注意要配置好相关域名白名单等条件)。
除此之外,还有像bindload、binderror、bindmessage等常用属性。bindload属性对应的事件会在网页加载成功时候触发,在对应的 js 文件中可以这样定义处理函数来响应这个事件:
bindload() {
console.log('webview load success');
}
binderror属性对应的事件则在网页加载失败的时候触发,处理函数示例如下:
binderror() {
console.log('webview load error');
}
而bindmessage属性在网页向小程序发送消息时起到关键作用,不过它的触发是有特定时机的,例如小程序后退、组件销毁、分享等场景下,小程序端才能接收到消息,在 js 文件中接收处理函数可以这么写:
bindmessage(e) {
console.log(e.detail.data);
}
在对应的 js 文件中,对于 webview 组件也有一系列的基本设置情况。比如在小程序页面的 js 文件里,我们需要对 webview 相关的数据以及事件进行合理的配置和管理。像在页面加载(onLoad生命周期方法)时,可以根据传入的参数来设置 webview 组件的src属性值,示例代码如下:
Page({
data: {
url: ''
},
onLoad(options) {
// 使用从其他页面传入的参数来设置webview的src
let params = JSON.parse(options.params);
this.setData({
url: params.url
});
}
});
总之,webview 组件在小程序中的使用需要综合考虑 dom 布局、属性运用以及 js 文件中的相关设置,这些都是后续实现与小程序之间通信的基础内容。
(二)通信方式概述
在微信小程序开发中,小程序原生页面和 webview 页面之间常常需要进行双向通信,以实现更丰富的功能交互。
从原生页面到 webview 页面的通信方面来看,本质上 webview 也是小程序的一个页面,所以小程序到 webview 可以按照正常的小程序间的通信方式来操作。例如,通过wx.navigateTo、wx.redirectTo等方法,带上url参数以及query参数(query参数就如同正常url的参数一样跟在后面),然后在 webview 的页面的Page实例里面通过onLoad方法的参数来获取url的值,进而设置给 webview 的src属性为该值即可。
而从 webview 页面到小程序原生页面通信时,由于在 webview 中的跳转通常是在src对应的网页中的操作来处理的,所以需要结合jssdk来完成。具体来说,不需要wx.config配置,直接通过script标签引入https://res.wx.qq.com/open/js/jweixin-1.3.0.js,之后就可以使用wx.miniProgram.navigateTo、wx.miniProgram.navigateBack、wx.miniProgram.switchTab、wx.miniProgram.reLaunch、wx.miniProgram.redirectTo等接口来实现跳转,不过要注意的是这种跳转只能在当前小程序页面内进行。并且在网页向小程序发送消息时,还可以通过wx.miniProgram.postMessage向小程序发送消息,小程序端通过bindmessage事件来接收消息,但接收消息存在特定触发场景,比如小程序后退、组件销毁、分享时等才会触发接收。
这些常规的通信实现途径只是基础,后续我们还会深入探讨在实际应用中可能遇到的各种细节问题以及相应的解决方案,来确保小程序与 webview 之间通信的高效性和准确性。
三、小程序原生页面到 webview 页面通信
(一)页面跳转实现
在微信小程序开发中,从小程序原生页面跳转到 webview 页面是比较常见的操作,以下通过代码示例来说明具体的实现过程以及参数传递和数据装载的方式。
假设我们有一个小程序原生页面 A,现在要跳转到包含 webview 组件的页面 B,并且要传递一些参数过去。我们可以使用wx.navigateTo方法来进行页面跳转,示例代码如下:
let params = {
a: 1,
b: 2
};
wx.navigateTo({
url: `/pages/webview/index?params=${JSON.stringify(params)}`
});
在上述代码中,params对象就是我们要传递的参数,通过JSON.stringify方法将其转换为字符串形式,拼接到url后面。
当跳转到目标 webview 页面 B 后,需要在该页面的onLoad生命周期方法中获取并处理传递过来的参数,然后设置 webview 的src属性来装载对应的网页数据,示例如下:
Page({
data: {
url: ''
},
onLoad(options) {
// 使用从A页面传入的参数来设置webview的src
let params = JSON.parse(options.params);
this.setData({
url: params.url
});
}
});
这里的options参数就是包含了从上个页面传递过来的所有参数信息,我们从中解析出需要的参数,进而可以根据业务需求来动态设置 webview 要加载的网页地址,完成数据的装载,使得 webview 页面能正确展示出期望的内容。
(二)通信注意要点
在小程序原生页面到 webview 页面通信的过程中,有几个容易出现问题的地方需要特别留意。
首先是参数传递格式方面,要确保传递的参数能够被正确序列化和反序列化。比如使用JSON.stringify将对象参数转换为字符串进行传递时,参数对象中的数据类型要符合 JSON 规范,像函数等不可序列化的数据类型是不能直接传递的,如果包含了这类数据可能会导致传递失败或者在接收端解析出错。并且在接收参数的 webview 页面,一定要准确地使用JSON.parse进行反序列化操作,否则无法正确获取参数内容。
在数据接收处理环节,要注意对接收数据的验证和容错处理。例如传递过来的参数可能由于网络问题或者上个页面逻辑异常等情况出现不完整或者不符合预期格式的问题,此时在 webview 页面接收参数后,需要进行必要的格式校验,比如判断参数是否为null或者undefined,对于一些应该是数值类型的参数要验证是否确实是有效的数字等。另外,当根据参数来设置 webview 的src属性加载网页时,如果src的值不符合要求(比如不是合法的 https 域名地址、未在微信公众平台配置白名单的域名等),则会导致网页加载失败,所以要提前做好相关的合法性检查以及错误提示等逻辑,保障用户体验。同时,还要考虑到网络延迟等情况对数据加载的影响,避免因长时间等待数据加载而出现页面卡顿或者空白等不良现象,可以适当添加一些加载提示动画等交互元素,告知用户当前的页面状态。
四、webview 页面到小程序原生页面通信
(一)借助 JSSDK 跳转通信
在实际项目中,比如常见的 react 项目里,我们来看看在 webview 页面中如何利用 JSSDK 向小程序原生页面进行跳转并实现通信。
首先,在 webview 对应的 html 页面中,我们需要引入 JSSDK,代码如下:
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js" defer></script>
引入之后,我们就可以在相应的 js 代码逻辑里使用微信小程序提供的相关接口来进行跳转操作了。例如,假设我们要从 webview 页面跳转到小程序的某个具体原生页面,并且传递一些参数过去,代码示例如下:
componentDidMount() {
// 获取url中路由参数
let query = this.props.history.location.search.slice(1);
}
enter() {
let params = {
a: 1,
b: 2
};
wx.miniProgram.navigateTo({
url: `/pages/index/index?params=${JSON.stringify(params)}`
});
}
在上述代码中,componentDidMount 方法里可以先获取当前页面 url 中的路由参数(如果有需要的话),而 enter 方法里则定义了要传递的参数 params,通过 wx.miniProgram.navigateTo 接口进行页面跳转,将参数拼接到 url 后面传递给目标小程序原生页面。
这里要注意的是,像 wx.miniProgram.navigateTo、wx.miniProgram.navigateBack、wx.miniProgram.switchTab、wx.miniProgram.reLaunch、wx.miniProgram.redirectTo 这些接口的使用是有一定规则的,它们只能在当前小程序页面内进行跳转操作,开发者在使用时要确保符合相应的场景需求,这样才能准确地实现从 webview 页面到小程序原生页面的跳转通信。
(二)返回按键刷新问题及解决方案
当 webview 页面跳转到小程序原生页面后,按左上角返回按键时,webview 页面的刷新方面往往会存在一些问题,这里我们来详细分析一下,并讲讲对应的解决方案。
目前常见的有两种处理方案,分别是记录路由栈和不记录路由栈。
记录路由栈方案:在 <web-view> 页面的 onShow 方法中进行操作,示例代码如下:
onShow() {
this.setData({
url: params.url
});
}
这种操作方式下,web-view 会记录自己的路由栈,每执行一次 setData,路由栈就会增加一级。此时在 <web-view> 页面按返回按钮,回退的是 <web-view> 自己的路由栈,而不是小程序自身的路由栈。这样做的好处是能够比较细致地记录页面的访问路径,方便在页面逻辑中根据路由栈情况做一些相应处理,比如可以实现多层级的页面返回逻辑,适用于一些有复杂页面层级关系且需要精确控制回退页面展示内容的业务场景。不过缺点就是路由栈如果不断增加,可能会占用一定的内存资源,而且在某些特定业务下,可能不符合我们期望的直接回退到小程序特定页面的需求。
不记录路由栈方案:代码示例如下:
onShow() {
this.setData({
url: ''
}, () => {
this.setData({
url: params.url
});
});
}
这种操作是先清空 url,由于 wx:if="{{url}}" 会销毁 <web-view> 组件,然后在回调中重新 setData,相当于初始化 <web-view> 组件,<web-view> 自身路由栈不会增加。此时在 <web-view> 页面按返回按钮,回退的是小程序自身的路由栈。优点在于能够简洁地实现回退到小程序原生页面的逻辑,避免了路由栈不断累积的问题。但它也有不足的地方,部分机型存在 bug,例如在 iPhone X 和 iPhone XS 等机型搭配的 iOS 12.4.1 系统下,webview 的 url 先清空后赋值的方案中在 binderror 中会报错,报错信息为 insertHTMLWebView:fail only one webView component,这就需要开发者在实际应用中针对这些特定机型做额外的兼容处理或者考虑其他替代方案。
(三)终极刷新不记录路由栈方案
下面介绍一种解决从小程序原生页面回退到 webview 中,刷新 webview 并不记录 webview 自身路由栈问题的终极方案。
在具体代码实现上,首先在 onLoad 生命周期方法中,让 webview 需要的 url 从公共数据 app.globalData 中读取,示例如下:
onLoad(options) {
this.setData({
url: app.globalData.url
});
}
这样做可以减少页面间通信的耦合度,让数据获取更加统一和规范。
然后在 onShow 方法里添加如下逻辑:
onShow() {
if (this.firstEnter) {
this.firstEnter = false;
return;
}
wx.redirectTo({
url: 'index'
});
}
不过需要注意的是,这个方案会触发 onUnload 事件,如果之前在 onUnload 事件中有相关的业务逻辑,那么就需要针对这些逻辑做好兼容处理,避免出现意想不到的问题,比如数据丢失或者页面状态异常等情况。
通过这样一套方案的实施,能够较好地解决从小程序原生页面回退到 webview 页面时,刷新且不记录路由栈相关的一系列问题,提升小程序与 webview 页面之间交互的流畅性和稳定性,为用户带来更好的使用体验。
五、通信常见故障及解决办法
(一)消息接收不到问题
在开发过程中,有时会遇到从 h5 页面给小程序发送消息,但小程序却接收不到的情况,下面就来分析一些常见的故障场景以及对应的排查思路和解决办法。
例如在 uniapp 中,有从 h5 页面给小程序传递消息的需求时,直接发送消息往往不起作用。这时,首先要检查 h5 页面的相关设置。常见的解决办法是在 h5 页面的 index.html 中引入 js-sdk(加入 body 下面),代码如下:
<script type="text/javascript">
var userAgent = navigator.userAgent;
if (userAgent.indexOf('AlipayClient') > -1) {
// 支付宝小程序的 JS-SDK 防止404 需要动态加载,如果不需要兼容支付宝小程序,则无需引用此 JS 文件。
document.writeln('<script src="https://appx/web-view.min.js">'+'</'+'script>');
} elseif (/QQ/i.test(userAgent) && /miniProgram/i.test(userAgent)) {
// QQ 小程序
document.write('<script type="text/javascript" src="https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js"></script>');
} elseif (/miniProgram/i.test(userAgent) && /micromessenger/i.test(userAgent)) {
// 微信小程序 JS-SDK 如果不需要兼容微信小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>');
} elseif (/toutiaomicroapp/i.test(userAgent)) {
// 头条小程序 JS-SDK 如果不需要兼容头条小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js"></script>');
} elseif (/swan/i.test(userAgent)) {
// 百度小程序 JS-SDK 如果不需要兼容百度小程序,则无需引用此 JS 文件。
document.write('<script type="text/javascript" src="https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.18.js"></script>');
} elseif (/quickapp/i.test(userAgent)) {
// quickapp
document.write('<script type="text/javascript" src="https://quickapp/jssdk.webview.min.js"></script>');
}
if (!/toutiaomicroapp/i.test(userAgent)) {
document.querySelector('.post-message-section').style.visibility ='visible';
}
</script>
<!-- uni 的 SDK -->
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script>
<script type="text/javascript">
document.addEventListener('UniAppJSBridgeReady', function() {
// 在这里测试是否接收到uni.webView.postMessage({data: {type: 'back'}});
uni.webView.getEnv(function(res) {
console.log('当前环境:'+ JSON.stringify(res));
});
uni.webView.back();
});
</script>
同时,在 uniapp 小程序页面中,要正确使用<web-view>组件来接收消息,像这样:
<web-view src="h5地址" @message="onMessage" >
</web-view>
然后在对应的 methods 里面定义onMessage方法来处理接收到的消息,示例如下:
onMessage(event) {
console.log('Received message from H5:',event);
}
总的来说,关键步骤就是引入相关的兼容模块,在 webview 中的 h5 页面添加相关兼容 sdk 后,往往就能解决消息接收不到的问题。
另外,还要注意一些细节情况。比如有的开发者可能错误地使用了事件名称,在微信小程序中,要接收来自 H5 页面的消息,需要使用 webview 组件的bindmessage
事件,而不是message。在小程序的 wxml 文件中,要确保正确使用了<web-view>组件,并为其绑定message事件,示例如下:
<webview id="myWebview" src="你的H5页面URL" bindmessage="onMessage">
</webview>
在对应的 js 文件中,实现onMessage事件处理函数,代码如下:
Page({
onMessage: function(e) {
console.log(e.detail.data); // 这里将打印出从H5页面传递过来的数据
}
});
同时,要确保 H5 页面和小程序页面在同一域下,因为出于安全考虑,跨域的消息传递可能会被阻止。如果按照上述步骤操作后仍然无法接收消息,还需要进一步检查:比如查看 webview 组件的src属性是否正确指向了 H5 页面的 URL,确保 H5 页面中的postMessage方法调用没有错误,查看小程序的控制台输出,看是否有其他错误信息等,通过这些全面的排查来解决消息接收不到的问题。
(二)微信 PC 端显示问题
在微信 PC 端使用小程序的 web-view 时,常常会遇到一些兼容性问题,其中比较典型的就是 web-view 不显示的情况。
原因在于微信 PC 端小程序使用的引擎有兼容性问题,不支持页面的onTabItemTap声明周期钩子方法,所以在此方法内写的显示 web-view 的语句不会执行。
解决方法是放弃使用onTabItemTap,可以采用 store 或自定义事件监听等曲线方式来实现相应需求。
另外,需要注意的是,当前微信开发者工具的 PC 端真机调试不支持忽略 web-view 域名校验,所以在测试时可能会显示 “不支持打开非业务域名”。这就要求开发者在配置业务域名时要格外仔细,要确保所使用的域名已经添加到了白名单中,并且业务域名(即需要跳转的域名)需经过 ICP 备案,新备案域名需 24 小时后才可配置,域名格式只支持英文大小写字母、数字及 “-”,不支持 IP 地址等。
如果遇到企业主体的微信小程序在配置业务域名后,线上版本或体验版仍无法通过 web-view 组件访问相应外部链接的情况,要检查配置业务域名的相关操作是否准确无误,比如是否取消了所有第三方平台的授权,校验文件是否放置在需要跳转的域名的根目录下等步骤,按照正确流程配置好业务域名后,才能保障 web-view 在微信 PC 端可以正常显示并使用,帮助我们避开这些兼容性方面的坑洼,顺利进行小程序开发与调试工作。