React Native热更新部署-CodePush(适配Android)

CodePush简介

CodePush是微软提供的一套用于热更新 React Native 和 Cordova 应用的服务。CodePush 是提供给 React Native 和 Cordova 开发者直接部署移动应用更新给用户设备的云服务。CodePush 作为一个中央仓库,开发者可以推送更新 (JS, HTML, CSS and images),应用可以从客户端 SDK 里面查询更新。CodePush 可以让应用有更多的可确定性,也可以让你直接接触用户群。在修复一些小问题和添加新特性的时候,不需要经过二进制打包,可以直接推送代码进行实时更新。

官方文档:http://microsoft.github.io/code-push/docs/

安装CodePush客户端与注册账号

  • 安装客户端
1
npm install -g code-push-cli
  • 查看版本
1
code-push -v
  • 注册账号
1
code-push register

授权登陆并得到 access key

  • 登陆
1
code-push login
  • 查看当前登录用户
1
code-push whoami
  • 注销
1
code-push logout
  • 列出当前登陆会话
1
code-push session ls
  • 删除登录会话
1
code-push session rm <machineName>

访问密钥

  • 假如您不想通过 Github 或者 Microsoft 第三方授权获取 access key,那么可以通过命令方式生成,默认有效期为60天
1
code-push access-key add "VSTS Integration"
  • 通过 access key 登录
1
code-push login --accessKey <accessKey>
  • 设置用户名及有效期
1
code-push access-key patch <accessKeyName> --name "new name" --ttl 10d

CodePush服务器APP管理

  • 添加APP
1
code-push app add <appName>
  • 分平台添加APP
1
2
code-push app add MyApp-Android
code-push app add MyApp-iOS
  • 重命名APP
1
code-push app rename <appName> <newAppName>
  • 删除APP
1
code-push app rm <appName>
  • 查看当前账号下APP列表
1
code-push app ls

部署管理

一个APP名字默认对应两个部署环境:生产环境Production、模拟或演示环境Staging

  • 添加部署环境
1
code-push deployment add <appName> <deploymentName>
  • 删除及重命名部署环境
1
2
code-push deployment rm <appName> <deploymentName>
code-push deployment rename <appName> <deploymentName> <newDeploymentName>
  • 查看指定APP的部署环境
1
code-push deployment ls <appName> [--displayKeys|-k]

-k 表示显示 Deployment Key

Android集成CodePush、打包、发布更新

参考:https://github.com/Microsoft/react-native-code-push

安装组件

1
npm install --save react-native-code-push@latest

链接组件

1
2
// React Native v0.27+
react-native link react-native-code-push
1
2
// React Native v0.27-
rnpm link react-native-code-push

过程需要输入部署环境 key

使用CodePush更新策略

使用 CodePush 包裹根组件

1
2
3
4
5
6
7
//ES6
import codePush from "react-native-code-push";

class MyApp extends Component {
}

MyApp = codePush(MyApp);
1
2
3
4
5
6
//ES7
import codePush from "react-native-code-push";

@codePush
class MyApp extends Component {
}
  • 使用更新选项
1
2
3
4
5
6
let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };

class MyApp extends Component {
}

MyApp = codePush(CodePushOptions)(MyApp);
  • 手动更新选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let codePushOptions = { checkFrequency: codePush.CheckFrequency.MANUAL };

class MyApp extends Component {
onButtonPress() {
codePush.sync({
updateDialog: true,
installMode: codePush.installMode.IMMEDIATE
});
}

render() {
<View>
<TouchableOpacity onPress={this.onButtonPress}>
<Text>Check for updates</Text>
</TouchableOpacity>
</View>
}
}

MyApp = codePush(CodePushOptions)(MyApp);

Redux Saga for CodePush:react-native-code-push-saga

发布更新

1
2
3
4
code-push release-react <appName> <platform>

code-push release-react MyApp ios
code-push release-react MyApp-Android android

自动打包js和资源文件成bundle并且上传发布到CodePush的服务器。

也可以自己打包:

react-native bundle –platform 平台 –entry-file 启动文件 –bundle-output 打包js输出文件 –assets-dest 资源输出目录 –dev 是否调试

1
react-native bundle --platform android --entry-file index.android.js --bundle-output ./bundles/index.android.bundle --dev false

