首页教程React Native

集成热更新(CodePush)

集成热更新(CodePush)

使用 React Native 开发 App 不仅可以使用前端技术栈,而且可以通过热更新快速修复 BUG,让用户能及时体验产品新的特性。

本文介绍如何通过 CI / CD 发布热更新,并把符号表上传到 Sentryopen in new window,方便监控补丁包可能产生的崩溃问题。

注册热更新服务

我们使用微软提供的热更新服务 react-native-code-pushopen in new window。

这个服务有个配套的命令行应用 code-push-cli,但我们需要配合 Sentry 使用,这里采用的是微软的另外一个命令行应用 appcenter-cliopen in new window。

安装 appcenter-cli

npm i -g appcenter-cli

安装成功后,查看有哪些命令可用

appcenter -h

提示:可以使用 -h 参数查看任意子命令的帮助信息,如:

appcenter login -h appcenter apps create -h

现在,让我们先注册一个账号,在弹出的网页中,建议使用 GitHub 账号登录

appcenter login

列出已注册 app, 如果你之前有使用 code-push-cli 的话,应该能看到曾经注册过的 app

appcenter apps list

如果没有,那么让我们来注册,注意需要区分平台

appcenter apps create -p React-Native -o Android -d myapp-android appcenter apps create -p React-Native -o iOS -d myapp-ios

hotfix-2021-10-19-16-18-34

现在输入下面命令,应该能看到我们刚刚注册的 app 了

appcenter apps list

创建生产环境和测试环境的 deployment key

appcenter codepush deployment add -a listenzz/myapp-android Production appcenter codepush deployment add -a listenzz/myapp-android Staging

之所以使用 Production 和 Staging,是因为使用过 code-push-cli 的人都知道,code-push-cli 默认创建的就是这俩名称

列出 deployment key

appcenter codepush deployment list -k -a listenzz/myapp-android

hotfix-2021-10-19-16-18-54

列出当前 app 最近发布过的补丁包,如果之前没使用 code-push 发布过补丁的话,那么现在应该是空的

appcenter codepush deployment list -a listenzz/myapp-android

hotfix-2021-10-19-16-19-12

配置 RN 工程

安装 react-native-code-pushopen in new window 作为项目的依赖

yarn add react-native-code-push

查看 package.json,确认安装了对的版本

Supported React Native platformsopen in new window

新建一个 react hook

// useCodePush.ts import { useCallback, useEffect } from 'react' import { AppState, AppStateStatus } from 'react-native' import CodePush from 'react-native-code-push' import { useVisibleEffect } from 'hybrid-navigation' export function useCodePush(sceneId: string) { useEffect(() => { async function syncCode(status: AppStateStatus) { try { if (status === 'active') { await CodePush.sync({ installMode: CodePush.InstallMode.IMMEDIATE, }) } } catch (e) { // ignore } } syncCode(AppState.currentState) AppState.addEventListener('change', syncCode) return () => { AppState.removeEventListener('change', syncCode) } }, []) const visibleCallback = useCallback(() => { CodePush.allowRestart() return () => { CodePush.disallowRestart() } }, []) useVisibleEffect(sceneId, visibleCallback) }

这里使用的是静默更新,如果需要提示用户或者显示进度条,请参考官方文档或其它资料。

修改 App.tsx 文件,激活这个 hook

import { useCodePush } from './useCodePush' function App({ sceneId }: InjectedProps) { useCodePush(sceneId) }

配置 iOS 工程

在 ios 目录下执行

bundle exec pod install

pod 安装好后,修改 AppDelegate.m 文件

#import <CodePush/CodePush.h> - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURL *jsCodeLocation; #if DEBUG jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #else jsCodeLocation = [CodePush bundleURL]; #endif }

或者

#import <CodePush/CodePush.h> - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { #if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; #else return [CodePush bundleURL]; #endif }

列出当前 app 的 deployment keys

appcenter codepush deployment list -k -a listenzz/myapp-ios

添加字段名为 CODEPUSH_KEY 的 User-Defined 配置

hotfix-2021-10-19-16-19-36

hotfix-2021-10-19-16-20-03

只需要配置 release 的 configuration 即可

在 Info.plist 文件中添加 CodePushDeploymentKey 和它的值,也就是 $(CODEPUSH_KEY)

hotfix-2021-10-19-16-20-22

配置 Andriod 工程

修改 app/build.gradle 文件

apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" productFlavors { qa { buildConfigField "String", "CODEPUSH_KEY", '"L-XEelCs4LTKQwOK0huftB3JLbp6rkAfA5tcN"' } production { buildConfigField "String", "CODEPUSH_KEY", '"MWyeVC38WoHktu9u4Mg2TQRH8rMOHJaIp9Y5V"' } }

