Fork me on GitHub

ReactNative之搭建本地Code-Push-Server服务器实现热更新

CodePush简介

CodePush是微软开发的云服务器,主要应用于应用更新服务,通过它开发者可以直接在设备上部署手机应用更新。

CodePush相当于一个中心仓库,开发者可以推送当前的更新包(包括JS/HTML/CSS/Image等)到中心仓库,然后应用将会查询是否有更新,会自动下载更新包来更新应用,这里运用到了差异更新和版本控制、版本回退等处理操作,后续会详细说明。

热更新的大致原理如下:

由于ReactNative会将所有所需要加载的js文件、图片等资源打包到一个bundle文件中,而app运行时会加载该文件。所以如果要升级app,大致就是动态替换该bundle文件,然后重启该app即可(如果修改了底层Native代码则需要重新安装该app)。

实际上在开始时使用的更新模式就是上买呢所说。直接替换掉bundle文件。开发环境时使用调试工具可以Realod JS(动态替换bundle文件)。但是在生产环境则不存在该调试工具,需要自己手动实现动态替换bundle文件的功能,而CodePush就是实现了该功能的一个工具类。

然而CodePush服务器在国外,国内访问速度是很不理想的,所以自建本地CodePush服务器是最理想的。

自建CodePush服务

CodePush服务主要分为三个部分:服务器端、客户端、ReactNative项目。

一、服务器端

服务器端需要使用code-push-serverMySQL,提前安装好。

1. 安装MySQL

注意:MySQL一定要配置好,账号和密码一定要记住,供后续配置code-push-server使用,可以现在终端敲入mysql -uroot -p<密码>,看看是否会进入mysql终端命令,如果无法进入,考虑更新密码等操作,完成MySQL相关操作。

注意:

在初始化数据库的时候遇到此问题

解决办法如下,实现进入mysql终端命令:

1
2
3
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
// 进行刷新
FLUSH PRIVILEGES;
2. 安装code-push-server

github上提供了两种安装方式(npm安装和源码安装),在此我推荐使用源码安装,为后期我们要基于这个服务器修改自己的网页、源码安装方便些,本人采用docker的方式搭建的本地服务器。

  • 下载code-push-server代码
1
2
3
4
5
// clone代码
git clone https://github.com/lisong/code-push-server.git

// 进入code-push-server项目目录并安装资源
cd code-push-server && npm install
  • 修改config.js配置,在db对象中配置数据库相关信息,参考如下:

  • 初始化数据库,cd到code-push-server目录
1
2
3
4
5
// cd到code-push-server目录,然后进行初始化
cd code-push-server

// 初始化数据库
./bin/db/init --dbhost localhost -dbuser root --dbpassword <数据库密码>

如果数据库初始化过,跳过该步骤,因为初始化数据库的时候,默认是没有密码的,请到code-push-server安装目录下的bin/db中修改


密码修改成功后,在执行初始化数据库命令就可以成功了,如果这一步都没有成功,那就必须把MySQL的坑趟平在往下执行,否则以下都是空谈。

1
2
3
4
5
// 初始化mysql数据库
./bin/db init --dbhost localhost --dbuser root --dbpassword 数据库密码

eg..
./bin/db init --dbhost 127.0.0.1 --dbuser root --dbpassword 123456

  • 数据库地址配置完毕后,配置打包后bundle的存储地址,这里配置local本地可以配置qiniuOSS

创建storagedata文件夹,用来保存打包好的资源,供用户下载,downloadUrl必须为服务器所在的地址,不然用户无法下载到更新包。主要配置如下:

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
// 如果存储类型“storageType”为“qiniu”如果更新包放在七牛,需要配置相关信息 (http://www.qiniu.com/) 。
qiniu: {
accessKey: "",
secretKey: "",
bucketName: "",
downloadUrl: "" //文件下载域名地址
},

//阿里云存储配置 当storageType为oss时需要配置
oss: {
accessKeyId: "",
secretAccessKey: "",
endpoint: "",
bucketName: "code-push-server",
prefix: "storage", // Key prefix in object key
downloadUrl: "https://code-push-server.oss-cn-shenzhen.aliyuncs.com/storage", // binary files download host address.
},