然后手动上传:

code-push release <应用名称> <Bundles所在目录> <对应的应用版本> –deploymentName 更新环境 –description 更新描述 –mandatory 是否强制更新

1
code-push release CodePushDemo ./bundles/index.android.bundle 1.2.0 --deploymentName Staging --description "支持文章缓存" --mandatory true

多环境部署测试的步骤

客户端的回滚机制可以导致服务器端的回滚:code-push rollback

  • 发布更新到Staging环境:code-push release-react <appName> <platform>
  • 运行Staging版APP,同步更新,验证其是否正常工作
  • 从演示环境推进到生产环境:code-push promote
  • 运行Production版APP,同步更新,验证其是否正常工作

我们注意到第2步与第4步,需要生成两种APP,其实可以整合,但是却是分平台的,这里以Android为例。

配置 Debug 和 Release 模式分别对应 Staging 和 Production 部署环境

(1)使用 Android Studio 打开修改 app/build.gradle,配置 debug下 Staging部署环境的 key和 release下 Production部署环境的 key,然后同步

1
2
3
4
5
6
7
8
9
10
11
12
13
buildTypes {
debug {
...
buildConfigField "String", "CODEPUSH_KEY", '"<INSERT_STAGING_KEY>"'
...
}

release {
...
buildConfigField "String", "CODEPUSH_KEY", '"<INSERT_PRODUCTION_KEY>"'
...
}
}

(2)修改 MainApplication

1
2
3
4
5
6
7
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
//new CodePush(getResources().getString(R.string.reactNativeCodePush_androidDeploymentKey), getApplicationContext(), BuildConfig.DEBUG)
new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG)
);
}

配置之后,当你调试的时候更新用到的是Staging的key,当签名发布正式包的时候用的就是Production的Key

默认情况下, 用到的是Debug,对应 Staging环境

1
react-native run android

如果你想测试正式的版本包,即 Production环境,则运行:

1
react-native run-android --variant release

CodePush 插件的使用场景

CodePush 插件是由两部分组成:

  • 1、js模块
  • 2、原生API

CodePush 使用场景

下面列出CodePush一些常用的用法,你可以选择一个或者组合使用:

Silent sync on app start

1
2
3
4
// Fully silent update which keeps the app in sync with the server, without ever interrupting the end user
class MyApp extends Component {
}
MyApp = codePush(MyApp);

Silent sync everytime the app resumes

1
2
3
4
5
6
7
// Sync for updates everytime the app resumes.
class MyApp extends Component {
}
MyApp = codePush({
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESUME
})(MyApp);

Interactive

1
2
3
4
5
6
7
// Active update, which lets the end user know about each update, and displays it to them immediately after downloading it
class MyApp extends Component {
}
MyApp = codePush({
updateDialog: true,
installMode: codePush.InstallMode.IMMEDIATE
})(MyApp);

Log/display progress

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
// Make use of the event hooks to keep track of the different stages of the sync process.
class MyApp extends Component {
codePushStatusDidChange(status) {
switch(status) {
case codePush.SyncStatus.CHECKING_FOR_UPDATE:
console.log("Checking for updates.");
break;
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("Downloading package.");
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
console.log("Installing update.");
break;
case codePush.SyncStatus.UP_TO_DATE:
console.log("Installing update.");
break;
case codePush.SyncStatus.UPDATE_INSTALLED:
console.log("Update installed.");
break;
}
}

codePushDownloadDidProgress(progress) {
console.log(progress.receivedBytes + " of " + progress.totalBytes + " received.");
}
}
MyApp = codePush(MyApp);

CodePush 属性及回调方法

CodePushOptions 选项对象

CodePushOptions 选项对象可以自定义,其参数包括:

(1) checkFrequency (codePush.CheckFrequency)

检查频率,其值包括:

  • codePush.CheckFrequency.ON_APP_START (0)(默认)
  • codePush.CheckFrequency.ON_APP_RESUME (1)
  • codePush.CheckFrequency.MANUAL (2)

(2) deploymentKey (String)

部署环境 key

(3) installMode (codePush.InstallMode)

