react-native-android实战:4 CodePush使用

CodePush简介

CodePush 是微软开发的,可以实时更新 React Native 和 Cordova 应用。

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

CodePush 可以进行实时的推送代码更新:

  • 直接对用户部署代码更新
  • 管理 Alpha,Beta 和生产环境应用
  • 支持 Cordova 和 React Native

CodePush的react-native版本是react-native-code-push

JavaScript API

  • checkForUpdate
  • getCurrentPackage
  • notifyApplicationReady
  • restartApp
  • sync

codePush.checkForUpdate

询问code push 服务器是否app有新的版本

codePush.checkForUpdate(deploymentKey: String = null): Promise<RemotePackage>;

该方法返回Promise,有如下两种值:

  1. null 没有更新
  2. A RemotePackage instance

举例用法:

1
2
3
4
5
6
7
8
codePush.checkForUpdate()
.then((update) => {
if (!update) {
console.log("The app is up to date!");
} else {
console.log("An update is available! Should we download it?");
}
});

codePush.getCurrentPackage

获得当前已安装更新的元数据(比如描述,安装时间,大小)
举例:

1
2
3
4
5
6
7
8
9
codePush.getCurrentPackage()
.then((update) => {
// If the current app "session" represents the first time
// this update has run, and it had a description provided
// with it upon release, let's show it to the end user
if (update.isFirstRun && update.description) {
// Display a "what's new?" modal
}
});

codePush.notifyApplicationReady

通知CodePush进程,一个更新安装好了。当你检查并安装更新,(比如没有使用sync方法去handle的时候),这个方法必须被调用。否则CodePush会认为update失败,并rollback当前版本,在app重启时。

当使用sync方法时,不需要调用本方法,因为sync会自动调用,

codePush.restartApp

立即重启app.

当以下情况时,这个方式是很有用的。:

  1. app 当 调用 sync or LocalPackage.install 方法时,指定的 install mode 是 ON_NEXT_RESTART or ON_NEXT_RESUME时 . 这两种情况都是当app重启或resume时,更新内容才能被看到。
  2. You have an app-specific user event (e.g. the end user navigated back to the app’s home route) that allows you to apply the update in an unobtrusive way, and potentially gets the update in front of the end user sooner then waiting until the next restart or resume.

因为强制重启,能马上显示更新内容嘛!

codePush.sync

1
codePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress)): Promise<Number>;

允许检查更新,下载并安装,都在一个方法中。除非你需要自定义UI开表现。我们建议开发者在整合CodePush时,使用这个函数。
checkForUpdate方法只能检查更新,接下来怎么做要自己写。而该方法更新,下载,安装都能处理。该方法提供了两种模式

  • Silent mode(默认的模式),

它会自动下载可用更新,并在下次重启应用时应用它们(比如操作系统或用户杀死进程,或者重新启动设备) 。这样一来,整个更新是“沉默”给最终用户,因为用户看不到任何更新提示。

  • Active mode

当有可用的更新,提示用户是否允许下载,然后立即应用此更新。一旦选择了强制通知用户来手动更新,以后每次更新都会强制通知。个人感觉这种方式最合理

sync方法,有以下设置属性

  • deploymentKey (String): 部署key,用来区分不同的app,在Info.plist(Ios)和MianActivity.java(Android)修改,当然你也可以在JavaScript里填写,这样我们能动态的修改key
  • installMode (codePush.InstallMode): 非强制更新,默认codePush.InstallMode.ON_NEXT_RESTART
  • mandatoryInstallMode (codePush.InstallMode):强制更新,默认codePush.InstallMode.IMMEDIATE
  • minimumBackgroundDuration (Number):
  • updateDialog (UpdateDialogOptions) :可选的,更新的对话框,默认是null,包含以下属性

    • appendReleaseDescription (Boolean) - 是否显示更新description,默认false

    • descriptionPrefix (String) - 更新说明的前缀。 默认是” Description: “

    • mandatoryContinueButtonLabel (String) - 强制更新的按钮文字. 默认 to “Continue”.

    • mandatoryUpdateMessage (String) - 强制更新时,更新通知. Defaults to “An update is available that must be installed.”.

    • optionalIgnoreButtonLabel (String) - 非强制更新时,取消按钮文字. Defaults to “Ignore”.

    • optionalInstallButtonLabel (String) - 非强制更新时,确认文字. Defaults to “Install”.

    • optionalUpdateMessage (String) - 非强制更新时,更新通知. Defaults to “An update is available. Would you like to install it?”.

    • title (String) - 要显示的更新通知的标题. Defaults to “Update available”.

举例:

1
2
3
4
5
6
7
8
9
10
11
12
// Prompt the user when an update is available
// and then display a "downloading" modal
codePush.sync({ updateDialog: true }, (status) => {
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
// Show "downloading" modal
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
// Hide "downloading" modal
break;
}
});

再贴一个官方demo地址

实战应用

Service部署

安装CodePush CLI

npm install -g code-push-cli

可以输入code-push -v查看版本号

创建CodePush 账号

在发布一个更新前,必须有账号。

注册一个app

常用命令,只有下面几个:

  • access-key View and delete active user sessions
  • app View and manage your CodePush-enabled apps
  • collaborator View and manage collaborators for a given app
  • deployment View and manage the deployments for your apps
  • login Authenticate with the CodePush server in order to begin managing your apps
  • logout Log out of the current session
  • promote Promote the package from one deployment of your app to another
  • register Register a new account with the CodePush server
  • release Release a new version of your app to a specific deployment
  • release-react Release a new version of your React Native app to a specific deployment
  • rollback Performs a rollback on the latest package of a specific deployment

