1.概述
当 App
发布了一个新的版本,需要将所有已安装的低版本 App
自动升级到最新的发布版本。
根据这一常用的场景需求,本文主要介绍一下,在 YonBuilder
移动开发的技术体系里,实现 App
版本更新的完整流程的两种方式。
方式1: 自动更新(即利用自动自带的更新功能完成 App
的版本更新)
方式2:自定义更新(即手动编写整个的更新流程代码,包括显示提示页面,更新流程逻辑等)
两种更新方式各有优劣:
- 「自动更新」的好处是不需要开发客户端代码,开箱即用,省时省力,缺点是界面和交互都是系统写死的,开发无法进行修改;
- 「自定义更新」需要开发额外的编写所有的更新代码,有开发工作量,好处是可以自定义页面样式、内容及交互逻辑,更灵活;
从上面我们可以看到,两种方式刚好形成了互补,在具体的应用开发中,我们可以根据产品需求,选择合适的方式去使用。
好了,话不多说,我们现在开始!
2.发布新版本的操作流程
无论是「自动更新」还是「自定义更新」,都需要先在云端的工作台去发布新的应用版本。
发布了新的版本,手机端的应用才可以获取到更新版本信息,然后提示用户更新,完整整体的版本更新流程。
前置条件
发布新版本前,需要先用过移动打包面板,编译正式版的应用。只有正式版的应用才可以去进行发布。
注意:
如果使用系统自动更新功能,需要已发布版本的应用代码中,将 config.xml
文件中的自动更新参数 value
值为 true
<preference name="autoUpdate" value="true" />
如果是使用自定义更新(及手动更新方式),则需要将该参数设置为 false
。
这里我们先设置为 true
,先测试一下系统自带的应用更新功能。
2.1 进入控制台操作面板
登录开发者中心的控制台,选择目标应用进行详情页,切换到运营管理-版本管理面板
2.2 选择平台(每次只能操作一个平台的更新)
2.3 选择版本号(系统自动获取当前应用已编译的正式版本号)
注:点击「版本」后,下拉列表中的版本号信息,是当前应用在「移动打包」面板中已编译成功的正式版的版本号。
2.4 填写更新地址
如果是android,则需要先将应用的apk安装包上传到自己的业务服务器,或者其他支持下载的第三方平台(及支持联网下载的公网地址),这里注意不要直接使用官方编译生成的安装包的下载地址,因为那个地址存在有效期,过期后安装包就会被系统自动清除掉,导致地址失效,无法下载。
如果是iOS,则填写已上架appstore的应用地址(这里以友开发App为例)
如果地址中包含汉字,粘贴后会自动转码,不需担心。
2.5 填写备注信息后,点击更新按钮,即完成了版本更新操作。
注意:这个备注信息,会作为提示信息参数的内容,发送到客户端。
以上就完成新版本的发版工作,在使用自动更新方式时,当你按上述流程发布一个高于当前已安装的应用版本时,将当前应用重新启动,应该就会体验到系统的自动更新功能的效果了。
3. 拓展1:强制关闭
3.1 使用场景
当某个版本存在恶性 Bug
或其他问题导致开发者不希望用户在使用该版本时,可以在控制台操作将该 App
的指定版本进行关闭。关闭以后,已安装该 App
的用户使用该 App
时,将弹出提示弹窗。
3.2 操作流程
操作流程很简单,直接在当前页面底部的版本更新列表中,找到需要关闭的版本,点击该版本右侧的「关闭」按钮即可
4 扩展2:强制更新
4.1 使用场景
当应用有一个大版本的更新,与之前的版本不兼容(比如后端修改了 api
接口,低版本无法访问)或者 某个版本存在重大漏洞 Bug
,需要升级到新的版本时,可以使用强制更新
4.2 操作流程
当前操作是在上一阶段的 「强制关闭」基础上进行拓展的。
具体就是先发布希望用户升级使用的新版本,然后将低于该版本的所有版本都关闭掉。
这个操作的基本原理也很简单,因为当用户使用的某个版本关闭后,App
会自动寻找更高级的版本去提示用户进行升级。当你把所有非必要的版本都关闭后,App
自然会提示给用户你仅剩的那个版本,如果用户不升级,那么就无法进入 App
。
这样就实现了我们强制用户升级到指定版本的目的。
4.3 补充:
开发者:是否可以只关闭出问题的那个APP
版本,让那个问题版本进行强制升级,其余的版本不关闭继续使用,可以吗?
答案是可以的,你完全可以关闭某一个或者某几个你需要关闭的版本,关闭哪个版本,哪个版本就会变成强制升级,未被关闭的APP
版本还可以继续使用。
这里,我举个例子,大家就明白了
假设你现在app有 3
个版本 1.1
、1.2
,1.3
,并你在云端「版本更新」发布 1.4
新版本时
当你关闭 1.3
版本后
1.1
版本的用户启动APP
后,会接收到 1.2
版本的更新提示(用户可以选择更新或者不更新,非强制)
1.2
版本的用户启动APP
后,会接收到 1.4
版本的更新提示(因为1.3
被关闭,所以收不到1.3
的版本更新提示,用户可以选择更新或者不更新,非强制)
1.3
版本的用户启动APP
后,会接收到 1.4
版本的更新提示(必须更新到 1.4
,不更新则只有退出APP
一个选项,无法再使用 1.3
)
5 扩展3:自定义更新
5.1 使用场景
某些业务需求,对于 App
升级过程中的界面或者流程,有自己的特殊要求,比如觉得官方提供的更新弹出界面不好看,不美观;或者在升级前需要插入一些自己的付费判断、鉴权等交互逻辑
5.2 实现思路
App启动后,向服务端请求最新的应用版本信息然后跟当前应用的版本信息进行比对,如果存在高版本,则弹出自定义的提示弹窗提示用户进行更新升级或在其中插入自定义的其他逻辑需求。
核心方法有:
- 新版本检测;
- 展示交互弹窗面板;
- 新版本下载;
- 新版本安装;
5.3 开发前的准备工作
5.3.1 项目添加 mam
插件
大多数应用应该不需要此操作,因为在创建项目的时候,系统会自动默认添加 mam
插件,除非后来被手动删除了。
如果我们发现已添加的插件列表中没有 mam
插件,则自己手动搜索添加即可。
5.3.2关闭系统自动更新
在本地使用 YonStudio
,将项目在本地打开,先设置关闭默认的自动更新配置。具体操作是打开 config.xml
文件,设置自动更新配置参数 value="false"
可以通过 YonStudio
的可视化配置页面修改
也可以切换到「源文件」进行修改
<preference name="autoUpdate" value="false" />
5.4 代码编写
本次示例仅对重要的代码进行展示和讲解,完成的项目示例代码我已上传到 gitee
仓库,有需要的可以访问仓库进行下载
5.4.1 编写版本更新检测方法
在应用的入口页( config.xml
里设置的入口页面,本应用是 index.html
),编写以下代码
// 应用版本检测
function checkAppVersion() {
var mam = api.require('mam');
// 检测当前版本是否有更新或者被强制关闭
mam.checkUpdate(function(ret, err){
if (err) {
console.log('版本检测失败:' + JSON.stringify(err.msg));
return;
}
var res = ret.result;
var sign;
if(res.update) {
sign = res.closed ? 1:2;
} else {
sign = res.closed ? 3:0;
}
if(sign > 0) {
// 可以通过 res.closed 判断是否需要强制更新,本示例将强制更新的处理逻辑编写在提示弹窗页面
api.openFrame({
name: 'panel_version_tip',
url:'html/panel_version_tip.html',
pageParam: { //
sign: sign, // 升级标识 1.强制更新 2.建议更新 3.强制关闭
data: res // 请求返回的原始数据源
}
})
}
});
}
这里我们使用了 mam
模块的 checkUpdate
API函数,该方法会请求云端服务器,返回当前应用的最新版本信息。
我们根据业务逻辑,设置3种提示场景
-
- 有新版本并且要求强制更新:{ update:true,closed:true }
-
- 有新版本,不要求强制更新:{ update:true,closed:false }
-
- 无新版本且当前版本被强制关闭:{ update:false,closed:true }
-
- 无新版本:{ update:false,closed:false }
PS: res.closed
表示是否需要强制关闭当前版本 res.update
表示是否存在新版本
本函数的逻辑意图就是获取到新版本信息后,对响应参数进行判断,根据参数的不同,设置对应的场景标识 sign
,用于展示不同场景下的提示弹窗内容。
当判断存在新的版本时,我们调用了 api.openFrame
引擎方法去打开一个我们新建的弹窗页面( panel_version_tip.html
),用来模拟版本升级时的提示单窗。
同时,我们利用 openFrame
自带的 pageParam
参数,将新版本的元数据信息发送给 panel_version_tip.html
页面,方便该页面利用相关参数内容进行对应的逻辑交互。
5.4.2 编写提示弹窗
UI部分的样式编写省略,具体可下载源码自行查看,下面截图是最终的实现效果。
通过 vue
的数据绑定功能,实现不同的升级场景下,展示不同的内容提示和不同的操作按钮。从而实现了强制更新、建议更新和强制关闭等逻辑功能。
5.4.2.1 panel_version_tip.html页面代码片段
PS:本页面引入了 vue3
,使用 vue
的写法来实现页面的动态数据绑定,具体使用方式可去源码中查看。
function initPageWithVue() {
app = Vue.createApp({
data() {
return {
title: '', // 标题
content: '', // 升级内容
sign: 0, // 自定义的升级标识 1.强制更新 2.建议更新 3.强制关闭
}
},
mounted() {
this.initPageData();
},
methods: {
initPageData() {
this.sign = api.pageParam.sign;
if(this.sign === 1 || this.sign === 2) {
this.handleUpdatePageData(); // 更新的处理逻辑
} else if(this.sign === 3) {
this.handleClosePageData(); // 关闭的处理逻辑
}
},
handleUpdatePageData() {
var data = api.pageParam.data;
this.title = "发现新版本" + data.version; // 设置更新标题内容
this.content = data.updateTip; // 设置更新内容说明
},
handleClosePageData() {
var data = api.pageParam.data;
this.title = "当前应用版本已关停"; // 设置关闭标题内容
this.content = data.closeTip; // 设置关闭内容说明
},
// 立即升级 按钮的点击事件处理
handleUpdateEvent() {
var appURL = api.pageParam.data.source;
// 判断系统版本
if(api.systemType === 'ios') {
// iOS应用需要去apple的appStore去下载
api.installApp({
appUri: appURL
});
return;
}
// 非iOS系统,则下载应用的安装包文件
api.download({
url: api.pageParam.data.source,
report: true
}, function(ret, err){
if(err) {
api.alert({
title: '下载失败',
msg: '下载失败:' + err.msg
})
return;
}
if(ret.state === 0) {
// 下载中
api.toast({
msg:'正在下载中...' + ret.percent + '%',
duration: 1500
});
} else if(ret.state === 1) {
// 通过返回的 state 来判断文件是否下载完成,不要通过 percent 来判断
// 下载完成,安装新的app(需要应用被授权「应用安装」权限)
api.installApp({
appUri: ret.savePath
});
} else {
api.alert({
title: '下载失败',
msg: '下载失败,请稍后再试'
})
}
});
},
// 以后再说 按钮的点击事件处理
handleCancelEvent() {
api.closeFrame(); // 关闭当前面板弹窗页面,返回主页面
},
// 退出按钮的点击事件处理
handleCloseEvent(){
api.closeWidget(); // 直接关闭App
},
}
});
app.mount('#app');
}
5.4.2.2 代码分析:UI内容控制
我们在data中声明了3个 data
变量:title
、content
、sign
,其中
-title
和 content
是用来展示更新面板的标题和升级内容说明文本的( content
使用了 v-html
方式,支持富文本显示,用于云端发版时可以控制展示的文本样式);
sign
变量通过api.pageParam
引擎api
,可以获取到上个页面传递的场景值,用于控制面板底部的功能按钮的动态展示。
在引擎加载完成后,通过 apiready
函数,初始化vue
实例,再通过mounted
生命周期事件调用 initPageData
初始化页面方法,实现页面内容的动态加载。
下面是3种场景的自定义样式展示效果
通过上面的截图可以看出,3种场景的内容打通小异,主要是底部的功能按钮有所不同。
强制版本更新的具体操作实现,其原理就是通过提供给用户有限的操作交互,让用户按开发者的意图去推进业务流程。以强制升级版本功能场景为例,具体就是App启动后就显示升级弹窗,弹窗仅提供给用户2个按钮,要么升级 App
,要么退出 App
,用户没有第三种选择,从而实现强制用户进行 App
版本的升级操作。
5.4.2.3 强制退出功能的实现
在本示例中,强制更新 和 强制关闭 场景都使用了 「退出」 按钮
<div class="btn" tapmode @click="handleCloseEvent();">退出</div>
退出按钮绑定的 handleCloseEvent
事件内,通过调用方法 api.closeWidget()
实现App的退出功能
// 退出按钮的点击事件处理
handleCloseEvent(){
api.closeWidget(); // 直接关闭App
},
5.4.2.4 升级版本功能的实现
升级 App
版本,需要先判断一下当前应用的操作系统类型。因为 iOS
系统是闭源系统,只能跳转到苹果的 appStore
去下载更新。
所以如果是 iOS
应用,则需要单独特殊处理。
if(api.systemType === 'ios') {
// iOS应用需要去apple的appStore去下载
api.installApp({
appUri: appURL // 发版iOS应用时填写的应用地址信息
});
return;
}
其他的类型应用,则需要先下载应用安装包,下载完成后,再进行应用安装,完成应用的升级操作。
我们通过引擎提供的 api.download
函数方法来实现安装包文件的下载操作。
代码中,在回调函数中通过 percent
函数,实现了下载进度的实时提示功能。
// 非iOS系统,则下载应用的安装包文件
api.download({
url: api.pageParam.data.source, // 发版时填写的安装包下载地址
report: true
}, function(ret, err){
if(err) {
/** 下载失败的处理逻辑*/
}
if(ret.state === 0) {
// 下载中实时显示下载进度
api.toast({
msg:'正在下载中...' + ret.percent + '%',
duration: 1500
});
} else if(ret.state === 1) {
// 下载完成安装应用
api.installApp({
appUri: ret.savePath
});
} else {
/** 下载异常的处理逻辑*/
}
});
5.4.2.5 安装更新App
通过引擎提供的 api.installApp
方法可以实现应用的安装,前提条件是用户需要授权给应用「安装新应用」的权限才可以。
5.5 避坑指南
大家在实际开发中,一定要保证当前应用拥有一下的应用权限
- 「安装新应用」权限:注意要给应用安装权限,否则应用无法自动更新新的版本
- 「文件存储」权限:给应用文件存储权限,否则无法下载新的安装包
在编译应用安装包时,就需要设置相关权限
本文为了方便大家理解,简化了一些功能逻辑的交互逻辑。大家在实际的商用项目开发者,需要先通过 api.hasPermission
判断当前应用是否具备权限,如果不具备,需要调用 api. requestPermission
向用户申请相关的应用权限。
5.6 实际效果展示
这里发布新版本的安装包,我使用了友开发 App
的下载链接来进行演示,实际使用中,开发者需要发布自己的实际版本的应用下载地址
6 总结
以上简单讲解了一下 App
版本升级功能的具体编码开发,完整源码可以访问我的Gitee仓库:https://gitee.com/T0T/yonbuilder-app-demos/tree/master/App%E8%87%AA%E5%AE%9A%E4%B9%89%E6%9B%B4%E6%96%B0Demo获取。限于篇幅和突出核心重点的需要,很多的知识点并没有进行详情的展开和拓展。本文的主要目的还是通过完整的示例展示 App
版本升级的实现思路、主要交互逻辑和整体流程。掌握核心思想和方法,开发者就可以根据自己的业务逻辑需求,去扩展编写符合业务场景需求的场景代码了。例如本文是是使用官方提供的 mam
模块去检查代码更新的,如果开发者有自己的独立后端服务,也可以让自己的后端,去封装一个查询最新版本号及提供 App
下载的相关API接口,再由前端使用 ajax
等请求方法去调用该 API
接口进行版本的查询和下载安装,从而实现了剥离对官网控制台的依赖。
2024新年已到,祝大家在新的一年里行好运,工作学习一帆风顺!