WdBly Blog

懂事、有趣、保持理智

WdBly Blog

懂事、有趣、保持理智

周维 | Jim

603927378@qq.com

最新React Native 搭建本地Code Push服务(非常全!)

React Native 搭建本地Code Push服务, iOS、Android项目配置, 获取和发布更新, 错误汇总。

React-native-code-push 是微软针对React-native推出的热更新服务,因为公司产品有接入热更新的需求,所以在项目中接入了code-push, 本篇文章我为大家介绍本次为App接入code-push的过程, 包括可能遇到的一些问题, 希望大家能从中找到想要的答案。

本片文章从六个部分介绍本次接入:

  1. code-push服务端
  2. code-push客户端
  3. react-native项目
  4. 在项目中使用code-push检测安装更新
  5. 使用code-push发布更新
  6. 常见问题和踩过的坑

本次项目的项目依赖的关键包的版本如下

react-native: 0.59.9, // 后改成5.6.0 原因见常见错误 react-native-code-push: 5.7.0

一、本地Code Push 服务搭建

自建Code Push 服务器

1:安装mysql

mac MySql安装

2:下载 code-push-server 仓库

git clone https://github.com/lisong/code-push-server.git

cd code-push-server && npm install

3:修改 code-push-server 仓库配置

vim config/config.js

1:修改db中的数据库信息, 如下:

image.png

2:修改local对象的下载地址为本机(或服务器的ip地址)

image.png

3:修改jwt对象下的tokenSecret

打开 https://www.grc.com/passwords.htm, 复制其生成的随机字符串作为秘钥即可。

4: 创建storageDir 和 dataDir

根据local对象下的storageDir路径, 创建相应的文件夹。
创建common对象下的dataDir文件夹

image.png

5:初始化数据库信息

./bin/db init --dbhost localhost --dbuser root --dbpassword 数据库密码

关于Mac下数据库安装问题请参考mac MySql安装

4:启动本地的code-push服务端

./bin/www

二、Code Push 客户端

1: 首先全局安装微软提供的code-push-cli工具

npm install code-push-cli@latest -g

常用code-push命令

  • 注册账号: code-push register

  • 登陆: code-push login

  • 注销: code-push logout

  • 添加项目: code-push app add app名称

  • 删除项目: code-push app remove app名称

  • 列出账号下的所有项目: code-push app list

  • 显示登陆的token: code-push access-key ls

  • 部署一个环境: code-push deployment add appName deploymentName

  • 删除部署: code-push deployment rm appName

  • 列出应用的部署: code-push deployment ls appName

  • 查询部署环境的key: code-push deployment ls appName -k

我们会在后面使用此工具创建,发布,更新App

2:登录code-push服务器

code-push login http://localhost:3000

在弹出的网页中登录,账号:admin, 密码: 123456,然后获取token,将token复制到控制台中登录即可。

3:创建应用

code-push app add CodePushDemoIos ios react-native

code-push app add CodePushDemoAndroid android react-native

4:获取应用的Key

code-push deployment ls <appName> -k

appName 是我们创建的应用名称,后面发布应用时也会使用它。

code-push deployment ls 查看app的部署环境


三、React Native 项目配置

在React Native项目中首先安装react-native-code-push

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

react-native link react-native-code-push

此时会弹出输入App Key的提示,可以暂时忽略

iOS配置

1:xcode -> PROJECT -> Info -> Configurations -> 点击+ -> 选泽Duplicate “Release Configaration , 输入Staging。 这表示新建一种打包方式 名称是Staging。

2:xcode -> TARGET -> Build Setting
image.png

然后输入CODEPUSH_KEY,然后在下面的release中填写product deployment key, 在Staging下填写 staging deploymeng key, debug可以不用填写。

同理,新增CODEPUSH_URL, 填写各个环境的code-push服务器地址,如Staging我们可以填写 本机iP://3000;

3: ios/<项目>/info.plist中新增

<key>CodePushDeploymentKey</key> <string>$(CODEPUSH_KEY)</string> <key>CodePushServerURL</key> <string>$(CODEPUSH_HOST)</string> // 修改为三位数版本号 <key>CFBundleShortVersionString</key> <string>1.7.0</string>

Android配置

1:MainApplication.java 中用Android应用的Key和Code Push服务器地址替换下面的内容 :

@Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new CodePush( "YourProductKey", MainApplication.this, BuildConfig.DEBUG, "YourProductHOSTURL" ) ); }

2:将默认的版本号改成1.0.0类型的三位版本号, 已经是三位版本号的请忽略。