可选的安装模式,其值包括:

  • codePush.InstallMode.IMMEDIATE (0)(默认)
  • codePush.InstallMode.ON_NEXT_RESTART (1)
  • codePush.InstallMode.ON_NEXT_RESUME (2)

(4) mandatoryInstallMode (codePush.InstallMode)

强制性的安装模式,其值包括:

  • codePush.InstallMode.IMMEDIATE (0)(默认)
  • codePush.InstallMode.ON_NEXT_RESTART (1)
  • codePush.InstallMode.ON_NEXT_RESUME (2)

(5) minimumBackgroundDuration (Number)

只在 InstallMode.ON_NEXT_RESUME 安装模式下有效,设置更新生效的时间间隔,单位为秒

(6) updateDialog (UpdateDialogOptions)

更新弹窗,包含选项:

  • appendReleaseDescription (Boolean) - false
  • descriptionPrefix (String) - “ Description: “
  • mandatoryContinueButtonLabel (String) - “Continue”
  • mandatoryUpdateMessage (String) - “An update is available that must be installed.”
  • optionalIgnoreButtonLabel (String) - “Ignore”
  • optionalInstallButtonLabel (String) - “Install”
  • optionalUpdateMessage (String) - “An update is available. Would you like to install it?”
  • title (String) - “Update available”

(7) codePushStatusDidChange

codePushStatusDidChange (event hook)

status code 产生变化的回调,SyncStatus 所有值:

  • codePush.SyncStatus.CHECKING_FOR_UPDATE (0)
  • codePush.SyncStatus.AWAITING_USER_ACTION (1)
  • codePush.SyncStatus.DOWNLOADING_PACKAGE (2)
  • codePush.SyncStatus.INSTALLING_UPDATE (3)
  • codePush.SyncStatus.UP_TO_DATE (4)
  • codePush.SyncStatus.UPDATE_IGNORED (5)
  • codePush.SyncStatus.UPDATE_INSTALLED (6)
  • codePush.SyncStatus.SYNC_IN_PROGRESS (7)
  • codePush.SyncStatus.UNKNOWN_ERROR (-1)

(8) codePushDownloadDidProgress

codePushDownloadDidProgress (event hook)

更新下载过程回调,包含参数:

  • totalBytes (Number)
  • receivedBytes (Number)

实验调试

效果:程序员视角、终端用户视角

报错:The uploaded package is identical to the contents of the specified deployment’s current release 表示:已经存在与这次上传完全一样的bundle(对应一个版本有两个bundle的md5完全一样),那么CodePush会拒绝此次更新。

index.android.js

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/

import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Alert,
Text,
Image,
View
} from 'react-native';

import codePush from "react-native-code-push";

class CodePushDemo extends Component {
codePushStatusDidChange(status) {
switch (status) {
case codePush.SyncStatus.CHECKING_FOR_UPDATE:
console.log("Checking for updates.");
break;
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("Downloading package.");
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
console.log("Installing update.");
break;
case codePush.SyncStatus.UP_TO_DATE:
console.log("Installing update.");
break;
case codePush.SyncStatus.UPDATE_INSTALLED:
console.log("Update installed.");
break;
}
}

codePushDownloadDidProgress(progress) {
console.log(progress.receivedBytes + " of " + progress.totalBytes + " received.");
}

componentDidMount() {
console.log('组件加载后执行');
//访问慢,不稳定
codePush.checkForUpdate().then((update) => {
if (!update) {
Alert.alert("提示", "已是最新版本", [ {
text: "Ok", onPress: () => {
console.log("最新版本确定");
}
}]);
} else {
codePush.sync({ updateDialog: true, installMode: codePush.InstallMode.IMMEDIATE });
}
});
}

render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.android.js
</Text>
<Text style={styles.instructions}>
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu
</Text>
<Image
style={styles.img}
resizeMode="contain"
source={require('./rnapp.png')}
/>
<Text style={styles.author}>Powered by RNAPP.CC</Text>
</View>
);
}
}

CodePushDemo = codePush(CodePushDemo);

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,
},
img: {
height: 60,
width: 332,
marginTop: 20,
},
author: {
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 10,
marginTop: 50,
textAlign: 'center',
fontSize: 16,
color: '#3BC1FF',
},
});

AppRegistry.registerComponent('CodePushDemo', () => CodePushDemo);

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :