uiapp套壳热更新实现方案(h5也是uiapp写的)
总体思路: 在HBuilder X同一个工程里打包apk和h5, apk代码中调用webview, webview加载的就是打包出的h5(h5放在nginx中)。apk和h5同一工程,通过条件编译来区分。//#ifdef APP-PLUS下的代码是打包apk需要的代码, // #ifdef H5 是打包h5需执行的代码。(apk和h5的代码也可以写在俩个工程里,就不需要用条件编译来区分)
具体实现:
1. pages.json中指定加载的首页, apk(即原生app)指定index为首页, 这个页面就加载了一个webview。 h5中指定具体的业务页面welcome为首页。
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages { // #ifdef APP-PLUS "path": "pages/login/index", // #endif // #ifdef H5 "path": "pages/login/welcome", // #endif }, ...
2. index页面加载webview代码, src中的地址就是打包出的h5, 通过nginx转发。
<template>
<view class="content">
<web-view :src="src" @message="onMessage"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
src: `http://192.168.2.148:18068/#/?timestamp=${new Date().getTime()}`,
}
},
methods: {
onMessage(event) {
}
}
</script>
以上代码也就实现了套壳技术,前端页面有调整只需要打包h5替换nginx下转发的包路径。
信心满满的交给测试同学,发现替换h5后不能自动更新,还需要去手机设置里找到应用然后清除缓存。这得解决啊,去网上看一下,选个最简单的方法就是webview每次请求都拼一个时间戳,这样就会每次都重新发起请求。

然后自测一下,发现还是不行。原来nginx访问html默认也是有缓存的,再配置一下nginx就可以了。如下图所示添加无缓存配置,大功告成,可以下班喽。

以上基本可以满足大部分客户要求,只要重新进应用就会加载最新的h5页面。但是有一个小小的缺陷,就是手机退到应用后台没杀死的情况再回到前台是不会触发webview加载的。 技术服务产品,产品服务用户,用户就是上帝。好的,安排。
最开始想的办法就是在App.vue里的onShow方法重新加载页面, 这样用户每次从后台切到前台都会重新加载。经测试拍照以及系统弹出的授权框都会触发onShow方法,本来想着通过增加变量去控制,但是第一次拍照时既弹出授权提示框,也会进拍照,触发俩次onShow, 很难区分。而且使用场景上也不符合用户, 从后台进前台每次都重新加载,用户无法切换应用记录信息。
条条大路通罗马,换个思路。我们可以在后端设置一个版本号, 手机里也对应设置一个版本号。如果监测到手机里的版本号低于后端版本号, 说明页面需要更新,这时再修改变量标志,通过这个变量标志去决定后台切换到前台是否要重新加载webview。还是上代码吧。
下面代码都是App.vue中的, 定义变量isFirstShow, 应用刚启动时也会触发onShow方法,避免二次加载。 定义全局变量needRefreshWebView, 需要更新webview时更新此变量。