检测环境安装情况

import CodePush from “react-native-code-push”;
console.log(CodePush);


四、检查更新

当前的code-push环境已经安装完成, 我们需要使用code-push客户端发布新的app版本, 同时在项目中检查更新, code-push的最简单的检查更新如下:

// 普通方式 import CodePush from "react-native-code-push"; class App extends React.Component {} export default CodePush(App); // ES7 装饰器的方式加载 @CodePush class App extends React.Component {} export default App;

使用CodePush高阶函数包裹根组件, 这样会在每次启动App时检查,下载,安装App。 使用高阶组件可以实现App自动更新。

CodePush也可以接受一个检查更新相关的配置对象CodePushOptions,使用如下:

import CodePush from "react-native-code-push"; class App extends React.Component {} export default CodePush(CodePushOptions)(App);

CodePushOptions配置对象有如下属性:

1、deploymentKey

指定要查询更新的部署密钥。一般来说code-push会从info.plist或者MainActivity.java文件中获取,但是我们可以使用此属性覆盖文件中的key值。

2、checkFrequency

指定检查更新的时间,可取值如下:

  1. codePush.CheckFrequency.ON_APP_START:启动时检查
  2. codePush.CheckFrequency.ON_APP_RESUME:从后台返回时检查
  3. codePush.CheckFrequency.MANUAL:禁用自动检查更新,仅在调用sync方法时检查

3、installMode

installMode表示应用程序应该何时安装更新(此更新未被标记为必需更新), 取值有以下四种

  1. codePush.InstallMode.IMMEDIATE, 表示马上安装更新并重启应用程序,此模式通常使用在提示用户更新时, 因为用户在点击更新后往往希望马上看到更新, 也常用于强制更新。
  2. codePush.InstallMode.ON_NEXT_RESTART, 表示你要安装更新但不重启应用程序, 当程序下次启动时会自然更新。
  3. codePush.InstallMode.ON_NEXT_RESUME, 表示你要安装更新但不重启应用程序, 当程序从后台恢复后自然更新(也就是常用的resume事件),当应用程序在后台超过minimumBackgroundDuration秒后恢复到前台,其实会相当于重启codePush.restartApp方法
  4. codePush.InstallMode.ON_NEXT_SUSPEND, 表示应用程序需要在后台minimumBackgroundDuration秒后才开始安装更新, minimumBackgroundDuration默认为0;

4、mandatoryInstallMode

当code-push服务器上的一项更新被标记为必需更新时,指定此更新的安装时机,默认为codePush.InstallMode.IMMEDIATE, 其他值参考installMode

5、minimumBackgroundDuration

表示应用程序需要在后台minimumBackgroundDuration秒后才开始安装更新,当installMode的值为codePush.InstallMode.ON_NEXT_RESUME或者codePush.InstallMode.ON_NEXT_SUSPEND时生效。

6、updateDialog

当updateDialog为一个真值时, 如果有可用更新, 向用户展示一个确认更新对话框, 默认为null(不展示对话框)。

根据地区和平台不同,各大应用市场对更新确认框有不同限制,目前只有google play需要更新确认提示, app store和中国大陆应用市场不允许弹更新确认框。

updateDialog属性可以配置对话框的部分属性, 包括一些话术, 这里就不说了, 一般如果需要做弹框提醒更新,往往会自定义弹框样式,不会使用原本的弹框, 在启动app时调用codePush.checkForUpdate()方法,在有更新时提醒更新, 如确实需要请自行查阅官方文档。

除了使用高阶组件的方式检查安装更新,我们也可以使用调用方法的方式检查更新, codePush上关于检查更新有如下方法:

一、codePush.sync(option, statusDidChange, downloadDidProgress)

codePush.sync方法是检测更新, 下载更新, 安装更新为一体方法, 它接收三个参数, option为配置对象,statusDidChange为更新过程状态改变的回调函数, statusDidChange参数是从code-push服务器下载更新时定时调用的回调函数,通常可以用于向用户展示进度。 调用该方法即可自动更新

option对象配置如下和CodePushOptions一致, 只是没有checkFrequency指定检查时间方法, 这是因为在调用sync方法后马上就会去检查更新。

codePush.sync()的后两个回调参数如下:

1、statusDidChange ((syncStatus: Number) => void)

