热更新:CodePush、react-native-pushy、
目录
一. 安装并注册CodePush
1. CodePush是什么
2. 安装并注册CodePush
二. 为我们的项目部署CodePush
1. 在CodePush服务器注册我们的App
2. 在我们的项目中集成CodePush SDK
三. 使用CodePush实现热更新
1. 制定热更新策略
2. 最常用的更新策略实现
3. 把JS包挂到CodePush服务器上
4. 打ipa包发布到AppStore
5. App上线后,假设我们又修改了RN的代码,进行热更新
四. 其它热更新方案
一. 安装并注册CodePush
1. CodePush是什么
CodePush是微软提供的一个中央仓库,我们可以把最新的JS包挂在它的服务器上(如更改了JS、HTML、CSS、图片资源等),而客户端则可以从它的服务器上加载到最新的JS包,它开源了RN版本——react-native-code-push。
- 开发阶段,我们的JS包是放在本地服务器上的,客户端加载的也是本地服务器上的这个JS包。
- 打包上线的时候,传统的方式是将RN代码打一个JS包,拖进Xcode里,再打成
ipa
包,上传到AppStore。这样JS包就存在于iOS项目里,客户端加载的也是项目里的这个JS包了。 - 我们可以看到传统打包方式的缺陷,即每次改变RN的代码,我们都需要重新打一个JS包,拖进Xcode,再打成
ipa
包,上传到AppStore,这很繁琐,而且更新也无法做到及时。于是可以考虑使用CodePush热更新,使用了它,我们则是把JS包挂在CodePush的服务器上,客户端加载的也是CodePush服务器上的这个JS包,我们就不需要像传统打包方式把RN项目那样打JS包,然后拖进Xcode使用了,这样即便我们改变了RN的代码,也只需要把新的JS包重新往CodePush服务器挂一下就可以了,客户端就能加载到最新的代码来执行。这样在修复一些小问题和添加新特性的时候,就避免了反复地打包和提交审核,可以进行实时更新。
2. 安装并注册CodePush
使用CodePush之前首先要安装CodePush客户端,在终端输入npm install -g code-push-cli
指令,就可以安装了。
安装结束后,输入code-push -v
指令,若有版本号输出,则代表安装成功。
然后我们需要注册一个CodePush账号。在终端输入code-push register
指令,会打开如下注册页面让你选择授权账号。
授权通过之后,CodePush会告诉你“token”,复制此“token”到终端即可完成注册,并且帮你自动登录进去了。
登录成功后,你的session
文件将会写在/Users/你的用户名/.code-push.config
下。
CodePush相关命令
code-push login
:登录
code-push loout
:注销
code-push access-key ls
:列出登陆的token
code-push access-key rm <accessKye>
:删除某个 access-key
二. 为我们的项目部署CodePush
1. 在CodePush服务器注册我们的App
为了让CodePush服务器知道我们的App,我们需要向它注册一下,在终端输入code-push app add <appName> <os> <platform>
指令即可完成注册。注册完成之后会返回一套deployment key
,该key
在后面步骤中会用到,可以先复制下来。
注意:如果我们的应用分为Android和iOS版,那么在向CodePush注册App的时候需要注册两个App获取两套deployment key
,如:
code-push app add GitHub_RN_iOS ios react-native
code-push app add GitHub_RN_Android android react-native
CodePush相关命令
code-push app add
:在账号里面添加一个新的App
code-push app remove
:在账号里移除一个App
code-push app rename
: 重命名一个存在的App
code-push app list
:列出账号下面的所有App
code-push app transfer
:把App的所有权转移到另外一个账号
2. 在我们的项目中集成CodePush SDK
第一步:打开我们的RN项目,集成react-native-code-push
组件。
yarn add react-native-code-push
第二步:终端cd到我们的RN项目下,运行react-native link react-native-code-push
,这条命令将会自动帮我们在iOS项目中添加好CodePush相关的设置。
在终端运行此命令之后,终端会提示让你输入deployment key
,你只需按提示,输入你生产环境下的key
,当然如果你不想输入,也可以直接回车跳过,而是选择在项目中自己配置。
在项目中自己配置deployment key
。
- 按箭头所示,添加
Duplicate "Release" Configuration
,名字改为Staging
。
- 按箭头所示,添加
Add User-Defined Setting
。
- 然后把名字改为输入
CODEPUSH_KEY
,并输入相应的key
。
我们可以通过code-push deployment ls appName -k
命令来查看deployment key
。
- 然后在
info.plist
中,把CodePushDeploymentKey
的值改为$(CODEPUSH_KEY)
。
这样就完成了我们RN项目和iOS项目的CodePush部署,接下来我们可以使用CodePush来实现热更新了。
三. 使用CodePush实现热更新
1. 制定热更新策略
在使用CodePush实现热更新之前,我们需要根据自己的实际需求来制定一下App热更新的策略,即:
-
什么时候检测是否有更新?是APP一启动就检测是否有更新?还是在设置页面添加一个按钮,让用户自己去检测是否有更新?还是说两者都实现?当然如果我们期望更及时地检测更新,可以在APP每次从后台进入前台时去都检测更新,这时就可以在App根组件的
componentDidMount()
中添加如下代码:
AppState.addEventListener("change", (newState) => {
newState === "active" && codePush.sync();
});
- 是否强制更新?检测到有更新后,是要强制用户更新?还是用户可以选择性更新?(这条策略是由我们往CodePush服务器挂代码的时候控制的)
- 什么时候将更新呈现给用户?是用户下载完更新后立即重启App呈现更新?还是等用户下次启动App后再把最新呈现给用户?
2. 最常用的更新策略实现
最常用的更新策略就是,APP一启动就检查是否有更新,然后根据更新的必要程度由我们决定是否强制用户更新,通常情况下我们选择更新后立马重启App把更新呈现给用户,接下来我们简单实现一下。
我们在App根组件中来做是否有更新的检测。
// DynamicBottomNavigator.js
import CodePush from "react-native-code-push";
import CodePushPage from "../Controller/Mine/CodePushPage";
// staging:'Hqspd-sYzTo-FrAfpR7py9P0pBnF7627d37f-ad25-47e2-a0ef-8a9b98f656cc'
// release:'sMIGG-ocNzGwkB2ZoxpnT1Sr62-47627d37f-ad25-47e2-a0ef-8a9b98f656cc'
const CODEPUSH_KEY = 'sMIGG-ocNzGwkB2ZoxpnT1Sr62-47627d37f-ad25-47e2-a0ef-8a9b98f656cc';
class DynamicBottomNavigatorClass extends Component {
constructor(props) {
super(props);
this.state = {
// 检测是否有更新
update: false,
};
}
render() {
return (
<View style={{flex: 1}}>
<DynamicBottomNavigatorContainer
// 切换tab会触发
onNavigationStateChange={(preState, newState, action) => {
// 发出通知
DeviceEventEmitter.emit(NotificationName.DID_CHANGE_TAB, {
preState,
newState,
})
}}
/>
{/*是否modal出更新提示界面*/}
{this.state.update ? <CodePushPage/> : null}
</View>
)
}
componentDidMount() {
// 检测是否有更新
CodePush.checkForUpdate(CODEPUSH_KEY)
.then((update) => {
this.setState({update});
});
}
}
export default DynamicBottomNavigatorClass;
在这里我们封装了一个CodePushPage
的组件来专门处理更新的逻辑。
import React, {Component} from 'react';
import {
StyleSheet,
View,
Text,
Modal,
} from 'react-native';
import CodePush from "react-native-code-push";
import ProjectSingleton from "../../Project/ProjectSingleton";
import * as Progress from 'react-native-progress';
export default class CodePushPage extends Component {
/*-----------生命周期方法-----------*/
constructor(props) {
super(props);
this.state = {
// 状态提示文本
syncMessage: '',
// 因为我们并非纯自定义更新提示的那一套额,而是在CodePush.sync那个弹出框的下面又塞了一个这样的CodePushPage,
// 所以有些界面的逻辑需要通过一些属性来控制一下,而这些界面逻辑正好可以由CodePush的状态来控制
showUpdateView: false,
// 下载进度
currentProgress: 0,
currentProgressPercent: '',
};
}
render() {
return (
this._genContent()
);
}
componentDidMount() {
this.syncImmediate();
}
/*-----------CodePush相关方法-----------*/
// syncImmediate
syncImmediate() {
// CodePush会通过该方法帮我们自动完成检查更新、弹出更新提示框、下载、安装、是否立即重启App呈现更新等一系列操作。
CodePush.sync({
// 弹出更新提示框
updateDialog: {
// 标题
title: '发现新版本',
// 是否显示更新description,默认false
appendReleaseDescription: true,
// 更新description的前缀
descriptionPrefix: '\n更新内容:\n',
// 强制更新的message
mandatoryUpdateMessage: '',
// 强制更新的按钮文本
mandatoryContinueButtonLabel: '立即更新',
// 非强制更新的message
optionalUpdateMessage: '',
// 非强制更新的取消按钮文本
optionalIgnoreButtonLabel: '残忍拒绝',
// 非强制更新的更新按钮文本
optionalInstallButtonLabel: '立即更新',
},
// 立即重启App呈现更新
installMode: CodePush.InstallMode.IMMEDIATE,
},
// CodePush状态的变化
this.codePushStatusDidChange.bind(this),
// CodePush下载进度的检测
this.codePushDownloadDidProgress.bind(this)
);
}
// CodePush状态的变化
codePushStatusDidChange(syncStatus) {
switch (syncStatus) {
case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
this.setState({syncMessage: "检测更新中...", showUpdateView: false});
break;
case CodePush.SyncStatus.AWAITING_USER_ACTION:
this.setState({syncMessage: "请做出你的选择...", showUpdateView: false});
break;
case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
this.setState({syncMessage: "下载更新中...", showUpdateView: true});
break;
case CodePush.SyncStatus.INSTALLING_UPDATE:
this.setState({syncMessage: "安装更新中...", showUpdateView: true});
break;
case CodePush.SyncStatus.UP_TO_DATE:
this.setState({syncMessage: "已经是最新的了...", showUpdateView: false});
break;
case CodePush.SyncStatus.UPDATE_IGNORED:
this.setState({syncMessage: "您选择了残忍拒绝", showUpdateView: false});
break;
case CodePush.SyncStatus.UPDATE_INSTALLED:
this.setState({syncMessage: "您选择了立即更新", showUpdateView: false});
break;
case CodePush.SyncStatus.UNKNOWN_ERROR:
this.setState({syncMessage: "出错啦...", showUpdateView: false});
break;
}
}
// CodePush下载进度的检测
codePushDownloadDidProgress(progress) {
// number转小数,并四舍五入保留指定位数
const currentProgress = (parseFloat(progress.receivedBytes / progress.totalBytes)).toFixed(2);
const currentProgressPercent = progress.receivedBytes + '/' + progress.totalBytes + ' bytes';
this.setState({
currentProgress,
currentProgressPercent,
});
}
/*-----------私有方法-----------*/
_genContent() {
return(
this.state.showUpdateView ? (
<Modal
// 背景层是否显示
visible={true}
// 背景层显示和消失时的动画效果
animationType={'fade'}
// 背景层是否透明
transparent={true}
// 在安卓上用户按下设备的后退按键时触发,该属性在安卓设备上为必填,且会在modal处于开启状态时阻止BackHandler事件
onRequestClose={() => {
}}
>
<View style={styles.container}>
<Progress.Bar
style={styles.progress}
progress={this.state.currentProgress}
indeterminate={false}
color={'white'}
/>
<View style={{flexDirection: 'row'}}>
<Text style={[styles.text, {textAlign: 'right'}]}>{this.state.syncMessage}</Text>
<Text style={[styles.text, {textAlign: 'left'}]}>{this.state.currentProgressPercent}</Text>
</View>
</View>
</Modal>
) : null
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.4)',
},
progress: {
margin: 10,
},
text: {
marginTop: 30,
color: 'white',
},
});
不要看着代码不少,耐心去看,很简单的!这样我们就完成了CodePush热更新的代码,确实很简单!
3. 把JS包挂到CodePush服务器上
编写完了CodePush热更新的代码,我们的App就算正是开发完毕,准备打包上线了。那么此时,App第一次编写完,我们需要把JS包挂到CodePush服务器上。
挂包的方式也很简单,只需要在终端cd到我们的RN项目下,执行
code-push release-react AppName ios --t 1.0.0 --dev false --d Production --des "init" --m false
指令即可,该指令会自动帮我们打出RN项目的JS包并上传到CodePush服务器上去。
指令参数介绍:
- App的名字。
- App的平台。
--t
:App的版本号,要和iOS或安卓项目的版本号一致。--dev
:是否启用开发者模式,默认为false
。--d
:发布更新的环境是Production
(生产)还是Staging
(开发),默认为Staging
。--des
:更新说明。--m
:本次更新是否要强制更新,默认为false
。
上传完后,可以通过code-push deployment ls appName
指令,来查看我们针对名为“ GitHub_RN”的App发布过的JS包。
4. 打ipa
包发布到AppStore
我们把JS包挂到CodePush服务器后,就可以打ipa
包发布到AppStore了。
5. App上线后,假设我们又修改了RN的代码,进行热更新
App上线后,假设我们做了某些更新,就只需要把最新的JS包重新往CodePush服务器挂一下即可,当然挂的时候注意此次更新是否要强制。
比如,我们这里要把最热模块的标题改为“最热哈哈哈”,而且强制更新,那么修改完RN的代码后,挂JS包的指令就是code-push release-react GitHub_RN ios --t 1.0.0 --dev false --d Production --des "修改最热模块的标题" --m true
,当我们重新打开App的时候就会检测到更新了。
这就完成热更新了!但是使用CodePush进行热更新也有一些缺点:
- 服务器在国外,在国内访问,网速不是很理想。(我们可以实现让用户手动升级的策略,主动拉取更新可以给用户多一种入口,避免网速差就拉取不到更新提示的情况。)
- 其升级服务器端程序并不开源的,后期微软会不会对其收费还是个未知数。
如果在没有更好的动态更新React Native应用的方案的情况下,并且这些问题还在你的接受范围之内的话,那么CodePush可以作为动态更新React Native应用的一种选择。
四. 其它热更新方案
我们还可以使用RN中文网提供的react-native-pushy组件来实现热更新,这个可以设置淘宝镜像源,访问速度的问题可以得到解决。
当然我们也可以自己搭建服务器来实现热更新或者增量热更新,可以搜索相应的文章来学习。