Fork me on GitHub

ReactNative之CodePush热更新流程

什么是CodePush?

  CodePush是一个微软开发的云服务器。通过它,开发者可以直接在用户的设备上部署手机应用更新。CodePush相当于一个中心仓库,开发者可以推送当前的更新(js/HTML/CSS/IMAGE等)到CodePush,然后应用将会查询是否有更新。

集成流程

  • 安装CodePush CLI
  • 注册CodePush账号
  • 在CodePush服务器中注册App
  • ReactNative中集成react-native-code-push
  • 原生应用中配置CodePush
  • 发布版本并测试更新


安装CodePush CLI

1
$ npm install -g code-push-cli

注册CodePush账号

1
$ code-push register

当执行上述命令后,会跳转到一个授权页面,你可以选择授权方式,我选择的授权方式是GitHub

当注册成功后,CodePush会返回给我们一个key

我们直接复制这个key,粘贴到终端回车,效果如下:

我们通过命令可以验证是否登录成功

1
$ code-push login

CodePush注册和登录相关的命令如下:

  • code-push login #登录
  • code-push register # 注册
  • code-push logout #注销
  • code-push access-key ls #列出登录的access-key
  • code-push access-key rm #移除某个access-key

在CodePush服务器中注册App

  为了让CodePush服务器识别我们的App,我们需要在CodePush中注册App,这里需要注意的是如果我们的应用分别为iOS和Android两个平台,这时我们需要分别注册两套key,应用添加成功后就会返回对应的ProductionStaging两个key,其中Production代表生产版本的热更新部署,Staging代表开发版本的热更新部署。在ios中将Staging的部署key复制在info.plist的CodePushDeploymentKey值中,在android中复制在Application的getPackages的CodePush中

  • 添加iOS平台应用
1
$ code-push app add iOSRNHybrid ios react-native
  • 添加Android平台应用

未完待续

我们可以通过命令查看我们通过CodePush添加了哪些app应用

1
$ code-push app list

CodePush管理App的命令如下:

  • code-push app add # 在账号里面添加某个平台下的app
  • code-push app remove/rm # 移除账号下的某个app
  • code-push app rename # 重命名一个app的名称
  • code-push app list/ls # 列出账号下所有的app
  • code-push app transfer # 把app的所有权转给另外一个账号

ReactNative中集成react-native-code-push

  首先我们需要安装react-native-code-push组件,然后通过link命令添加原生工程依赖,最后在ReactNative的根组件中添加热更新的逻辑代码

  • 安装组件
1
$ npm install react-native-code-push --save
  • 添加原生依赖
1
$ react-native link react-native-code-push

这里我们暂且都忽略deployment key,后续在工程中配置

设置更新策略

在使用CodePush更新你的应用的时候,先要配置一下更新策略,即:

  • 什么时候检查更新?(在App启动的时候?在设置页面添加一个检查更新按钮?)
  • 什么时候可以更新?如何将更新呈现给终端用户

1.在componentDidMount中调用sync方法,请求后台更新

如果可以进行更新,CodePush会在后台静默的下载更新到本地,等待App下一次启动的时候更新

2.方式二:在componentDidMount中添加如下代码

如果我们希望获得及时更新,可以在每次App从后台进入前台的时候检查更新

1
2
3
AppState.addEventListener("change", (newState) => {
newState === "active" && codePush.sync();
});

本案例采用方式一的更新策略,我们在ReactNative的根组件添加以下逻辑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import CodePush from 'react-native-code-push';


let codePushOptions = {
//设置检查更新的频率
//ON_APP_RESUME APP恢复到前台的时候
//ON_APP_START APP开启的时候
//MANUAL 手动检查
checkFrequency : CodePush.CheckFrequency.ON_APP_START
};