statusDidChange回调会返回app的安装更新情况, 每个阶段都会触发,syncStatus一共有如下情况

  1. codePush.SyncStatus.UP_TO_DATE:应用程序与配置的部署完全一致。
  2. codePush.SyncStatus.UPDATE_INSTALLED:已安装可用更新,将在此函数返回后立即运行,或者在下次应用程序恢复/重新启动时运行,具体取决于installMode的值。
  3. codePush.SyncStatus.UPDATE_IGNORED:应用程序有一个可选的更新,最终用户选择忽略。(仅在updateDialog使用时适用)
  4. codePush.SyncStatus.UNKNOWN_ERROR:同步操作遇到未知错误
  5. codePush.SyncStatus.CHECKING_FOR_UPDATE:正在查询code-push服务器以进行更新。
  6. codePush.SyncStatus.AWAITING_USER_ACTION:有可用更新,并向最终用户显示确认对话框。(仅在updateDialog使用时适用)。
  7. codePush.SyncStatus.DOWNLOADING_PACKAGE:正在从服务器下载可用更新。
  8. codePush.SyncStatus.INSTALLING_UPDATE:已下载更新,即将安装。
// 用法如下: codePush.sync({ ... }, this.codePushStatusDidChange ) codePushStatusDidChange = syncStatus => { switch(syncStatus) { case CodePush.SyncStatus.CHECKING_FOR_UPDATE: consloe.log("Checking for update."); } ... }

2、downloadDidProgress((progress: DownloadProgress) => void)

下载更新过程中定时调用此回调函数, DownloadProgress参数是返回的进度,其中包含了两个属性:

  • totalBytes: 此次更新的从字节数
  • receivedBytes: 当前已经接收的字节数
codePush.sync({ ... }, this.codePushStatusDidChange, this.codePushDownloadDidProgress ) codePushDownloadDidProgress = progress => { console.log(progress) }

在使用高阶函数包裹根组件的方式中,也会有这两个回调, 只不过是以生命周期函数出现的, 用发是在App根组件中添加两个生命周期方法, 用法如下。

import CodePush from "react-native-code-push"; class App extends React.Component { codePushStatusDidChange(state){ ... } codePushDownloadDidProgress(progress){ ... } } export default CodePush(App);

一般来说,我们使用高阶函数或者sync方法配合一些配置已经可以完成检查更新的大部份需求, 但有时我们需要手动去控制整个过程(检查更新, 下载更新, 安装更新), 这时我们可能会用到下面的一些高级方法,为了对code-push有一个更清晰的了解,我们还是需要了解这些方法的用处用法, 下面我会一一介绍。

二、codePush.disallowRestart()方法

由于安装了更新, 在下次启动时安装的更新会被应用。 在这期间(安装了更新但还未重启), 调用codePush.disallowRestart()可以禁止通过程序重启App.

什么时候会用到此方法呢?
当installMode的值为IMMEDIATE,或ON_NEXT_RESUME,或者手动调用codePush.restart()方法时。
也可以理解为codePush.disallowRestart()方法阻止codePush.restart()的调用。

在调用codePush.disallowRestart()方法后,任然可以获取和安装更新, 但必须等待allowRestart方法被调用后才会重启。

三、codePush.allowRestart()方法

如果调用了disallowRestart方法,而导致更新被挂起(未重启),那么调用allowRestart方法将立即重启程序。

否则将会有以下四种情况

  1. 这期间没有更新,所以无需重启
  2. installMode为ON_NEXT_RESTART(下次启动更新), 所以无需重启
  3. installMode为ON_NEXT_RESUME,但程序一直在前台,所以无需重启
  4. 这期间没有调用过restartApp方法

使用

class App extends Component { componentWillMount(){ // 组件活动状态不允许重启 codePush.disallowRestart(); } componentWillUnmount(){ // 组件卸载时可以运行重启更新了 codePush.allowRestart(); } ... }

四、codePush.checkForUpdate(deploymentKey, handleBinaryVersionMismatchCallback)

checkForUpdate用于查询code-push服务器是否有可用更新, 它接收两个参数,第一个是deploymentKey可用于覆盖配置文件中的key, 第二个为查询的回调函数。

handleBinaryVersionMismatchCallback返回一个promise表示查询结果, 有两种情况:

  1. null
  2. 可用的更新实例RemotePackage

返回结果为null表示无更新 可能是如下几种情况造成的:

  1. 服务器上该部署还没有任何版本
  2. 配置部署的二进制版本和当前用户版本不一致(二进制版本更新需重新上传应用商店)
  3. 已经是最新版本
  4. 部署中的版本被标记为禁用
  5. 部署中的最新版本是活动部署状态,当前用户不在百分百范围内(也就是灰度发布)

否则返回一个可用的更新实例RemotePackage(远端包的实例)

这个实例中包含了一些包的基础信息和下载信息, 另外提供了一个下载方法,用于我们调用此方法下载更新。具体如下

