我正在参加「掘金·启航计划」
以下是基于版本 7.1.0 进行记录总结的。
1. Code Push 各个更新状态说明
enum SyncStatus {
UP_TO_DATE: 0, // The running app is up-to-date
UPDATE_INSTALLED: 1, // The app had an optional/mandatory update that was successfully downloaded and is about to be installed.
UPDATE_IGNORED: 2, // The app had an optional update and the end-user chose to ignore it
UNKNOWN_ERROR: 3,
SYNC_IN_PROGRESS: 4, // There is an ongoing "sync" operation in progress.
CHECKING_FOR_UPDATE: 5,
AWAITING_USER_ACTION: 6,
DOWNLOADING_PACKAGE: 7,
INSTALLING_UPDATE: 8
}
2. 当启动就选择热更新的状态变化
也就是在程序的入口就选择检查热更新,并且当有更新的时候立马安装更新,代码如下:
useEffect(() => {
CodePush.sync(
{
installMode: CodePush.InstallMode.IMMEDIATE,
},
// 这个是在检查更新或者安装中等状态发生变化的时候会毁掉
codePushStatusDidChange,
)
}, [])
2.1 当没有更新的时候
当我们并没有热更新的时候, app 打开的时候的状态变化。
5(SyncStatus.CHECKING_FOR_UPDATE])也就是检查更新;0(SyncStatus.UP_TO_DATE)改app目前运行就是最新的版本。
2.2 当有更新的时候
当我们推送了热更新以后, app 打开的时候的状态变化。
5(SyncStatus.CHECKING_FOR_UPDATE)也就是检查更新;7(SyncStatus.DOWNLOADING_PACKAGE)下载最新的热更新包;8(SyncStatus.INSTALLING_UPDATE)正在安装最新的热更新包;1(SyncStatus.UPDATE_INSTALLED)最新的热更新包安装成功。
这个时候app会重新加载最新的热更新包,然后会重新调用热更新检查相关逻辑。所以后面的状态变化跟2.1想通。有时候并没有状态1,只有578,因为一般安装完成后就会立马重启app,可能此时最后的状态1就会被扔掉。
2.3 当没有更新,但是网络有问题的情况
当我们并没有热更新的时候,但是此时 app 并没有网络,我们来看看状态的变化。
5(SyncStatus.CHECKING_FOR_UPDATE)也就是检查更新;3(SyncStatus.UNKNOWN_ERROR)出现了未知问题
我是通过设置 ios 的网络为百分之百丢包来测试的;这个过程很慢要等很久才会出现状态3。当我采用将手机开启飞行模式的时候,这个过程是很快的。
2.4 当没有更新,网络很慢的时候
当用户所处的网络环境不是特别好的时候,我们启动 app 的状态变化。下面列出各种网络情况下的测试结论。
Very Bad Network跟2.1相同,只不过这个过程会比较慢;3G同上,相比上面要快一些;LTE同上。
其余的就不列出了,都是一样的,只是快慢的问题。
2.5 当有更新,网络很慢的时候
当我们有热更的时候,在网络环境很差的情况下启动 app 。跟 2.2 相同,只不过会慢一些。
2.6 当新增的热更新有闪退
这里说的闪退是入口闪退,也就是已加载 bundle 就闪退,而不是某个界面有问题。只要是 bundle 包本身有 bug ,都是能成功热更新的,即便是在 App 中就出现了闪退,也不会触发 code-push 的回滚机制。这里可能需要我对热更新了解更深入才能更准确,将来学习这部分安排上。
我是直接写在下面的地方的:
const syncImmediate = useCallback(() => {
CodePush.sync(
{
installMode: CodePush.InstallMode.IMMEDIATE,
},
codePushStatusDidChange,
)
throw new Error('出现了错误')
}, [codePushStatusDidChange, dispatch])
那怎样才能触发 code-push 的回滚机制呢,接下来我会进行各种测试。我看了看网络上别人写关于 code-push 的教程和说明,我看他们是写在 App.ts 中的生命周期函数里就可以做到回退,由于我是 Hooks ,所以我写到 useEffect 函数中是不行的,后来我只是写在 index.js 中就实现了回滚:
AppRegistry.registerComponent(appName, () => render)
throw new Error('test')
可以查看更新结果:
在这之前启动 app,状态跟 2.2 一样,只不过紧接着就闪退了,再次启动就是跟 2.1 相同了。然后杀死 app 再次重启,此时的状态也是跟 2.1 相同。假如说是因为其他什么原因导致的热更新失败,比如下载不完整等等,这样 app 下次再次启动的时候就重新检查然后重新安装,这时我们怎么做呢?
3. 回滚后的操作
当下载下来的包有问题,也就是在刚开始就闪退( index.js 代码中有问题),那么就会触发回滚,就像我上面测试的一样,假如我们的热更新并不是自己代码的问题导致的,那么怎么样才能恢复更新呢,因为我们知道只要重新下载再安装大概率就不会有问题了。
让我们翻开API文档,会看到这么一个参数 rollbackRetryOptions ,这个就是回滚的相关参数设置。我设置如下:
CodePush.sync(
{
installMode: CodePush.InstallMode.IMMEDIATE,
rollbackRetryOptions: {
// 方便测试我这里写的一分钟
delayInHours: 1 / 60,
},
},
codePushStatusDidChange,
)
除了 delayInHours 这个参数还有重试次数:
export interface RollbackRetryOptions {
/**
* Specifies the minimum time in hours that the app will wait after the latest rollback
* before attempting to reinstall same rolled-back package. Defaults to `24`.
*/
delayInHours?: number;
/**
* Specifies the maximum number of retry attempts that the app can make before it stops trying.
* Cannot be less than `1`. Defaults to `1`.
*/
maxRetryAttempts?: number;
}
这下等我热更新自动回滚以后,只需要再等一分钟就会再次重试。
4. 和闪屏页结合使用
假如我们热更新跟闪屏页是一起使用的,也就是当热更新完成,闪屏页才消失;那么我只需要这样即可:
CodePush.sync(
{
installMode: CodePush.InstallMode.IMMEDIATE,
rollbackRetryOptions: {
delayInHours: 8,
},
},
syncStatus => {
switch (syncStatus) {
case CodePush.SyncStatus.UP_TO_DATE:
case CodePush.SyncStatus.UNKNOWN_ERROR:
SplashScreen.hideAsync() // 隐藏启动屏
break
}
},
)
也就是当热更新的状态是UP_TO_DATE和UNKNOWN_ERROR的时候;成功的情况下:
- 没有热更新,最后是
UP_TO_DATE的状态; - 有热更新,由于热更新成功以后再次启动也会经历跟
1相同的状态变更,所以最终还是UP_TO_DATE的状态; - 当更新失败,状态就会变成
UNKNOWN_ERROR的状态。
5. 手动控制更新
可能有时候我们并不想自动更新,自动重启,那么我们首先就要修改安装模式:
CodePush.sync({installMode: CodePush.InstallMode.ON_NEXT_RESTART})
这样就不会下载完成就立即重新启动app了。当然从这个安装模式我们也能知道,即便现在不重启,如果用户手动杀死 app ,重新打开,那么也会更新的。热更新说白了就是倾向于静默更新的,只是像我们整个项目只有一个bundle的情况下,立即生效就得重启 app ,所以才需要用到这个。先看看整个热更新的流程:
我们知道只要有热更新就会下载,就会替换,替换这个过程是自动的,不能手动操作;所以不管我们的安装模式是怎样的,只要重启就会使用最新的。在这个流程中能够控制的就是重启的时间,我们可以让用户手动重启,这里说的手动重启是在 app 已经在运行的状态下进行。下面来看我编写的代码:
const checkUpdate = () => {
// 开始检查更新
setCheckUpdating(true)
// 不允许重启
CodePush.disallowRestart()
CodePush.sync(
{
// 更新模式是下一次重启
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
rollbackRetryOptions: {
delayInHours: 1 / 3600,
},
},
status => {
// 正在下载热更新包
if (status === CodePush.SyncStatus.DOWNLOADING_PACKAGE) {
setCheckUpdating(false)
setDownloadProgress('100%')
// 开始检查更新会先触发这个状态
} else if (status === CodePush.SyncStatus.UP_TO_DATE) {
setCheckUpdating(false)
setDownloadProgress(null)
SimpleToast.show('Already the latest version.')
// 安装包已经下载完成
} else if (status === CodePush.SyncStatus.UPDATE_INSTALLED) {
setCheckUpdating(false)
setDownloadProgress(null)
// 这是我 app 里面的弹框
showOKCancelAlert({
titleKey: 'Update Ready',
messageKey:
'Now that the update is ready, do you want to update now?',
}).then(isOk => {
if (isOk) {
// 当用户点击确定就会重启
CodePush.allowRestart()
CodePush.restartApp(true)
}
})
}
},
// 当下载过程中进度变化的时候会触发这个回调
({ totalBytes, receivedBytes }) => {
const progress = (100 - (receivedBytes / totalBytes) * 100).toFixed(0)
setDownloadProgress(`${progress}%`)
},
)
}
这样就可以实现用户手动检查,然后手动更新了。