其中, CODEPUSH_KEY 可以通过 appcenter codepush deployment list -k -a <appOwner/appName> 查看。

修改 MainApplication.java 文件

// 1. 导包 import com.microsoft.codepush.react.CodePush; private final ReactNativeHost mReactNativeHost = new HybridReactNativeHost(this) { // 2. 重载这个方法 @Nullable @Override protected String getJSBundleFile() { return CodePush.getJSBundleFile(); } };

测试热更新是否能正常工作

所有工作准备完毕,让我们来测试下吧

以 ios 平台为例

先对 UI 做些修改,然后通过以下命令发布补丁包

appcenter codepush release-react -a listenzz/myapp-ios -t 1.0.11 -o ./build -d Staging

发布成功后,通过以下命令查看发布结果

appcenter codepush deployment list -a listenzz/myapp-ios

hotfix-2021-10-19-16-20-44

可以看到,我们刚刚发布了一个补丁,但是还没有人安装它

让我们的 App 退到后台,然后进入前台

再次输入以下命令

appcenter codepush deployment list -a listenzz/myapp-ios

可以看到有一个用户正在等待更新

hotfix-2021-10-19-16-21-07

让我们的 App 再退到后台,然后再进入前台

hotfix-2021-10-19-16-21-25

恭喜你,所有用户都更新到了最新版本

如果觉得两进两出会导致更新不及时,请参考官方文档或其它资料,调整相应 js 代码即可。

一个不好的消息是,我们的补丁包也是会有 BUG,这要求我们需要把补丁包的符号表(source map)也上传到 Sentry。

export SENTRY_PROPERTIES=./ios/sentry.properties sentry-cli react-native appcenter --release-name com.shundaojia.myapp@1.0.11 --deployment Staging listenzz/myapp-ios ios ./build/codePush

将热更新功能与 CI / CD 集成

手动发补丁包,不仅麻烦,还容易出错,我们通过 CI 脚本来解决这个问题

在 ci/config.js 文件中,添加新的常量

// ci/config.js // package.json 所在目录 const REACT_ROOT = path.resolve(__dirname, '../') // appcenter 上注册的 app 名字 const APP_NAME_CODEPUSH = process.env.APP_NAME_CODEPUSH || `listenzz/${APP_NAME.toLowerCase()}-${PLATFORM}` // 热更新目标 example.com const APP_TARGET_CODEPUSH = process.env.APP_TARGET_CODEPUSH || VERSION_NAME // 是否只需要打补丁包 const PATCH_ONLY = !!process.env.PATCH_ONLY // 是否强制更新 const MANDATORY = !!process.env.MANDATORY

添加 ci/pack/patch/android.js 文件,关键代码如下

// 发布补丁 // appcenter-cli 根据 build.gradle 文件中是否含有字符串 “enableHermes: true” 来判断是否开启 hermes sh( `appcenter codepush release-react \ -a ${APP_NAME_CODEPUSH} \ -t "${APP_TARGET_CODEPUSH}" \ -o ${ARTIFACTS_DIR} \ -d ${deployment} \ -m ${MANDATORY} \ --extra-bundler-option="--sourcemap-sources-root=${REACT_ROOT}"`, )

添加 ci/pack/patch/ios.js 文件,关键代码如下

// 发布补丁 sh( `appcenter codepush release-react \ -a ${APP_NAME_CODEPUSH} \ -t "${APP_TARGET_CODEPUSH}" \ -o ${ARTIFACTS_DIR} \ -d ${deployment} \ -m ${MANDATORY} \ --extra-bundler-option="--sourcemap-sources-root=${REACT_ROOT}"`, )

添加 ci/sentry/patch.js 文件,关键代码如下

// 上传 sourcemap 到 sentry sh( `sentry-cli react-native appcenter \ --log-level INFO \ --release-name ${release} \ --dist ${VERSION_CODE} \ --deployment ${deployment} \ ${APP_NAME_CODEPUSH}${PLATFORM}${ARTIFACTS_DIR}/CodePush`, { env: { ...process.env, SENTRY_PROPERTIES: SENTRY_PROPERTIES_PATH }, }, )

现在,就差一个按钮了

让我们到 Gitlab CI / CD -> Schedules 上去配置一个

hotfix-2021-10-19-16-22-17

记得不要勾选 Active,因为我们不希望每天都发一个补丁包。

之后只要轻轻一点 Schedules 里的 Play 按钮,就会自动发布两个平台的补丁包,并把符号表上传到 Sentry。