class App extends Component {

//如果有更新的提示
syncImmediate() {
CodePush.sync( {
//安装模式
//ON_NEXT_RESUME 下次恢复到前台时
//ON_NEXT_RESTART 下一次重启时
//IMMEDIATE 马上更新
installMode : CodePush.InstallMode.IMMEDIATE ,
deploymentKey: '配置你想测试用的key',
//对话框
updateDialog : {
//是否显示更新描述
appendReleaseDescription : true ,
//更新描述的前缀。 默认为"Description"
descriptionPrefix : "更新内容:" ,
//强制更新按钮文字,默认为continue
mandatoryContinueButtonLabel : "立即更新" ,
//强制更新时的信息. 默认为"An update is available that must be installed."
mandatoryUpdateMessage : "必须更新后才能使用" ,
//非强制更新时,按钮文字,默认为"ignore"
optionalIgnoreButtonLabel : '稍后' ,
//非强制更新时,确认按钮文字. 默认为"Install"
optionalInstallButtonLabel : '后台更新' ,
//非强制更新时,检查到更新的消息文本
optionalUpdateMessage : '有新版本了,是否更新?' ,
//Alert窗口的标题
title : '更新提示'
} ,
} ,
);
}

componentWillMount() {
CodePush.disallowRestart();//禁止重启
this.syncImmediate(); //开始检查更新
}

componentDidMount() {
CodePush.allowRestart();//在加载完了,允许重启
}

render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>Welcome to React Native!</Text>
<Text>我只想说Code-Push很操蛋</Text>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});


// 这一行必须要写
export default CodePush(codePushOptions)(App)

原生应用中配置CodePush

配置iOS平台

  • 使用Xcode打开项目,Xcode的项目导航视图中的PROJECT下选择你的项目,选择Info页签 ,在Configurations节点下单击 + 按钮 ,选择Duplicate “Release Configaration,输入Staging
  • 选择Build Settings,搜索Build Location -> Per-configuration Build Products Path -> Staging,将之前的值:$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 改为:$(BUILD_DIR)/Release$(EFFECTIVE_PLATFORM_NAME)
  • 选择Build Settings,点击+号,选择Add User-Defined Setting,将key设置为CODEPUSH_KEYReleaseStaging的值为前面创建的key,我们直接复制进去即可
  • 打开Info.plist文件,在CodePushDeploymentKey中输入$(CODEPUSH_KEY),并修改Bundle versions为三位

iOS平台至此配置完毕,这里需要特别注意的是上述的前三步配置都是基于PROJECT的,而不是TARGETS

配置Android平台

未完待续

发布版本并测试更新

  一般常见的应用内更新机制分为两种方式,一种是打开App就检查更新,另外一种就是在设置界面让用户主动检查更新并安装。

  • 方式一:打开App就检查版本更新

    最为简单的使用方式就是在ReactNative的根组件的componentDidMount方法中通过CodePush.sync()(前提是你已经导入的CodePush包:import CodePush from ‘react-native-code-push’) 方法检查并安装更新,如果有安装包可供下载则会在重启后生效。不过这种下载和安装都是静默的,即用户不可见。如果需要用户可见则需要额外的配置。具体可以参考react-native-code-push官方API文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
CodePush.sync( {
//安装模式
//ON_NEXT_RESUME 下次恢复到前台时
//ON_NEXT_RESTART 下一次重启时
//IMMEDIATE 马上更新
installMode : CodePush.InstallMode.IMMEDIATE ,
// 强制更新
// mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE
deploymentKey: CODE_PUSH_PRODUCTION_KEY,
//对话框
updateDialog : {
//是否显示更新描述
appendReleaseDescription : true ,
//更新描述的前缀。 默认为"Description"
descriptionPrefix : "更新内容:" ,
//强制更新按钮文字,默认为continue
mandatoryContinueButtonLabel : "立即更新" ,
//强制更新时的信息. 默认为"An update is available that must be installed."
mandatoryUpdateMessage : "必须更新后才能使用" ,
//非强制更新时,按钮文字,默认为"ignore"
optionalIgnoreButtonLabel : '稍后' ,
//非强制更新时,确认按钮文字. 默认为"Install"
optionalInstallButtonLabel : '后台更新' ,
//非强制更新时,检查到更新的消息文本
optionalUpdateMessage : '有新版本了,是否更新?' ,
//Alert窗口的标题
title : '更新提示'
} ,
} ,
);

   上面的配置在检查更新时会弹出提示框,mandatoryInstallMode表示强制更新,appendReleaseDescription表示在发布更新时的描述,是否会显示到更新对话框上让用户可见。

  • 方式二:设置界面,用户手动检查版本更新

    在用户检查更新按钮后进行检查,如果有更新则会弹出提 示框让用户选择是否更新,如果用户点击了立即更新按钮,则会进行安装包的下载(实际上这时候应该显示现在进度,这里忽略)下载完成后立即重启并生效(也可以配置稍后重启),配置代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
