> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# 发布热更新

现在你的应用已经具备了检测更新的功能，下面我们来尝试发布并更新它。流程可参考下图：

```mermaid
flowchart TD
    codebase["🖥️&nbsp;&nbsp;项目代码库"]
    subgraph 发布原生基准版本
    tagNativeVersion["🏷️&nbsp;&nbsp;(在 git 上)标记原生版本号"]
    newNativeVersion["🗂️&nbsp;&nbsp;新的原生基准版本"]
    nativePackage["📦&nbsp;&nbsp;原生完整包(apk、aab或ipa文件)"]
    tagNativeVersion--"🔨&nbsp;&nbsp;编译"-->nativePackage
    nativePackage--"⬆️&nbsp;&nbsp;使用<br/>pushy uploadApk/uploadAab/uploadIpa<br/>命令上传"-->newNativeVersion
    end
    subgraph 发布热更新版本
    tagBundleVersion["🏷️&nbsp;&nbsp;(在 git 上)标记热更新版本号"]
    bundlePackage["🎁&nbsp;&nbsp;js代码与资源包(ppk文件)"]
    tagBundleVersion--"🔨&nbsp;&nbsp;使用<br/>pushy bundle<br/>命令生成并上传"-->bundlePackage
    someNativeVersions["🗂️&nbsp;&nbsp;一个或多个原生基准版本"]
    bundlePackage--"🖇️&nbsp;&nbsp;绑定"-->someNativeVersions
    end
    user["👨‍👩‍👧‍👦&nbsp;&nbsp;安装有对应原生基准版本的用户"]
    codebase--"✏️&nbsp;&nbsp;改动js代码，<br/>或添加、更新js组件，<br/>或添加、更新js代码中引用的图片等资源"-->发布热更新版本
    codebase--"🖊️&nbsp;&nbsp;改动原生代码、设置，<br/>或添加、更新原生组件，<br/>或添加、更新原生代码中引用的图片等资源"-->发布原生基准版本
    发布热更新版本--"📲&nbsp;&nbsp;推送增量热更新(diff文件)"-->user
```

流程总结如下：