//文件存储在本地配置 当storageType为local时需要配置
local: {
storageDir: "/Users/tablee/workspaces/storage",
//文件下载地址 CodePush Server 地址 + '/download' download对应app.js里面的地址
downloadUrl: "http://localhost:3000/download",
// public static download spacename.
public: '/download'
},

jwt: {
// 登录jwt签名密钥,必须更改,否则有安全隐患,可以使用随机生成的字符串
// Recommended: 63 random alpha-numeric characters
// Generate using: https://www.grc.com/passwords.htm
tokenSecret: '4l6zzyvxMzU44M30Zh6d6McFSSNnSF5GsKXwnZoiHfWPYLT4IFp1NOAA4bHTac6'
},

common: {
dataDir: "/Users/tablee/workspaces/data",
//选择存储类型,目前支持local,oss,qiniu,s3配置
storageType: "local"
},

注意:
common选项用来配置存储类型和更新包数据存储路径的。以及jwt的签名密钥配置、jwt的签名密钥配置、jwt的签名密钥配置,重要的事情说三遍,必须要配置jwt的签名。

  • 启动服务
1
2
// 在code-push-server根目录下执行命令
./bin/www


如果在浏览器中输入:http://127.0.0.1:3000http://你的ip地址:3000 能加载到CodePushServer登录界面即表示启动完成。

二、客户端

1
npm install -g code-push-cli
  • 登录code-push-server,使code-push和自建服务器关联

执行命令查看当前是否登录,因为是新服务,所以要先保证没有别的账号正在登录

1
code-push whoami

如果报错如下,表示没有登录

1
[Error]  You are not currently logged in. Run the 'code-push login' command to authenticate with the CodePush server.

如果没有报错,并显示了邮箱账号,则表示已经登录账号,则我们需要注销当前账号

1
code-push logout

成功注销后执行登录命令,浏览器会自动打开本地服务器的登录页面,命令中会提示输入key,默认账号和密码为:admin/123456,登录成功后获取token并复制token到命令行中,回车确认

1
2
3
4
code-push login http://192.168.8.165:3000

// 提示如下,表示登录成功
Successfully logged-in……
  • 创建应用,获取DeploymentKey
1
2
3
4
5
6
7
8
9
10
11
Usage: code-push app add <appName> <os> <platform>
选项:
-v, --version 显示版本号 [布尔]

示例:
app add MyApp ios react-native Adds app "MyApp", indicating that it's an iOS React Native app
app add MyApp windows react-native Adds app "MyApp", indicating that it's a Windows React Native app
app add MyApp android cordova Adds app "MyApp", indicating that it's an Android Cordova app

e.g..
code-push app add RZCodePushSimple-iOS ios react-native

结果如下:

其中Production对应的是生产的DeploymentKey,Staging是开始时使用的。

可以通过命令行查看更多的命令,更多相关的命令,请查阅官方文档

1
2
3
4
5
6
7
8
9
10
11
12
// 查看部署的app
code-push app ls

// 查看部署的历史记录
code-push deployment history RZCodePush-iOS Staging