codePush.checkForUpdate(deploymentKey).then((update) => {
if (!update) {
Alert.alert("提示", "已是最新版本--", [
{
text: "Ok", onPress: () => {
console.log("点了OK");
}
}
]);
} else {
codePush.sync({
deploymentKey: deploymentKey,
updateDialog: {
optionalIgnoreButtonLabel: '稍后',
optionalInstallButtonLabel: '立即更新',
optionalUpdateMessage: '有新版本了,是否更新?',
title: '更新提示'
},
installMode: codePush.InstallMode.IMMEDIATE,

},
(status) => {
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("DOWNLOADING_PACKAGE");
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
console.log(" INSTALLING_UPDATE");
break;
}
},
(progress) => {
console.log(progress.receivedBytes + " of " + progress.totalBytes + " received.");
}
);
}
}

如何发布CodePush更新包

  CodePush支持两种发布更新的方式,一种是通过code-push release-react简化方式,另外一种是code-push realse的复杂方式

第一种方式:通过code-push release-react发布更新

这种方式将打包与发布两个命令合二为一,可以说大大简化了我们的操作流程,建议大家多使用这种方式来发布更新

命令格式如下:

1
$ code-push release-react <AppName> <Platform>

eg:

1
2
$ code-push release-react iOSRNHybrid ios
$ code-push release-react iOSRNHybrid android

再来个更加高级的,命令格式如下:

1
$ code-push release-react <AppName> <Platform> --t <App项目版本号> --des <本次更新说明> --dev <是否调试> --d <Staging/Prodcution> --m <是否强制更新>

eg:

1
$ code-push release-react iOSRNHybrid ios --t 1.0.0 --des '这是第一个更新包' --dev false --d Production --m true

注意: CodePush默认是Staging环境的,如果发布生产环境的更新包,需要指定 –d参数: --d Production,如果发布强制更新包,还需要加上 --m true强制更新,这里的--t参数,不是更新包的版本,而且工程项目的版本号。

关于code-push release-react更多可选的参数,可以在终端输入code-push release-react进行查看。

另外我们可以通过code-push depleyment ls 来查看发布详情与此次更新的安装情况

第二种方式:通过code-push realse 发布更新

code-push release发布更新之前,我们需要将js与图片资源进行打包处理成bundle。

生成bundle

我们在RN项目的根目录创建bundle文件夹,再在bundle中创建ios和android文件夹,最后将生成的bundle文件和资源文件拖到我的项目工程中

生成bundle的命令如下

1
$ react-native bundle --platform <平台名称> --entry-file <启动文件> --bundle-output <打包js输出文件的目录> --assets-dest <资源输出目录> --dev <是否调试>


eg:

1
$ react-native bundle --platform ios --entry-file index.js --bundle-output ./bundle/ios/main.jsbundle --assets-dest ./bundle/ios --dev false




上传bundle

将生成的bundle文件上传到CodePush,我们输入以下命令即可

1
$ code-push release <AppName> <Bundles所在的目录> <对应的应用版本号> --d <Staging/Production> --des <更新描述> --m <是否强制更新>


eg:

1
$ code-push release iOSRNHybrid ./bundle/ios/main.jsbundle 1.0.0 --d Staging --des '第一次relase的方式更新' --m false


注意: CodePush默认是Staging环境的,如果发布生产环境的更新包,
–d: --d Production,默认是Staging环境的
–m: --m true,发布强制更新包, –t: 注意这里不是更新包的版本,而且工程项目的版本号。
* 注意这里如果涉及到非js的资源(图片、文件等资源)文件打包上传,这里的Bundles所在的目录为bundle目录

常用命令

部署App相关的命令

  • code-push deployment add 部署
  • code-push deployment rename 重命名
  • code-push deployment rm 删除部署
  • code-push deployment ls 列出应用的部署情况
  • code-push deployment ls -k 查看部署的key
  • code-push deployment history 查看历史版本(Production/Staging)

参考链接

代码实现热更新:https://www.jianshu.com/p/1240e4eae418