uniapp热更新,告别云打包

前言

项目一直使用uniapp来打包APP,但是每次发布测试或者上线,都要使用官方的云打包…有大小限制不说,周五等时间等待的时间比打包的时间更加长,所以就想着能不能热更新呢?说干就干

调研阶段

首先在官网的文档上找了一下,发现原来官方就有热更新的平台,这不巧了嘛官方文档,这里根据官方文档把插件安装好,文档写的还是很详细的,插件分两个,包的管理平台没什么可说,基本按照官方文档走就可以了,这里主要说一下APP的插件上的使用和一些完善的部分

静默更新

根据官方的文档走完,基本就能更新包了,因为更新的过程我们不希望用户感知到,所以我们选择使用静默更新,但是使用的过程中问题就来了…静默更新之后,会偶尔出现界面样式完全错乱的情况,然后手动重启应用之后又可以了…

uniapp热更新,告别云打包

这个问题折腾了一段时间,似乎官方也没有解决的办法,那就只能自己动手了

解决静默更新bug

现在已知在使用过程中更新包的话,会可能出现界面样式混乱的问题,那我们就更新之后,帮用户直接重启不就可以了,完美~

uniapp热更新,告别云打包

然而事情并没有这么简单,这个时候产品经理来告诉我,用户使用的过程中直接重启,用户会以为我们的软件有问题…我说…那要不我加个弹窗告诉他,然后产品经理认为这样还是不好.可能用户在填写一些表单之类的,弹窗之后直接退出也不行…经过漫长的讨论

uniapp热更新,告别云打包

最后的结论就是…重新写一下整个静默更新的流程,大概就是用户将APP前台运行–检测是否有更新版本–有就下载好,保存到本地,再用户下一次APP前台运行的时候进行安装,并且提示为使用最新功能,需要重启,然后帮用户重启

uniapp热更新,告别云打包

代码实现

查看插件源码得知,热更新的逻辑相对简单,主要是使用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的云空间,而且测试跟正式环境的热更新包地址是不一样的,那就带来了一个问题,每次分支更新手动切换云空间?那一旦忘记切换,把链接测试云空间的包发到正式…那岂不是第二天因为左脚进入公司被开除

uniapp热更新,告别云打包

根据分支名称进行云空间的自动切换

这里要先写一下这个云空间的一个注意的点那个uniCloud的文件夹不会自动生成的!!!,而且没有这个文件夹,如果跟我一样使用云空间的小伙伴就会发现无法再进行热更新了…所以如果有新的小伙伴拉取项目,记住一定要让他手动去生成,不要问我为什么知道…

uniapp热更新,告别云打包

获取当前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云空间后台找到空间的配置

uniapp热更新,告别云打包

新建一个文件来保存配置项,初始化云空间需要用

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

最后说几个注意点

  1. 静默更新只能更新业务代码,如果你这次更新添加了底层的调用能力,比如蓝牙的调用,推送这些,那就要使用整包更新
  2. 如果出现了访问不到云空间的问题,大概率就是上一次打包的人没有uniCould的文件夹,或者是账号不正确,因为云空间是跟着账号走的.
  3. IDE切换账号会导致uniCloud连接断掉,记得手动连一下再打包,否则会没有热更新功能

至此就全部结束了,完结撒花~

上一篇:【Go语言】【1】windows操作系统下GO环境配置


下一篇:mybatis-plus------代码生成器