// 清空部署记录
code-push deployment clear RZCodePush-iOS Staging
```
#### 三、ReactNative客户端

* 安装react-native-code-push

// 项目导入CodePush代码
npm install react-native-code-push –save
// 关联项目
react-native link react-native-code-push

1
* 项目中的配置

import React from ‘react’;
import {
View,
Text,
Image,
SafeAreaView,
StyleSheet
} from ‘react-native’
import CodePush from “react-native-code-push”;

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

class HomePage extends React.Component{
// 如果有更新就提示
syncImmediateOpt = () => {
CodePush.sync( {
//安装模式
//ON_NEXT_RESUME 下次恢复到前台时
//ON_NEXT_RESTART 下一次重启时
//IMMEDIATE 马上更新
installMode : CodePush.InstallMode.IMMEDIATE ,
//对话框
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.syncImmediateOpt();
}

render() {
    return(
        <SafeAreaView>
            <Text style={styles.containerText}>快易省V1.1.0-Staging版本</Text>
            <Image source={require('./images/Snip20191121_2.png')}/>
        </SafeAreaView>
    );
}

componentDidMount() {
    // 在加载完毕的时候重启
    CodePush.allowRestart();
}

}

const styles = StyleSheet.create({
containerText: {
fontSize: 20,
color: “skyblue”
}
});
export default CodePush(CodePushOptions)(HomePage)

1
2
* Android端配置
* setting.gradle加入:

include ‘:react-native-code-push’
project(‘:react-native-code-push’).projectDir = new File(rootProject.projectDir, ‘../node_modules/react-native-code-push/android/app’)

1
2

* build.gradle修改:

apply from: “../../node_modules/react-native-code-push/android/codepush.gradle”
dependencies {
compile fileTree(dir: “libs”, include: [“*.jar”])
compile “com.android.support:appcompat-v7:23.0.1”
compile “com.facebook.react:react-native:+” // From node_modules
compile project(‘:react-native-code-push’)
}

1
2

* MainApplication文件下修改

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
//第一个参数是刚刚申请的key(可以根据环境配置)
//第三个参数是服务器的URL
@Override
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new CodePush(“ nJ3oSQmb64bxRqTP9mwMhZuZLIm94ksvOXqog “, MainApplication.this, BuildConfig.DEBUG,”http://你的IP:端口/“)
);
}

1
2
3
    * 修改版本号

将 android/app/build.gradle 中的android.defaultConfig.versionName 改成3位数的版本号(默认是1.0,但是codepush需要三位数)。

android{
defaultConfig{
versionName “1.0.0”
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
* iOS端配置
* 工程项目配置
![](../../static/images/ReactNative/Snip20190605_47.png)
* info.plist配置
![](../../static/images/ReactNative/Snip20191125_10.png)
![](../../static/images/ReactNative/Snip20191125_11.png)
CodePushDeploymentKey 即为我们注册APP时获得的key,更加开发还是生产来分别设置;CodePushServerURL对应的是我们的bundle更新包的下载地址。这里因为用的是真机调试,所以配置了服务器的ip地址。
* Native项目配置,由于公司项目只是首页模块植入了RN,所以我们是分模块加载的方式加载对应RN模块的
![](../../static/images/ReactNative/Snip20191125_12.png)
* 注意:修改系统版本号为三位小数
* 进阶功能,由于公司项目只是加载首页模块,于是处理了分模块加载方式,这样就可以实现分模块加载更新了。
![](../../static/images/ReactNative/Snip20191125_13.png)

#### 四、发布更新
`CodePush`支持两种方式发布更新,一种是通过`code-push release-react`简化方式,另外一种是通过`code-push release`的复杂方式。
* 方式一:通过code-push release-react发布更新

Usage: code-push release-react [options]

eg:
code-push release-react RZCodePushIOS ios
code-push release-react RZCodePushAndroid android

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

code-push release-react RZCodePushIOS ios –t 1.0.0 –d Production –des ‘V 1.0.0版本发布’ –m true

1
2
3
4
5
6
7
8
9
10
关于`code-push release-react`更多的参数,参考文档,通过命令`code-push release-react --help`

* 方式二:通过`code-push release`的方式发布更新

`code-push release`之前我们需要现将js和图片等资源打包处理成bundle文件

首先在项目根目录下创建bundle文件夹,然后在文件夹下分别创建ios和andorid目录,大致结构如下
![](../../static/images/ReactNative/Snip20190606_57.png)

生成bundule命令如下:

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

eg:
$ react-native bundle –platform ios –entry-file index.js –bundle-output ./bundle/ios/ –assets-dest ./bundle/ios –dev false

1
上传bundle文件到更新服务器

Usage: code-push release [options]

eg:
$ code-push release RZCodePush-iOS ./bundle/ios 1.0.0 –des ‘第一次relase的方式更新’ –d Staging –m false
`

常用命令:

部署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)

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的所有权转给另外一个账号

参考文章

lisong的github
Microsoft Code Push