因为我们用的是套壳技术, 这里面就需要h5和原生apk通信。因为是uiapp套壳uiapp打包的h5, 不能直接调用官网的方法去发消息,会覆盖。引入以下文件,取名webviewsdk.js。
// #ifdef H5 !(function (e, n) { 'object' == typeof exports && 'undefined' != typeof module ? (module.exports = n()) : 'function' == typeof define && define.amd ? define(n) : ((e = e || self).uni = n()) })(this, function () { 'use strict' try { var e = {} Object.defineProperty(e, 'passive', { get: function () { !0 }, }), window.addEventListener('test-passive', null, e) } catch (e) {} var n = Object.prototype.hasOwnProperty function t(e, t) { return n.call(e, t) } var i = [], a = function (e, n) { var t = { options: { timestamp: +new Date() }, name: e, arg: n } if (window.__dcloud_weex_postMessage || window.__dcloud_weex_) { if ('postMessage' === e) { var a = { data: [n] } return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(a) : window.__dcloud_weex_.postMessage(JSON.stringify(a)) } var o = { type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i }, } window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(o) : window.__dcloud_weex_.postMessageToService(JSON.stringify(o)) } if (!window.plus) return window.parent.postMessage( { type: 'WEB_INVOKE_APPSERVICE', data: t, pageId: '' }, '*' ) if (0 === i.length) { var r = plus.webview.currentWebview() if (!r) throw new Error('plus.webview.currentWebview() is undefined') var d = r.parent(), s = '' ;(s = d ? d.id : r.id), i.push(s) } if (plus.webview.getWebviewById('__uniapp__service')) plus.webview.postMessageToUniNView( { type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }, '__uniapp__service' ) else { var w = JSON.stringify(t) plus.webview .getLaunchWebview() .evalJS( 'UniPlusBridge.subscribeHandler("' .concat('WEB_INVOKE_APPSERVICE', '",') .concat(w, ',') .concat(JSON.stringify(i), ');') ) } }, o = { navigateTo: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, n = e.url a('navigateTo', { url: encodeURI(n) }) }, navigateBack: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, n = e.delta a('navigateBack', { delta: parseInt(n) || 1 }) }, switchTab: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, n = e.url a('switchTab', { url: encodeURI(n) }) }, reLaunch: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, n = e.url a('reLaunch', { url: encodeURI(n) }) }, redirectTo: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}, n = e.url a('redirectTo', { url: encodeURI(n) }) }, getEnv: function (e) { window.plus ? e({ plus: !0 }) : e({ h5: !0 }) }, postMessage: function () { var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {} a('postMessage', e.data || {}) }, }, r = /uni-app/i.test(navigator.userAgent), d = /Html5Plus/i.test(navigator.userAgent), s = /complete|loaded|interactive/ var w = window.my && navigator.userAgent.indexOf('AlipayClient') > -1 var u = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent) var c = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent) var g = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent) var v = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent) var p = window.qa && /quickapp/i.test(navigator.userAgent) for ( var l, _ = function () { ;(window.UniAppJSBridge = !0), document.dispatchEvent( new CustomEvent('UniAppJSBridgeReady', { bubbles: !0, cancelable: !0, }) ) }, f = [ function (e) { if (r || d) return ( window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document.addEventListener('DOMContentLoaded', e) : window.plus && s.test(document.readyState) ? setTimeout(e, 0) : document.addEventListener('plusready', e), o ) }, function (e) { if (v) return ( window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('WeixinJSBridgeReady', e), window.wx.miniProgram ) }, function (e) { if (c) return ( window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QQJSBridgeReady', e), window.qq.miniProgram ) }, function (e) { if (w) { document.addEventListener('DOMContentLoaded', e) var n = window.my return { navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv, } } }, function (e) { if (u) return ( document.addEventListener('DOMContentLoaded', e), window.swan.webView ) }, function (e) { if (g) return ( document.addEventListener('DOMContentLoaded', e), window.tt.miniProgram ) }, function (e) { if (p) { window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QaJSBridgeReady', e) var n = window.qa return { navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv, } } }, function (e) { return document.addEventListener('DOMContentLoaded', e), o }, ], m = 0; m < f.length && !(l = f[m](_)); m++ ); l || (l = {}) var E = 'undefined' != typeof uni ? uni : {} if (!E.navigateTo) for (var b in l) t(l, b) && (E[b] = l[b]) return (E.webView = l), E }) // #endif
main.js定义全局变量引入上面的文件
//h5和app通信 import uniwebviewmsg from '@/utils/webviewsdk.js' Vue.prototype.uniwebviewmsg = uniwebviewmsg
在业务页面就可以发消息, 我是在主页的onshow方法里检查版本是否要更新, 如果更新就给原生app发消息。发消息代码如下:
sendMsgToApp() { // #ifdef H5 try { this.uniwebviewmsg.webView.postMessage({ data: { title: '我是从uni-app H5传递过来的message-需要升级', desc: '传递的消息信息,必须写在data对象中', }, }); } catch (e) { uni.showToast({ title: '**出错了** :' + e, icon: 'none', duration: 2000 }); } // #endif
}
在加载webview的index页面, 就可以收到消息,修改全局变量。这样有更新的时候用户从后台切换到前台也会触发重载webview, 更新成功后版本号就一致,下次前后台切换就正常了。
