前言
项目一直使用uniapp来打包APP,但是每次发布测试或者上线,都要使用官方的云打包…有大小限制不说,周五等时间等待的时间比打包的时间更加长,所以就想着能不能热更新呢?说干就干
调研阶段
首先在官网的文档上找了一下,发现原来官方就有热更新的平台,这不巧了嘛官方文档,这里根据官方文档把插件安装好,文档写的还是很详细的,插件分两个,包的管理平台没什么可说,基本按照官方文档走就可以了,这里主要说一下APP的插件上的使用和一些完善的部分
静默更新
根据官方的文档走完,基本就能更新包了,因为更新的过程我们不希望用户感知到,所以我们选择使用静默更新,但是使用的过程中问题就来了…静默更新之后,会偶尔出现界面样式完全错乱的情况,然后手动重启应用之后又可以了…
这个问题折腾了一段时间,似乎官方也没有解决的办法,那就只能自己动手了
解决静默更新bug
现在已知在使用过程中更新包的话,会可能出现界面样式混乱的问题,那我们就更新之后,帮用户直接重启不就可以了,完美~
然而事情并没有这么简单,这个时候产品经理来告诉我,用户使用的过程中直接重启,用户会以为我们的软件有问题…我说…那要不我加个弹窗告诉他,然后产品经理认为这样还是不好.可能用户在填写一些表单之类的,弹窗之后直接退出也不行…经过漫长的讨论
最后的结论就是…重新写一下整个静默更新的流程,大概就是用户将APP前台运行–检测是否有更新版本–有就下载好,保存到本地,再用户下一次APP前台运行的时候进行安装,并且提示为使用最新功能,需要重启,然后帮用户重启
代码实现
查看插件源码得知,热更新的逻辑相对简单,主要是使用H5+的plus.runtime.install的方法进行更新的,也就是说,其实怎么下载,怎么处理这个热更新的包我们完全可以自定义.只要在最后使用上面提到的install的方法进行安装就行.
那我们先从下载包的地方入手
找到APP热更新插件源码的位置,在根目录下的uni_modules/uni-upgrade-center-app/utils/check-update.js,在下载成功并且静默更新的地方,原本是下载直接安装,把安装的代码去掉,先保存起来
if (is_silently) {
uni.downloadFile({
url: e.result.url,
success: res => {
if (res.statusCode == 200) {
// 先将文件保存起来,在进来主页的时候再进行安装判断
uni.saveFile({
tempFilePath:res.tempFilePath,
success:function(res){
if(globalConfig.gitName != 'master'){
// 这里可以加,这里是根据git分支进行判断的,后面会说到
uni.showModal({
title:"WGT保存成功",
})
}
uni.setStorageSync('wgtFile',res.savedFilePath)
},
fail(e){
if(globalConfig.gitName != 'master'){
uni.showModal({
title:"保存失败",
content:JSON.stringify(e)
})
}
},
})
// plus.runtime.install(res.tempFilePath, {
// force: false
// });
}
},
fail(e){
if(globalConfig.gitName != 'master'){
uni.showModal({
title:"下载失败",
content:JSON.stringify(e)
})
}
},
});
return;
}
热更新包安装的处理
下载处理好之后,我们处理安装的逻辑.在App.vue的主入口处,onShow的生命周期里面,这个生命周期就是用户软件前台运行的时候,会进入的生命周期,判断是否有未安装的热更新包,有的话进入安装,安装完成之后,就弹窗提示用户需要重启,用户点击确定之后,就杀死整个APP,这里其实一开始是想要使用restart热重启,但是样式错乱的问题依然存在,所以就选择了杀死整个APP的方式,也就是下面的nativeQuit方法,后面会说到
if(uni.getStorageSync('wgtFile')){
installFinish = false
console.log("有wgtFile")
// 这里就是热更新的主要功能,借用H5+的install安装热更新的包
plus.runtime.install(uni.getStorageSync('wgtFile'), {
force: true
},function(){
uni.removeStorageSync('wgtFile')
uni.showModal({
title: '提示',
content: 'APP需要重启以使用最新功能',
showCancel:false,
success: function (res) {
console.log("准备重启")
if (res.confirm) {
_this.nativeQuit();
// plus.runtime.restart();
}
}
});
},function(error){
uni.showModal({
title: '提示',
content: '安装失败了' + error,
showCancel:false,
});
});
}
if(installFinish && !uni.getStorageSync('wgtFile') && !uni.getStorageSync('downing')){
// 调用APP更新方法,如果安装包正在安装,或者正在下载.则不要去获取
wgtUpdate().then((e)=>{
}).catch((e)=>{
})
}
自定义退出APP的方法nativeQuit
上面说到,H5+的热重启依然解决不了样式错乱的问题,只能强行杀死APP让用户手动启用APP了.这里由于uniapp是原生APP的webview,所以需要根据IOS跟安卓来实现不同的退出APP的方式
安卓退出APP并且杀死后台的方法
// 热更新之后需要杀死进程重新打开,需要引入安卓system的类来实现
let system = plus.android.importClass('java.lang.System')
_this.nativeQuit = function(){
system.exit(0);
};
IOS退出APP并且杀死后台的方法
_this.nativeQuit = function(){
plus.ios.import('UIApplication').sharedApplication().performSelector('exit');
}
nativeQuit完整代码
let _this = this;
uni.getSystemInfo({
success: function(e) {
console.log(e.platform)
Vue.prototype.StatusBar = e.statusBarHeight;
Vue.prototype.$phoneInfo = e
// let system = plus.android.import('java.lang.System')
if (e.platform == 'android') {
let main = plus.android.runtimeMainActivity();
// 热更新之后需要杀死进程重新打开,需要引入安卓system的类来实现
let system = plus.android.importClass('java.lang.System')
_this.nativeQuit = function(){
system.exit(0);
};
}else{
_this.nativeQuit = function(){
plus.ios.import('UIApplication').sharedApplication().performSelector('exit');
}
}
}
})
至此热更新部分已经基本完成了,但是由于插件直接使用的是uni的云空间,而且测试跟正式环境的热更新包地址是不一样的,那就带来了一个问题,每次分支更新手动切换云空间?那一旦忘记切换,把链接测试云空间的包发到正式…那岂不是第二天因为左脚进入公司被开除
根据分支名称进行云空间的自动切换
这里要先写一下这个云空间的一个注意的点那个uniCloud的文件夹不会自动生成的!!!,而且没有这个文件夹,如果跟我一样使用云空间的小伙伴就会发现无法再进行热更新了…所以如果有新的小伙伴拉取项目,记住一定要让他手动去生成,不要问我为什么知道…
获取当前git分支的名称
在vue.config.js当中,借用child_process这个库,就可以运行脚本,通过脚本获取分支名称,并且保存起来让全局可以让访问到
const childProcess = require('child_process')
// 获取当前git分支名称
const branchName = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
cwd: __dirname.replace(/\\/g, '/')
}).toString().trim()
module.exports = {
chainWebpack: config => {
config
.plugin('define')
.tap(args => {
// 把分支名称保存起来
args[0]['process.env'].GIT_BRANCH_NAME = JSON.stringify(branchName)
return args
})
}
}
新建一个文件保存云空间的配置项
在uni云空间后台找到空间的配置
新建一个文件来保存配置项,初始化云空间需要用
const config = {
gitName: process.env.GIT_BRANCH_NAME, // 当前git的分支名称
uniClound: { // 云空间的配置信息,用于做热更新使用
master: {
provider: 'aliyun', // 这里阿里云就是aliyun,腾讯是什么忘记了...
spaceId: '云空间Id',
clientSecret: '云空间秘钥'
},
test: {
provider: '云空间服务商',
spaceId: '云空间Id',
clientSecret: '云空间秘钥'
}
}
}
export default config
动态初始化云空间
官方插件初始化云空间的文件在uni_modules/uni-upgrade-center-app/utils/call-check-version.js,修改默认导出的函数
import config from "../../../globalConfig.js" // 引入上面的配置文件
export default function() {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
// 这个方法可以获取到当前APP的版本号等信息,要用来判断版本号高低的
let myCloud = ''
// 利用git的分支名称来初始化不同的云空间
if(config.gitName == 'master'){
// 连接生产环境的云空间
myCloud = uniCloud.init(config.uniClound.master);
}else{
// 连接测试环境的云空间
myCloud = uniCloud.init(config.uniClound.test);
}
myCloud.callFunction({
name: 'check-version',
data: {
appid: plus.runtime.appid,
// appVersion: plus.runtime.version,
appVersion: widgetInfo.version, // 应用名称为大的版本号,用于比较APP的整包更新
// wgtVersion: widgetInfo.version
wgtVersion: widgetInfo.versionCode, // 改成利用应用版本号来进行判断wgt的热更新
},
success: (e) => {
resolve(e)
},
fail: (error) => {
reject(error)
}
})
})
})
// #endif
// #ifndef APP-PLUS
return new Promise((resolve, reject) => {
reject({
message: '请在App中使用'
})
})
// #endif
}
在测试环境下,热更新弹窗
这里方便自己排除错误,个人感觉挺重要的,因为静默更新几乎看不到什么提示,所以最好在几个下载,更新的节点捕捉一下错误,并且弹窗,但是仅限在测试环境下,所以我们也可以根据分支名称来进行判断,在uni_modules/uni-upgrade-center-app/utils/check-update.js下增加测试环境的弹窗提示
uni.downloadFile({
url: e.result.url,
success: res => {
if (res.statusCode == 200) {
// 先将文件保存起来,在进来主页的时候再进行安装判断
uni.saveFile({
tempFilePath:res.tempFilePath,
success:function(res){
if(globalConfig.gitName != 'master'){
// 在分支不是master的时候,进行弹窗提示
uni.showModal({
title:"WGT保存成功",
})
}
uni.setStorageSync('wgtFile',res.savedFilePath)
},
fail(e){
if(globalConfig.gitName != 'master'){
// 在分支不是master的时候,进行弹窗提示
uni.showModal({
title:"保存失败",
content:JSON.stringify(e)
})
}
},
})
// plus.runtime.install(res.tempFilePath, {
// force: false
// });
}
},
fail(e){
if(globalConfig.gitName != 'master'){
// 在分支不是master的时候,进行弹窗提示
uni.showModal({
title:"下载失败",
content:JSON.stringify(e)
})
}
},
});
最后说几个注意点
- 静默更新只能更新业务代码,如果你这次更新添加了底层的调用能力,比如蓝牙的调用,推送这些,那就要使用整包更新
- 如果出现了访问不到云空间的问题,大概率就是上一次打包的人没有uniCould的文件夹,或者是账号不正确,因为云空间是跟着账号走的.
- IDE切换账号会导致uniCloud连接断掉,记得手动连一下再打包,否则会没有热更新功能
至此就全部结束了,完结撒花~