  1. appVersion: 二进制包的版本号
  2. deploymentKey: 秘钥
  3. packageSize: 包的大小
  4. downloadUrl: 包的地址
  5. download(downCallBack ? function) : Promise, 下载的回调, 下载完成后会返回一个 LocalPackage本地包的实例。
  6. 其他的属性不说了…

使用

codePush.checkForUpdate().then(update => { if (!update) { console.log("上面那五种失败情况之一"); } else { console.log("有可用更新"); // 下载远端的包到本地 update.download(this.downCallBack); } });

将远端的包下载到本地后,可以拿到LocalPackage本地包的实例,本地包实例包含了和LocalPackage包相似的属性方法, 另外提供了一个install方法用于安装更新。

五、codePush.notifyAppReady()

调用此方法通知codePush服务器新的安装已经成功,此方法用在手动下载更新时,如果没有调用此方法通知,那么在下一次启动app时,code-push服务器会任务上一次安装失败了,然后会回滚更新。 在使用sync方法或者高阶函数时不需要调用此方法。

六、codePush.getUpdateMetadata(UpdateState)

获取用户当前已安装的二进制文件信息

七、codePush.restartApp

立即重启应用程序, 但有可能被阻止。

五、发布更新

1: 进入React Native项目目录

2: 执行:

code-push release-react <appName> ios

所有发布相关参数如下:

appName //必须 app名称 OS //必须 发布平台iOS/Android updateContents //非必须 Bundle文件所在目录 targetBinaryVersion //非必须 需要热更的app 版本 deploymentNmae //必须 需要发布的部署 description //非必须 描述 disabled //非必须 该版本客户端是否可以获得更新,默认为false mandatory //非必须 如果有则表示app强制更新 优先级较高,应用更新后会立即重启。
code-push release ReactDemoIos -t "1.0.0" --deploymentName Production --description "测试更新" --mandatory true

需要注意的是 deploymentNmae默认是Staging
targetBinaryVersion是指的当前app的版本
mandatory在更新完成后会强制重启app(调用restartApp方法)除非先调用了disallowRestart方法

六、Code-Push常见错误

使用code-push命令时出现下面错误:

Unable to connect to the CodePush server. Are you offline, or behind a firewall or proxy?

在我这里是由于iTerm软件的问题。 重启iTerm即可。

‘CodePush/CodePush.h’ file not found

xcode中出现错误 ‘CodePush/CodePush.h’ file not found
image.png

解决方案:

找到 xcode Build Settings -> Search Paths -> Header Search Paths -> Debug和Release

将$(SRCROOT)/…/node_modules/react-native-code-push/ios/CodePush 修改为 $(SRCROOT)/…/node_modules/react-native-code-push/ios

$(SRCROOT)/…/node_modules/react-native-code-push/ios

上述解决方案存在一定的问题, 当我们遇到’CodePush/CodePush.h’ file not found时先不要着急修改 Search Paths, 可以先尝试Clena Build Folder然后看是否能正确打包, 否则直接修改了可能会造成下面的错误ios [CodePush] The CodePush module doesn’t appear to be properly installed. Please double-check that everything is setup correctly.

ios [CodePush] The CodePush module doesn’t appear to be properly installed. Please double-check that everything is setup correctly.

出现此错误的原因是xcode没有正确的导入code-push

解决

xcode -> 选择项目 -> Build Settings -> Search Paths -> Header Search Paths;

添加code-push的路径如下

$(SRCROOT)/../node_modules/react-native-code-push/ios/CodePush

如果项目分为了Debug和Release模式,需要为它们俩分别添加

code push check_update 报错404

这是由于react-native-code-push@5.7.0的更新导致的,code-push-server仓库还没有同步更新。

解决方案,在code-push-server仓库更新前,都只能先使用@5.6.x版本的react-native-code-push包

doesn’t specify a value for the “android.defaultConfig.versionName” property.

当运行 code-push release-react ReactDomeAndroid android命令时出现上述错误

解决: android/app/build.gradle -> defaultConfig添加属性versionName, 如:

defaultConfig { versionName "1.7.0" }

[CodePush] Unable to get the hash of the binary’s bundled resources - “codepush.gradle” may have not been added to the build definition.

安卓控制台输出此错误, 解决方案

1: 进入项目下的android

cd android

2: 使用命令打包apk

./gradlew assembleRelease

3: 在android studio中签名

Build -> Generate Signed Apk