内容简介:作者 | 钱凯杏仁移动开发工程师,前嵌入式工程师,关注大前端技术新潮流。
作者 | 钱凯
杏仁移动开发工程师,前嵌入式工程师,关注大前端技术新潮流。
本文使用的环境:
-
React@16.3.1
-
React Native@0.55.4
-
react-native-code-push@5.3.4
-
Android SDK@23
-
Android Build Tool@23.0.3
-
Gradle@2.14.1
-
Android Gradle Plugin@2.2.3
Why CodePush?
CodePush 是微软提供的一个热更新前后台方案,它对 React Native 项目有很好的支持。
目前针对 React Native 的 hot update 方案有许多,但是 CodePush 是最成熟稳定的方案,它最大的特点是提供了完整的后台工具。它主要的优点是:
-
微软出品,大厂保证
-
良好的多环境支持(Testing,Staging, Production)
-
灰度发布、自动回滚等等特性
-
良好的数据统计支持:下载、安装、出错一目了然
-
强大的 CLI 工具,一个终端搞定全部流程
由于 React Native 执行的是脚本 js 文件,对热更新有天然的亲和,有余力的团队可以尝试实现自己的框架,一个简单的实现思路是:
-
修改加载
jsBundle
的代码,转而从指定的本地存储位置去加载。如果没有,下载 bundle, 并且本次打开使用 app 包中的 bundle。 -
如果找到
jsBundle
文件,调用 api 比较版本号,如果不一致,则从指定服务器下载最新的 bundle 进行替换。 -
通过反射调用私有方法,在下载完成的回调中更新运行时资源,从而能立即看到更新的效果。
-
使用类似
google-diff-match-patch
的 diff 工具,生成差异化补丁,不必下载完整 bundle,从而大大减小补丁包体积。
网上有很多资料和源码,这里就不细述了。
后台配置
为了使用 Code Push 发布热更新,我们需要向微软服务注册我们的应用。这部分工作微软提供了强大的命令行工具: CodePush CLI
。
安装 cli 工具
npm 全局安装:
npm install -g code-push-cli
关联账号
使用命令
code-push register
注册一个账号,可以直接使用 GitHub 账号授权,完成后将 token 复制回命令行中。
使用 whoami
查看登录状态:
code-push whoami
注册应用
登录成功后,我们注册一个app:
code-push app add 你的App名称 android react-native
注意 一定要为 Android 和 iOS 分别注册 ,两者的更新包内容会有差异。
查询状态
每个 App 有不同的运行时环境,比如 Production
, Staging
等,我们也可以配置自己的环境。查看 App 的不同环境和部署状况:
code-push deployment ls 注册的app名称
目前我们还没有发布任何更新,所以表中的状态是空的。
到这里就完成了后端的基本配置。
App端配置
版本兼容
安装 Code Push 环境前首先要 check 版本的兼容性问题,不同的RN版本需要使用不同的 Code Push,原则上我们建议将 RN 和 CodePush 都升级到最新版本。
下表是官方文档中的兼容性说明:
React Native version(s) | Supporting CodePush version(s) |
---|---|
<0.14 | Unsupported |
v0.14 | v1.3 (introduced Android support) |
v0.15-v0.18 | v1.4-v1.6 (introduced iOS asset support) |
v0.19-v0.28 | v1.7-v1.17 (introduced Android asset support) |
v0.29-v0.30 | v1.13-v1.17 (RN refactored native hosting code) |
v0.31-v0.33 | v1.14.6-v1.17 (RN refactored native hosting code) |
v0.34-v0.35 | v1.15-v1.17 (RN refactored native hosting code) |
v0.36-v0.39 | v1.16-v1.17 (RN refactored resume handler) |
v0.40-v0.42 | v1.17 (RN refactored iOS header files) |
v0.43-v0.44 | v2.0+ (RN refactored uimanager dependencies) |
v0.45 | v3.0+ (RN refactored instance manager code) |
v0.46 | v4.0+ (RN refactored js bundle loader code) |
v0.46-v0.53 | v5.1+ (RN removed unused registration of JS modules) |
v0.54-v0.55 | v5.3+ (Android Gradle Plugin 3.x integration) |
安装包
使用命令:
npm info react-native-code-push
来查看包相关信息。
我们建议始终将RN、React以及一些相关库升级到最新版本。在根目录下使用命令:
npm install --save react-native-code-push
来安装最新版本的 CodePush。
也可以参照上面的兼容性表格,安装指定版本:
npm install --save react-native-code-push@5.1.4
工程配置(Android)
如果工程创建的时候比较早,可能是使用命令 create-react-native-app
来创建的,则需要在根目录执行:
npm run eject
来改变工程结构,防止后面的兼容性问题。
配置安卓工程,官方提供了两种途径:
-
使用命令行工具
rnpm
(现在已经被整合到React Native CLI工具中了)。执行
react-native link react-native-code-push
-
手动配置
如果你是新手,或者对 gradle、安卓工程结构不了解,我们强烈建议执行一次手动配置,帮助理解到底发生了什么。
手动配置
step 1
在 android/settings.gradle
文件中添加:
include ':app', ':react-native-code-push' project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
这个文件定义了哪些 module 应该被加入到编译过程,对于单个 module 的项目可以不用需要这个文件,但是对于 multiModule 的项目我们就需要这个文件,否则 gradle 不知道要加载哪些项目。这个文件的代码在初始化阶段就会被执行。
我们添加的内容告诉 gradle:去 node_modules
目录下的 react-native-code-push
加载 CodePush 子项目。
step 2
在 android/app/build.gradle
中的 dependencies
方法中添加依赖:
... dependencies { ... compile project(':react-native-code-push') }
这样就能在主工程中引用到 CodePush 模块了。
step 3
继续在 android/app/build.gradle
中,添加在编译打包阶段 CodePush 需要执行的 task 引用:
... apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" ...
这段代码其实就是调用了 project 对象的 apply 方法,传入了一个以 from 为 key 的 map。完整写出来就是这样的:
project.apply([from: '../../node_modules/react-native-code-push/android/codepush.gradle'])
apply from
和 apply plugin
的区别在于,前者是从指定 url 去加载脚本文件,后者则用是从仓库拉取 plugin id 对应的二进制执行包。
step 4
CodePush 发布有各种环境(deployment),默认有 Staging 和 Production,我们需要在 buildType
中配置对应的环境,并且设置 PushKey
,从而让 App 端的 CodePush RunTime 根据不同的健值来下载正确的更新包。
查询各个环境 Key 的方法是使用上文安装的 CLI 工具:
code-push deployment ls App名称 -k
上表中的 Deployment Key
就是对应环境的 Key 值了。
在 android/app/build.gradle
中,配置 buildTypes:
buildTypes { // 对应Production环境 release { ... buildConfigField "String", "CODEPUSH_KEY", '"从上述结果中复制的production值"' ... } // 对应Staging环境 releaseStaging { // 从 release 拷贝配置,只修改了 pushKey initWith release buildConfigField "String", "CODEPUSH_KEY", '"从上述结果中复制的stagingkey值"' } debug { buildConfigField "String", "CODEPUSH_KEY", '""' } }
注意这里不同 buildType 的命名,Staging 环境对应的 buildType 就叫 releaseStaging
,要符合这样的命名规范。
Debug 环境虽然用不到 CodePush, 但是也要配置空的 Key 值,否则会报错。
step 5
处理完引用关系后,我们修改 MainApplication.java
,在 App 执行时启动 CodePush 服务:
// 声明包 import com.microsoft.codepush.react.CodePush; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { ... // 重写 getJSBundleFile() 方法,让 CodePush 去获取正确的 jsBundle @Override protected String getJSBundleFile() { return CodePush.getJSBundleFile(); } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), // 创建一个CodePush运行时实例 new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG) ... ); } }; }
js端引入 Code Push
配置完项目工程后,我们将 CodePush 引入到 js 端。
首先将 App 的根组件包裹在 CodePush 中:
import codePush from "react-native-code-push"; AppRegistry.registerComponent('BDCRM', () => codePush(App));
CodePush 会在 App 启动后自动去 check 和更新最新的版本,我们可以添加一些配置,让它在进入后台的时候也执行检查:
let codePushOptions = { checkFrequency: codePush.CheckFrequency.MANUAL }; AppRegistry.registerComponent('BDCRM', () => codePush(codePushOptions)(App));
CodePush js端的 api 不多,我们可以用这些 api 控制更新的一系列流程,常用的有:
// 检测是否有更新包可用 codePush.checkForUpdate(deploymentKey: String = null, handleBinaryVersionMismatchCallback: (update: RemotePackage) => void): Promise<RemotePackage>; // 获取本地最新更新包的属性 codePush.getCurrentPackage(): Promise<LocalPackage>; // 重启app(即使不用在 Hot Updating,也挺有用的) codePush.restartApp(onlyIfUpdateIsPending: Boolean = false): void; // 手动进一次更新 codePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress), handleBinaryVersionMismatchCallback: function(update: RemotePackage)): Promise<Number>;
更多详细信息见文档。
使用 CodePush CLI 发布更新
完成前后端的配置,打包发布应用后,后续的改动我们就能通过 CLI 工具来发布啦!
升级前首先要 check:
-
应用的版本号要有更新(app/build.gradle: defaultConfig/versionName)
-
js bundle 要有改动,Code Push 会 diff 前后版本,如果代码一致会认为是无效的更新包
打开终端,进入到工程目录,完整发布命令是:
code-push release-react <appName> <platform> [--bundleName <bundleName>] [--deploymentName <deploymentName>] [--description <description>] [--development <development>] [--disabled <disabled>] [--entryFile <entryFile>] [--gradleFile <gradleFile>] [--mandatory] [--noDuplicateReleaseError] [--outputDir <outputDir>] [--plistFile <plistFile>] [--plistFilePrefix <plistFilePrefix>] [--sourcemapOutput <sourcemapOutput>] [--targetBinaryVersion <targetBinaryVersion>] [--rollout <rolloutPercentage>] [--privateKeyPath <pathToPrivateKey>] [--config <config>]
命令参数很多,但用途都一目了然,嫌每次打麻烦的话,做成脚本也可以。
一般来说,我们发布应用首先会在测试环境进行稳定性测试,通过后再发布到生产环境中:
-
打包发布 Staging 环境
code-push release-react 应用名 --platform android --deploymentName Staging --description "修复一些bug"
这样,我们 Staging 环境就可以收到更新推送啦,具体加载新 bundle 的实际,和我们在应用中配置的策略有关,上文已经介绍过了。
-
测试 ok 后,提升(Promoting)到 Production 环境,并且进行灰度20%发布
code-push promote 应用名 Staging Production --rollout 20%
-
在生产环境验证 ok,使用 patch 将灰度修改为100%,进行全网发布:
code-push patch 应用名 Production -rollout 100%
以上就是按照 测试 - 灰度 - 全部发布 步骤的一个典型 CodePush 发布工作流。
总体来说,CodePush 能满足我们灰度发布 React Native 应用的大部分需求了,由微软提供的服务器端支持可以节省很多工作,是一个成熟可靠的方案。如果要说缺点,可能有几个需要考虑一下:
-
服务器速度,国内网络状况可能会影响下发的成功率和效率。
-
污染代码,在 js 端必须将根节点包裹到 CodePush 模块中去,污染了代码。
-
冗余,如果只是想要简单的下发小体积的 js bundle,CodePush 显得太“重”,过于冗余了,这时候用轻量化的方案更好。
总之,我们根据自己项目的需要去进行选型就好了!
更多细节,可以参考文档
全文完
以下文章您可能也会感兴趣:
我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。
杏仁技术站
长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Rails 与 Slack 整合指南 (1)
- Rails 与 Slack 整合指南 (4)
- Rails 与 Slack 整合指南 (3)
- Rails 与 Slack 整合指南 (2)
- SpringBoot整合MybatisPlus的简单教程(简单整合)
- springmvc教程--整合mybatis开发(spring+springMVC+mybatis整合开发)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。