选项:

  • -v, –version 显示版本号 [boolean]

比如

  • 查看当前部署的app: code-push app ls
  • 添加一个app: code-push app add Zlot_react

部署已经注册的app

  • 部署: code-push deployment add app名字 部署名
  • 重命名部署名: code-push deployment rename app名字 旧部署名 新部署名
  • 删除部署名字: code-push deployment rm app名字 部署名字
  • 列表部署名字: code-push deployment ls app名字
  • 查看部署的key: code-push deployment ls app名 -k
  • 查看release 的历史版本
    code-push deployment history appName deploymentName

注意:默认的部署名是staging,所以staging的key值就是我们的deployment key

react-native-code-push插件安装

需要有git环境,并在app工程目录控制台输入

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

安装成功在package.json会看到

"react-native-code-push": "^1.8.0-beta",

android配置

  • 编辑android/setting.gradle文件,添加
1
2
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
  • 编辑android/app/builde.gradle文件,添加依赖
    1
    compile project(':react-native-code-push')

android react-native v0.18.0+

此种配置方法仅仅适用react-native 0.18版本及其以上。而且是android studio环境

  • 修改MainActivity.java文件,添加CodePush包
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
// 1. Import the plugin class
import com.microsoft.codepush.react.CodePush;


public class MainActivity extends ReactActivity {
// 2. Define a private field to hold the CodePush runtime instance
private CodePush _codePush;
...

// 3. Override the getJSBundleFile method in order to let
// the CodePush runtime determine where to get the JS
// bundle location from on each app start
@Override
protected String getJSBundleFile() {
return this._codePush.getBundleUrl("index.android.bundle");
}

@Override
protected List<ReactPackage> getPackages() {
// 4. Instantiate an instance of the CodePush runtime, using the right deployment key. If you don't
// already have it, you can run "code-push deployment ls <appName> -k" to retrieve your key.
this._codePush = new CodePush("0dsIDongIcoH0mqAmoR0CYb5FhBZNy1w4Bf-l", this, BuildConfig.DEBUG);

// 5. Add the CodePush package to the list of existing packages
return Arrays.<ReactPackage>asList(
new MainReactPackage(), this._codePush.getReactPackage());
}

...
}
  • 确定在android/app/build.gradle文件有android.defaultConfig.versionName属性
1
2
3
4
5
6
7
8
9
android {
...
defaultConfig {
...
versionName "1.0.0"
...
}
...
}

JavaScript代码

  • 导入CodePush的JavaScript组件

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

  • 在在每个app启动时,在生命周期的componentDidMount阶段调用sync,来开启一个后台的更新。

codePush.sync();

设计方案

  • isMandatory === true强制更新,采用弹出model框提醒的方式
  • isMandatory === false非强制更新,采用静默安装的方式

如:

1
2
3
4
5
6
7
componentDidMount() {
codePush.sync({
updateDialog: false,
installMode: codePush.InstallMode.ON_NEXT_RESUME,
deploymentKey: CODE_PUSH_PRODUCTION_KEY,
});
},

app如何更新(JavaScript+image)

注意:从react-native v0.19.0开始支持android更新assets资源,并且需要CodePush v1.7.0。所以你需要升级你的react-native跟CodePush。

确保在工程目录,新建release文件夹,如下

package命令

1
2
3
4
5
6
react-native bundle 
--platform ios 选择平台
--entry-file index.ios.js 选择启动项
--bundle-output ./release/main.jsbundle 选择打包js输出文件
--assets-dest ./release \ 选择资源输出目录
--dev false 是否调试
  • Android版本

    1
    react-native bundle --platform android --entry-file index.android.js --bundle-output ./release/main.jsbundle  --assets-dest ./release --dev false
  • ios版本

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

release命令

1
2
3
4
code-push release <appName> <updateContents> <targetBinaryVersion>
[--deploymentName <deploymentName>]
[--description <description>]
[--mandatory]

mandatory :是否强制更新。

注意:targetBinaryVersion指的是当前的app版本,比如客户端是1.0.0,如果我们要更新客户端,targetBinaryVersion也要是1.0.0,表示是1.0.0版本的更新,跟native的更新不太一样。这是我们第一次经常犯的错误大家不要搞混了。

  • Android版本
    1
    code-push release Zlot ./release 1.0.0 --description "这是一个1.0.0版本的更新" --mandatory true

常见问题

1 更新不成功,报but it is being ignored due to having been previously rolled back.

问题原因:客户端版本1.0.0,然后release的版本是1.0.1,所以检测的时候发现不了1.0.0版本的更新。

因此我们也建议,js的更新采用静默更新,native的更新,做出提示。

注意:targetBinaryVersion指的是当前的app版本,比如客户端是1.0.0,如果我们要更新客户端,targetBinaryVersion也要是1.0.0,表示是1.0.0版本的更新,跟native的更新不太一样。这是我们第一次经常犯的错误大家不要搞混了。

2 [CodePush] An update is available, but it is being ignored due to having been previously rolled back.

解决方案:一般这种情况是在,我们自定义model,展示更新过程,并监听了安装完成,比如这个demo,我这这里忘了在componentDidMount()方法里添加CodePush.notifyApplicationReady();,就一直报这个错误。添加上就ok了

参考文章

使用CodePush热更新ReactNative JS代码

react-native-code-push官方文档