1. 我们需要先打包一个原生 release 版本，在打包前请确保已集成了`react-native-update`并在调试过程中运行正常，安卓端[关闭了`crunchPngs`设置](/docs/getting-started.md#%E7%A6%81%E7%94%A8-android-%E7%9A%84-crunch-%E4%BC%98%E5%8C%96)，打包说明可参考[iOS 打包](https://reactnative.cn/docs/publishing-to-app-store)和[android 打包](https://reactnative.cn/docs/signed-apk-android)。打包完成后请使用`pushy uploadIpa`、`pushy uploadApk`或`pushy uploadAab`命令来把这个安装包上传到 pushy 服务器端，以作为之后热更差量对比的基准。同时请保留好这个安装包，上架和分发给用户所使用的安装包`需要和服务器端完全一致`。建议使用 git tag 功能来标记原生版本号（例如`v1.0.0`）。
2. 然后在基准版本之上迭代业务逻辑（增删 js 代码，增删图片等静态资源），使用`pushy bundle`命令来生成和发布热更新版本，而不需要重新打包。建议使用 git tag 功能来标记热更版本号（例如`v1.0.1`）。
3. 如果迭代过程中有原生方面的修改，则需要发布并上传新的原生基准版本（重复步骤 1，但需要设置不同的原生版本号）。可以只保留一个原生基准版本，也可以多版本同时维护。

## 发布原生基准版本

### iOS

首先参考[文档-在设备上运行](https://reactnative.cn/docs/running-on-device)，确定你正在使用离线包。然后点击菜单。

按照正常的发布流程打包`.ipa`文件：

1. Xcode 中运行设备选真机或 Generic iOS Device
2. 菜单中选择 Product - Archive
3. Archive 完成后选择`Export`生成.ipa 文件
4. 然后运行如下命令上传到 pushy 服务器以供后续版本比对之用

```bash
$ pushy uploadIpa <ipa后缀文件>
```

此 ipa 的`CFBundleShortVersionString`字段(位于`ios/项目名/Info.plist`中)会被记录为原生版本号`packageVersion`。

随后你可以选择往 AppStore 上传这个版本（可以重新 export 并调整相关选项，但请不要重新 archive），也可以先通过[Test flight](https://developer.apple.com/cn/testflight/)或[蒲公英](https://www.pgyer.com/doc/view/build_ipa)等渠道进行真机安装测试。请注意：暂不支持通过 Xcode 直接进行热更新测试。

如果后续需要再次 archive 打包（例如修改原生代码或配置。如果只是修改 js 代码则不需要重新打包。），请先**更改版本号**，并在打包完成后再次`uploadIpa`到服务器端记录，否则后续生成的相同版本的原生包会由于[编译时间戳不一致而`无法获取热更新`](/docs/faq.md#热更新报错：热更新已暂停，原因：buildtime-mismatch。)。

### Android

首先参考[文档-打包 APK](https://reactnative.cn/docs/signed-apk-android)设置签名，然后在 android 文件夹下运行`./gradlew assembleRelease`或`./gradlew aR`，你就可以在`android/app/build/outputs/apk/release/app-release.apk`中找到你的应用包。

如果你需要同时向 Google Play 等渠道分发 `.aab`，并向其他渠道分发 `.apk`，建议在项目根目录的`package.json`中配置一个 npm script，在同一次 Gradle 调用中同时执行`assembleRelease`和`bundleRelease`。这样 APK 与 AAB 会复用同一份 release 构建产物，内置 bundle 与编译时间戳保持一致，后续按渠道分发对应格式即可。如果已有`scripts`字段，只需要追加其中一项：

```json
{
  "scripts": {
    "package:android:release": "cd android && ./gradlew clean assembleRelease bundleRelease"
  }
}
```

```bash
$ npm run package:android:release
```

产物路径如下：

```text
android/app/build/outputs/apk/release/app-release.apk
android/app/build/outputs/bundle/release/app-release.aab
```

如果项目使用了 flavor，请按实际 variant 调整 npm script 中的任务名，例如`assembleProdRelease`和`bundleProdRelease`。不要先单独执行一次`assembleRelease`，再在另一次 Gradle 命令中执行`bundleRelease`，否则两个包可能带有不同的编译时间戳。

然后根据实际分发格式运行对应命令

```bash
$ pushy uploadApk android/app/build/outputs/apk/release/app-release.apk
# 如果你实际分发的是 aab 格式的包，则使用：
$ pushy uploadAab android/app/build/outputs/bundle/release/app-release.aab
```

即可上传对应的 Android 原生包以供后续版本比对之用。此包的`versionName`字段(位于`android/app/build.gradle`中)会被记录为原生版本号`packageVersion`。

随后你可以选择往应用市场发布这个版本，也可以先往设备上直接安装 apk 文件以进行测试。若同一个版本同时产出了 APK 与 AAB，请根据渠道要求分发对应格式：Google Play 通常使用 AAB，其他直装或第三方渠道通常使用 APK。

如果后续需要再次打包（例如修改原生代码或配置。如果只是修改 js 代码则不需要重新打包。），请先**更改版本号**，并再次上传对应原生包到服务器端记录，否则后续生成的相同版本的原生包会由于[编译时间戳不一致而`无法获取热更新`](/docs/faq.md#热更新报错：热更新已暂停，原因：buildtime-mismatch。)。

### Harmony

首先下载鸿蒙开发IDE DevEco-Studio，然后通过Build => Build Hap(s)/App(s) => Build App(s)，你就可以在`harmony/build/outputs/default/harmony-default-unsigned.app`中找到你的应用包。

然后运行如下命令

```bash
$ pushy uploadApp harmony/build/outputs/default/harmony-default-unsigned.app
```

即可上传 app 以供后续版本比对之用。此 app 的`versionName`字段(位于`harmony/AppScope/app.json5`中)会被记录为原生版本号`packageVersion`。

随后你可以选择往华为应用市场发布这个版本，也可以先往设备上通过命令`hdc shell`命令安装这个 app 文件以进行测试。

如果后续需要再次打包（例如修改原生代码或配置。如果只是修改 js 代码则不需要重新打包。），请先**更改版本号**，并再次`uploadApp`到服务器端记录，否则后续生成的相同版本的原生包会由于[编译时间戳不一致而`无法获取热更新`](/docs/faq.md#热更新报错：热更新已暂停，原因：buildtime-mismatch。)。

## 发布热更新版本

你可以尝试修改一行代码(譬如将版本一修改为版本二)，然后使用`pushy bundle --platform <ios|android|harmony>`命令来生成新的热更新版本。

:::info
如果你使用了较新版本的`expo`或其他没有`index.js`的框架，执行`bundle`命令时会报错。此时请手动创建一个`index.js`文件，在其中引用框架自身的入口文件即可。具体入口文件的路径如何，请参考框架的说明文档或者`package.json`中的`main`字段。例如针对`expo`的`index.js`可能是如下这样写：

```js
import "expo-router/entry";
```

:::

```bash
$ pushy bundle --platform android
Bundling with React Native version:  0.22.2
<各种进度输出>
Bundled saved to: build/output/android.1459850548545.ppk
Would you like to publish it?(Y/N)
```

如果想要立即上传，此时输入 Y。当然，你也可以在将来使用`pushy publish --platform android build/output/android.1459850548545.ppk`来上传刚才打包好的热更新包。

```
  Uploading [========================================================] 100% 0.0s
Enter version name: <输入热更新版本名字，如1.0.0-rc>
Enter description: <输入热更新版本描述>
Enter meta info: {"ok":1}
Ok.
Would you like to bind packages to this version?(Y/N)
```

此时版本已经提交到 pushy 服务，但用户暂时看不到此更新，你需要先将特定的原生包版本绑定到此热更新版本上。

此时输入 Y 立即绑定，你也可以在将来使用`pushy update --platform <ios|android|harmony>`来对已上传的热更包和原生包进行绑定。除此以外，你还可以在网页端操作，简单的将对应的原生包版本拖到需要的热更新版本下即可。

```
┌────────────┬──────────────────────────────────────┐
│ Package Id │               Version                │
├────────────┼──────────────────────────────────────┤
│   46272    │ 2.0(normal)                          │
├────────────┼──────────────────────────────────────┤
│   45577    │ 1.0(normal)                          │
└────────────┴──────────────────────────────────────┘
共 2 个包
输入原生包 id: 46272
```

版本绑定完毕后，服务器会在几秒内生成差量补丁，客户端就可以获取到更新了。

后续要继续发布新的热更新，只需反复执行`pushy bundle`命令即可，不需要重新打包。

恭喜你，至此为止，你已经完成了植入代码热更新的全部工作。

## 灰度发布

灰度发布（又称金丝雀发布、渐进式发布）是一种降低热更新发布风险的策略，通过逐步扩大更新范围来验证新版本的稳定性。

### 什么是灰度发布

灰度发布是指在正式全量发布热更新之前，先将更新推送给一小部分用户（如 5%、10%），观察这部分用户的使用情况后，再逐步扩大更新比例，直到最终推送给所有用户。

### 灰度发布的作用

- **降低风险**：如果新版本存在 bug，只会影响小部分用户，可以及时发现并回滚
- **验证稳定性**：通过小范围用户的真实使用反馈，验证新版本在各种设备和网络环境下的表现
- **平滑过渡**：避免突然的全量更新对服务器造成的压力峰值
- **快速止损**：一旦发现问题，可以立即停止灰度，将影响范围控制在最小

### 工作原理

当你设置了灰度比例（如 10%）后，检查更新时会根据每个用户的设备 UUID 进行哈希计算，确定该用户是否在灰度范围内：

- 灰度范围内的用户会收到新版本的更新推送
- 灰度范围外的用户会收到上一个全量版本（如果有），或显示为已是最新
- 同一用户的灰度状态是稳定的，不会因为多次检查更新而变化

### 使用方法

#### 通过网页端操作

1. 登录 [Pushy 管理后台](https://update.reactnative.cn)
2. 选择对应的应用和原生包版本
3. 点击"发布"按钮
4. 选择灰度发布比例

#### 通过命令行操作

请查看[命令行工具文档中的 rollout 参数](/docs/cli.md#pushy-update)

### 注意事项

:::warning
**重要提示**：灰度版本与全量版本是独立的绑定关系。
:::

- **同时只能有一个灰度版本**：每个原生包版本只能绑定一个灰度热更版本（比例小于100%）和一个全量热更版本
- **优先级**：如果同时存在灰度版本和全量版本，灰度范围内命中的用户会收到灰度版本，没有命中的用户会收到全量版本
- **提升为全量**：将灰度比例设置为 100% 后，该版本会自动提升为全量版本，替换原有的全量版本
- **客户端版本要求**：`react-native-update` >= 10.32.0 才能支持同时发布灰度和全量版本。

## 强制启动（救砖）

强制启动（forceBoot）是挂在「热更版本 × 原生包」绑定上的一个标记：客户端的**原生冷启动检测**在下载到该版本后会直接激活（下次启动生效），无视应用内配置的更新策略（`updateStrategy` / `checkStrategy`）。

### 为什么需要它

如果某次热更把应用的 JS 打死（启动即崩溃、白屏），设备上的 JS 代码将永远没有机会再运行检查更新的逻辑——常规热更通道从此失效，这就是「变砖」。

自 `react-native-update` 10.52.1 起，客户端会在每次冷启动几秒后，由**原生代码**独立发起一次后台检查（完全不依赖 JS）。强制启动正是给这条通道下达的指令：即使 JS 已经无法运行，原生检测也能把修复版本拉下来并激活，让设备在下一次启动时恢复正常。

### 使用方法

1. 登录 [Pushy 管理后台](https://update.reactnative.cn)
2. 找到修复版本，发布时选择「全量+强制启动(救砖)」；或对已绑定的版本，在包名菜单中选择「强制启动(救砖)」
3. 确认弹窗中的语义说明后发布

### 注意事项

:::warning
强制启动会越过用户侧的更新策略，请只在救砖等紧急场景使用，并确保修复版本已经过验证。
:::

- **客户端版本要求**：原生包中的 `react-native-update` >= 10.52.1（低于此版本没有完整的原生冷启动检测能力，管理后台也不会显示该选项）
- **仅作用于原生检测**：JS 侧的交互式检查更新流程不受此标记影响
- **安全护栏依然生效**：设备本地的回滚保护（rolledBack 记录）优先于强制启动；首次启动崩溃保护（first\_time）也依然生效，激活后如果再次崩溃仍会自动回滚
- **标记随绑定存在**：重新发布（重新绑定）会替换绑定并清除强制启动标记；也可在包名菜单中选择「取消强制启动」
