{
  "schemaVersion": 1,
  "product": "Pushy",
  "baseUrl": "https://pushy.react-native.cn",
  "resourceScheme": "pushy-docs",
  "sourceCommit": "c67c96d0466de148b4cf6b9f1af10ccb114ac2b2",
  "documents": [
    {
      "slug": "api-token",
      "title": "API Key",
      "path": "pages/docs/api-token.mdx",
      "url": "https://pushy.react-native.cn/docs/api-token",
      "resourceUri": "pushy-docs://docs/api-token",
      "content": "# API Key\n\nAPI Key 是一种用于在 CI/CD 流程或自动化脚本中调用 [Pushy API](https://update.reactnative.cn/api/openapi) 的认证方式。相比直接使用账号密码，API Key 更加安全且便于管理。\n\n## 使用场景\n\n- **持续集成/持续部署 (CI/CD)**：在 GitHub Actions、GitLab CI、Jenkins 等平台中自动发布热更新\n- **自动化脚本**：编写脚本批量管理应用、版本或原生包\n- **第三方工具集成**：将 Pushy 与其他开发工具集成\n\n## 创建 API Key\n\n1. 登录 [Pushy 管理后台](https://pushy-admin.reactnative.cn)\n2. 在左侧菜单中点击「API Key」\n3. 点击「创建 API Key」按钮\n4. 填写 API Key 名称（如：CI/CD Pipeline）\n5. 选择所需权限\n6. 可选设置过期时间\n7. 点击创建后，**立即复制并安全保存 API Key**\n\n:::warning\nAPI Key 只会在创建时显示一次，之后无法再次查看。请务必在创建后立即复制保存！\n:::\n\n## 权限说明\n\n| 权限              | 说明                 |\n| --------------- | ------------------ |\n| **读取 (read)**   | 查看应用、版本、原生包信息      |\n| **写入 (write)**  | 创建和更新应用、发布版本、上传原生包 |\n| **删除 (delete)** | 删除应用、版本、原生包        |\n\n:::info\n创建 API Key 时至少需要选择一个权限。根据实际需求选择最小必要权限是最佳实践。\n:::\n\n## 使用 API Key 调用 API\n\n在调用 Pushy API 时，将 API Key 添加到请求头中：\n\n```bash\ncurl -X GET \"https://update.reactnative.cn/api/app/list\" \\\n  -H \"x-api-token: YOUR_API_TOKEN\"\n```\n\n:::info\n请求头 `x-api-token` 与环境变量 `PUSHY_API_TOKEN` 是接口层面的名字，为兼容既有集成保持不变；页面与文档中统称 API Key，两者指的是同一样东西。\n:::\n\n### 在命令行工具中使用\n\n如果使用 `react-native-update-cli` 命令行工具（需 v2.7.0+），可以通过环境变量设置 API Key：\n\n```bash\nexport PUSHY_API_TOKEN=your_api_token_here\npushy bundle --platform android\n```\n\n### 在 CI/CD 中使用\n\n以 GitHub Actions 为例：\n\n```yaml\n# .github/workflows/publish.yml\nname: Publish Hot Update\n\non:\n  push:\n    branches: [main]\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      \n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n          \n      - name: Install dependencies\n        run: npm install && npm i -g react-native-update-cli\n        \n      - name: Publish update\n        env:\n          PUSHY_API_TOKEN: ${{ secrets.PUSHY_API_TOKEN }}\n        run: pushy bundle --platform android\n```\n\n:::tip\n在 CI/CD 环境中，建议将 API Key 存储在密钥管理中（如 GitHub Secrets），而不是直接写入代码。\n:::\n\n## 管理 API Key\n\n### 查看 API Key 列表\n\n在「API Key」页面可以看到所有已创建的 API Key，包括：\n\n- API Key 名称\n- 权限\n- 过期时间\n- 最后使用时间\n- 状态（正常/已过期/已撤销）\n\n### 撤销 API Key\n\n如果 API Key 泄露或不再需要，可以随时撤销：\n\n1. 在 API Key 列表中找到目标 API Key\n2. 点击「撤销」按钮\n3. 确认撤销\n\n:::warning\nAPI Key 撤销后立即生效，使用该 API Key 的所有请求将被拒绝。请确保在撤销前更新相关的 CI/CD 配置。\n:::\n\n## 安全建议\n\n1. **最小权限原则**：只授予 API Key 必要的权限\n2. **设置过期时间**：为临时使用的 API Key 设置合理的过期时间\n3. **定期轮换**：定期撤销旧 API Key 并创建新 API Key\n4. **安全存储**：不要将 API Key 提交到代码仓库，使用环境变量或密钥管理工具\n5. **监控使用**：定期检查 API Key 的最后使用时间，及时清理不再使用的 API Key\n\n## 限制\n\n- 每个用户最多可创建 **10 个** API Key\n- 如需创建更多，请先撤销不再使用的 API Key\n",
      "sections": [
        {
          "heading": "API Key",
          "level": 1,
          "offset": 0,
          "anchor": "api-key",
          "length": 136
        },
        {
          "heading": "使用场景",
          "level": 2,
          "offset": 136,
          "anchor": "使用场景",
          "length": 143
        },
        {
          "heading": "创建 API Key",
          "level": 2,
          "offset": 279,
          "anchor": "创建-api-key",
          "length": 259
        },
        {
          "heading": "权限说明",
          "level": 2,
          "offset": 538,
          "anchor": "权限说明",
          "length": 272
        },
        {
          "heading": "使用 API Key 调用 API",
          "level": 2,
          "offset": 810,
          "anchor": "使用-api-key-调用-api",
          "length": 270
        },
        {
          "heading": "在命令行工具中使用",
          "level": 3,
          "offset": 1080,
          "anchor": "在命令行工具中使用",
          "length": 172
        },
        {
          "heading": "在 CI/CD 中使用",
          "level": 3,
          "offset": 1252,
          "anchor": "在-cicd-中使用",
          "length": 47
        },
        {
          "heading": ".github/workflows/publish.yml",
          "level": 1,
          "offset": 1299,
          "anchor": "githubworkflowspublishyml",
          "length": 592
        },
        {
          "heading": "管理 API Key",
          "level": 2,
          "offset": 1891,
          "anchor": "管理-api-key",
          "length": 15
        },
        {
          "heading": "查看 API Key 列表",
          "level": 3,
          "offset": 1906,
          "anchor": "查看-api-key-列表",
          "length": 107
        },
        {
          "heading": "撤销 API Key",
          "level": 3,
          "offset": 2013,
          "anchor": "撤销-api-key",
          "length": 171
        },
        {
          "heading": "安全建议",
          "level": 2,
          "offset": 2184,
          "anchor": "安全建议",
          "length": 217
        },
        {
          "heading": "限制",
          "level": 2,
          "offset": 2401,
          "anchor": "限制",
          "length": 63
        }
      ]
    },
    {
      "slug": "api",
      "title": "API参考",
      "path": "pages/docs/api.mdx",
      "url": "https://pushy.react-native.cn/docs/api",
      "resourceUri": "pushy-docs://docs/api",
      "content": "# API参考\n\n### JavaScript 方法\n\n#### new Pushy(options: PushyOptions)\n\n创建 Pushy 热更新服务实例，其构造参数如下：\n\n```ts\ninterface PushyOptions {\n  // 必填，通过pushy createApp或selectApp命令，或在网页管理端获取\n  appKey: string;\n\n  // 自定义日志输出，也可用于上报统计数据\n  logger?: ({ type, data }: { type: EventType; data: EventData }) => void;\n\n  // 触发自动检查更新的策略\n  checkStrategy?:\n    | \"onAppStart\" // 仅在app启动时\n    | \"onAppResume\" // 仅在app从后台切换到前台时\n    | \"both\"; // 默认值，同时包含前两个场景\n    | null; // 不自动检查更新，必须手动调用checkUpdate方法，此选项需 v10.4.2+ 版本\n\n  // 自动下载和应用更新的策略\n  updateStrategy?:\n    | \"alwaysAlert\" // 调试环境（__DEV__）默认值，使用系统默认的alert页面提示热更且会在有报错时弹出提示\n    | \"alertUpdateAndIgnoreError\" // 生产环境默认值，在有热更时使用系统默认的alert页面提示热更，但不弹出任何报错提示\n    | \"silentAndNow\" // 自动静默下载并立刻应用热更\n    | \"silentAndLater\"; // 自动静默下载，但仅在用户退出app后重启时应用更新\n    | null; // 不自动下载和应用更新，如需自定义热更界面请选择此项\n\n  // 是否在热更重启后自动标记为成功，默认为true\n  // 一般情况下不建议手动标记\n  autoMarkSuccess?: boolean;\n\n  // 是否在若干ms后自动清除最后的报错，默认为不清除\n  dismissErrorAfter?: number;\n\n  // 是否在开发环境中检查热更，默认为false。如需在开发环境中调试热更，请打开此选项。\n  // 但即便打开此选项，也仅能检查、下载热更，并不能实际应用热更。实际应用热更必须在release包中进行。\n  // 此选项需 v10.4.2+ 版本\n  debug?: boolean;\n\n  // 是否在调用 checkUpdate 和 downloadUpdate 时抛出错误，默认为不抛出错误，通过 lastError 获取错误信息\n  // 启用后可以使用 try catch 语句 捕获错误，同时 lastError 也仍然可用\n  // try {\n  //   await checkUpdate();\n  // } catch (e) {\n  //   console.error(e);\n  // }\n  // 此选项需 v10.15.2+ 版本\n  throwError?: boolean;\n\n  // 在检查更新前执行，返回 false 则取消检查更新\n  // 此选项需 v10.12.0+ 版本\n  beforeCheckUpdate?: () => Promise<boolean>;\n\n  // 在每次检查更新结束后执行，可用于上报检查结果；不影响原有检查流程\n  // 此选项需 v10.38.3+ 版本\n  afterCheckUpdate?: (state: UpdateCheckState) => Promise<void> | void;\n\n  // 在下载更新前执行，返回 false 则取消下载更新，可以配合自定义的 metaInfo 做一些条件控制\n  // 此选项需 v10.12.0+ 版本\n  beforeDownloadUpdate?: (info: UpdateInfo) => Promise<boolean>;\n\n  // 在下载更新后执行，返回 false 则取消内置策略进一步执行，可以配合自定义的 metaInfo 做一些条件控制\n  // 此选项需 v10.27.0+ 版本\n  afterDownloadUpdate?: (info: UpdateInfo) => Promise<boolean>;\n\n  // 在原生包过期时执行，返回 false 则取消内置策略进一步执行，可以配合自定义的 metaInfo 做一些条件控制\n  // 此选项需 v10.28.2+ 版本\n  onPackageExpired?: (info: UpdateInfo) => Promise<boolean>;\n\n  // 在 switchVersion 或 restartApp 触发立即重启前执行，返回 false 则取消本次重启\n  // 可用于等待 Sentry 等原生 SDK 停止采样、flush 上报队列后再销毁 RN 实例\n  // 此选项需 v10.42.2+ 版本\n  beforeReload?: (\n    context: BeforeReloadContext,\n  ) => Promise<boolean | void> | boolean | void;\n\n  // 是否关闭更新生命周期事件上报（版本健康度统计），默认为 false（开启）\n  // 详见下方「版本健康度事件上报」一节\n  // 此选项需 v10.47.0+ 版本\n  disableTelemetry?: boolean;\n}\n\n// 检查更新结束后的状态\ntype UpdateCheckState = {\n  // completed: 检查完成；skipped: 因 debug、web 环境或 beforeCheckUpdate 返回 false 等原因跳过；error: 检查出错\n  status: \"completed\" | \"skipped\" | \"error\";\n  // status 为 completed 时的检查结果\n  result?: UpdateInfo;\n  // status 为 error 时的错误对象\n  error?: Error;\n};\n\n// beforeReload 接收到的重启上下文\ntype BeforeReloadContext = {\n  // switchVersion: 立即应用已下载热更；restartApp: 直接重启当前应用\n  type: \"switchVersion\" | \"restartApp\";\n  // type 为 switchVersion 时，表示即将应用的热更 hash\n  hash?: string;\n};\n\n// 日志事件类型\ntype EventType =\n  // 更新失败，重启后发生回滚\n  | \"rollback\"\n  // 检查更新时报错\n  | \"errorChecking\"\n  // 正在发起检查\n  | \"checking\"\n  // 正在下载更新\n  | \"downloading\"\n  // 已下载更新\n  | \"downloadSuccess\"\n  // 更新失败\n  | \"errorUpdate\"\n  // 更新成功\n  | \"markSuccess\"\n  // 已恢复到内置包（resetToPackagedBundle 成功），需 v10.48.0+ 版本\n  | \"reset\"\n  // 恢复内置包失败，需 v10.48.0+ 版本\n  | \"errorReset\"\n  // 下载apk\n  | \"downloadingApk\"\n  // 下载apk前申请存储权限被用户拒绝\n  | \"rejectStoragePermission\"\n  // 下载apk前申请存储权限发生错误\n  | \"errorStoragePermission\"\n  // 下载apk时发生错误\n  | \"errorDownloadAndInstallApk\";\n\n// 日志事件数据\ninterface EventData {\n  // 当前已完成的热更hash值，如尚未热更则为空字符串\n  currentVersion: string;\n  // 客户端版本信息\n  cInfo: {\n    rnu: string; // 当前 react-native-update 版本\n    rn: string; // 当前 react-native 版本\n    os: string; // 当前操作系统及版本\n    uuid: string; // 用户标识符\n  };\n  // 客户端原生版本号\n  packageVersion: string;\n  // 编译时间戳\n  buildTime: number;\n  // 报错相关的信息\n  message?: string;\n  // 发生回滚的版本hash值\n  rolledBackVersion?: string;\n  // 更新失败的新版本hash值\n  newVersion?: string;\n  // 其他一些数据\n  [key: string]: any;\n}\n```\n\n#### 版本健康度事件上报\n\n自 v10.47.0 起，SDK 会在更新流程的关键节点向更新服务自动上报少量生命周期事件，用于在管理端展示每个热更版本的健康度（下载失败率、patch 失败率、回滚率等），帮助你在发版后第一时间发现有问题的版本：\n\n- `download_success` / `download_fail`：热更包下载成功 / 全部下载策略失败\n- `patch_fail`：增量 patch 应用失败（含降级为完整包成功的情形）或版本切换失败\n- `rollback`：新版本启动异常，发生自动回滚\n- `mark_success`：新版本启动并标记成功\n\n上报内容仅包含热更版本 hash、原生版本号、`cInfo`（SDK/RN/系统版本与设备标识符，与 checkUpdate 请求一致）以及失败时截断后的错误信息摘要，不涉及任何业务数据。上报为一次性异步请求，不重试、失败静默，不会影响更新流程与性能；调试环境（`__DEV__`）下不会上报。\n\n如不希望上报这些事件，可在初始化时关闭：\n\n```js\nconst pushyClient = new Pushy({\n  appKey,\n  disableTelemetry: true,\n});\n```\n\n注意：关闭后管理端将无法统计该客户端的版本健康度，也无法在版本发生大面积异常时为你提供预警。\n\n#### beforeReload 示例：重启前清理原生 SDK\n\n`beforeReload` 会在 `switchVersion()` 和 `restartApp()` 真正重启前执行。返回 `false` 会取消本次重启；抛出异常或 Promise reject 时也不会继续重启。`switchVersionLater()` 不会立即销毁当前 RN 实例，因此不会触发此钩子。\n\n如果应用接入了 Sentry profiling、性能采样、日志上传等可能跨线程工作的原生 SDK，可以在这里先停止采样并 flush 队列，再让 Pushy 重启：\n\n```ts\nimport { NativeModules } from \"react-native\";\nimport * as Sentry from \"@sentry/react-native\";\nimport { Pushy } from \"react-native-update\";\n\nconst pushyClient = new Pushy({\n  appKey,\n  beforeReload: async (_context) => {\n    try {\n      NativeModules.RNSentry?.stopProfiling?.();\n    } catch {}\n\n    const flushed = await Promise.race([\n      Sentry.flush(),\n      new Promise<boolean>((resolve) => setTimeout(() => resolve(false), 1500)),\n    ]);\n\n    // 返回 false 会取消本次立即重启，等待下一次检查或手动触发。\n    return flushed;\n  },\n});\n```\n\n#### useUpdate()\n\n热更相关的工具函数。此方法也可使用别名 `usePushy` 引入。\n\n:::info\n注意，在使用 `<UpdateProvider>` 的当前组件（一般是根组件）中无法直接调用`useUpdate`，只有当前组件的子组件才能调用。\n:::\n\n```js\nconst {\n  checkUpdate,\n  switchVersion,\n  switchVersionLater,\n  markSuccess,\n  dismissError,\n  downloadUpdate,\n  downloadAndInstallApk,\n  getCurrentVersionInfo,\n  currentVersionInfo,\n  parseTestQrCode,\n  currentHash,\n  packageVersion,\n  client,\n  progress,\n  updateInfo,\n  lastError,\n  restartApp,\n  resetToPackagedBundle,\n} = useUpdate();\n```\n\n其类型定义和功能如下：\n\n```ts\ninterface UpdateContext {\n  // 检查更新（注意在 v10.26.0 版本之前，`checkUpdate`方法本身没有返回值，只能从`useUpdate()`返回的`updateInfo`中获取）\n  // 我们也仍然推荐优先从`useUpdate()`中获取`updateInfo`\n  checkUpdate: () => Promise<void | UpdateInfo>;\n  // 下载热更完成后调用，立即重启切换新版本\n  switchVersion: () => Promise<void>;\n  // 下载热更完成后调用，用户手动重启app后切换新版本（静默更新）\n  switchVersionLater: () => Promise<void>;\n  // 热更完成重启后，手动标记热更完成\n  markSuccess: () => void;\n  // 清除最后的报错状态\n  dismissError: () => void;\n  // 下载热更, v10.16.0+ 版本返回值为`boolean`，表示是否下载成功\n  downloadUpdate: () => Promise<boolean | void>;\n  // 下载并安装apk\n  downloadAndInstallApk: (url: string) => Promise<void>;\n  // 异步获取当前已热更版本的信息，v10.31.2 版本后用 `currentVersionInfo` 代替\n  getCurrentVersionInfo: () => Promise<{\n    name?: string;\n    description?: string;\n    metaInfo?: string;\n  }>;\n  // 当前已热更版本的信息，需 v10.31.2+ 版本\n  currentVersionInfo: {\n    name?: string;\n    description?: string;\n    metaInfo?: string;\n  };\n  // 解析测试二维码，此方法需 v10.11.2+ 版本\n  parseTestQrCode: (qrCode: string) => void;\n  // 当前的版本hash\n  currentHash: string;\n  // 当前的原生版本号\n  packageVersion: string;\n  // 当前的pushy热更服务实例\n  client?: Pushy;\n  // 立即重启应用，需 v10.28.2+ 版本\n  restartApp: () => Promise<void>;\n  // 恢复到原生包内置的 bundle，返回是否成功，需 v10.48.0+ 版本\n  resetToPackagedBundle: (options?: { restart?: boolean }) => Promise<boolean>;\n  // 下载开始后的进度数据\n  progress?: {\n    hash: string;\n    // 已下载的字节数\n    received: number;\n    // 待下载的总字节数\n    total: number;\n  };\n  // 热更相关信息\n  updateInfo?: {\n    // 已是最新版本，无需热更\n    upToDate?: true;\n    // 当前原生版本已过期，需要下载新的原生版本\n    expired?: true;\n    // 在pushy网页管理端设置的原生版本下载地址\n    downloadUrl?: string;\n    // 是否存在新的热更\n    update?: true;\n    // 新热更的版本名称\n    name?: string;\n    // 新热更的hash值\n    hash?: string;\n    // 新热更的更新说明\n    description?: string;\n    // 新热更携带的额外元数据\n    metaInfo?: string;\n    // 当前热更是否已暂停\n    paused?:\n      | \"app\" // 当前应用所有原生版本暂停\n      | \"package\" // 仅当前原生版本暂停\n      | \"quota\"; // 因检查次数超限而暂停\n    // 其他信息\n    message?: string;\n  };\n  // 检查、下载、应用热更等过程中的最近一次报错\n  lastError?: Error;\n}\n```\n\n***\n\n#### async function checkUpdate()\n\n触发更新检查，返回`updateInfo`（注意在 v10.26.0 版本之前，`checkUpdate`方法本身没有返回值，只能从`useUpdate()`返回的`updateInfo`中获取，且我们仍然推荐优先使用`useUpdate()`来获取），返回值有三种情形：\n\n1. `{expired: true}`：该应用原生包已过期（三种情况：1. 主动设置为过期状态，2. 主动删除，3. 从未上传），需要引导用户下载或跳转到应用市场(需要在网页管理端设置中填写`downloadUrl`)。如需在应用内执行 apk 更新，还需配置[安装权限](/docs/api.md#async-function-downloadandinstallapkurl)。\n\n```js\n{\n    expired: true,\n    downloadUrl: 'http://appstore/downloadUrl',\n}\n```\n\n2. `{upToDate: true}`：当前已经更新到最新，无需进行更新。\n\n3. `{update: true}`：当前有新版本可以更新。`name`、`description`字段可以用于展示给用户版本号，更新内容等信息，而`metaInfo`字段则可以根据你的需求自定义一些标记(如是否静默更新、是否强制更新等等，自己根据标记的属性做一些条件流程控制)，具体用法可参考[场景实践](/docs/bestpractice.md#%E5%85%83%E4%BF%A1%E6%81%AFmeta-info%E7%9A%84%E4%BD%BF%E7%94%A8)。另外还有几个字段，包含了热更新文件的下载地址，\n\n```js\n{\n    update: true,\n    name: '1.0.3-rc',\n    hash: 'hash',\n    description: '添加聊天功能\\n修复商城页面BUG',\n    metaInfo: '{\"silent\":true}',\n    pdiffUrl: 'http://update-packages.reactnative.cn/hash',\n    diffUrl: 'http://update-packages.reactnative.cn/hash',\n}\n```\n\n***\n\n#### async function downloadUpdate()\n\n下载热更包。仅当`update:true`时实际进行下载。会更新`progress`数据。v10.16.0+ 版本返回值为`boolean`，表示是否下载成功。\n\n***\n\n#### async function downloadAndInstallApk(url)\n\n下载更新的 apk 包并直接安装。`url`必须为可直接下载到 apk 文件的地址。\n\n由于系统安全限制等各种外部因素，此功能并不能保证一定正常调用。在应用当前运行周期内，如果下载或者安装失败，则再次调用时不会重试，只会静默失败。\n\n注意要使用这个功能还需要在`AndroidManifest.xml`中手动添加安装权限，如果需要考虑 Android 7.0 以下的客户，则还需要添加外部存储权限。\n\n```xml\n<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n\n<!-- 如果需要考虑Android 7.0以下的客户，则还需要添加外部存储权限 -->\n<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n```\n\n注意某些应用市场可能会因为上述权限拒绝应用上架。去掉上述两个权限并不影响热更新功能。\n\n***\n\n#### function markSuccess()\n\n**一般情况下请勿手动调用此函数**。调用此函数作为更新成功的标记（否则下次启动会默认失败自动回滚）。\n\n***\n\n#### currentVersionInfo\n\n当前已热更版本的信息（如尚未热更过则为空对象）。需 v10.31.2+ 版本。\n\n`currentVersionInfo` 是同步字段，推荐用它代替 `getCurrentVersionInfo()`：\n\n```js\nconst { currentVersionInfo } = useUpdate();\n\nconsole.log(currentVersionInfo.name);\n```\n\n字段示例：\n\n```js\n{\n    name: '1.0.3-rc',\n    description: '添加聊天功能\\n修复商城页面BUG',\n    metaInfo: '{\"silent\":true}',\n}\n```\n\n***\n\n#### async function getCurrentVersionInfo()\n\n获取当前已热更版本的信息（如尚未热更过则返回空对象）。v10.31.2 版本之后可以直接用 `currentVersionInfo` 代替。\n\n返回值示例：\n\n```js\n{\n    name: '1.0.3-rc',\n    description: '添加聊天功能\\n修复商城页面BUG',\n    metaInfo: '{\"silent\":true}',\n}\n```\n\n***\n\n#### function restartApp()\n\n立即重启应用。v10.28.2+ 版本可用。\n\n如果配置了 `beforeReload`，会等待它完成后再重启；当 `beforeReload` 返回 `false`、抛出异常或 Promise reject 时，会取消本次重启。\n\n***\n\n#### async function resetToPackagedBundle(options?)\n\n恢复到原生包内置的 bundle，返回 `boolean` 表示是否成功。v10.48.0+ 版本可用。\n\n调用后会清空全部热更状态、删除所有已下载的热更版本，下次启动将直接加载打包在原生安装包内的 bundle。传入 `{ restart: true }` 则会在成功后立即重启生效（重启内部走 `restartApp`，同样受 `beforeReload` 控制）。设备标识符（uuid）会保留，不影响灰度分桶。\n\n可用于客服指令、远程开关、异常兜底等需要\"恢复出厂\"的场景：\n\n```js\nconst { resetToPackagedBundle } = useUpdate();\n\n// 清空热更并立即重启回内置包\nconst ok = await resetToPackagedBundle({ restart: true });\n```\n\n也可以通过热更服务实例直接调用：\n\n```js\nconst ok = await pushyClient.resetToPackagedBundle();\n```\n\n与其他更新流程 API 一致，此方法默认不抛出错误：失败时返回 `false`，错误信息（错误码 `RESET_FAILED`）可通过 `lastError` / `onError` 获取；如初始化时配置了 `throwError: true` 则会抛出。\n\n:::warning\n请务必检查返回值：此方法需要原生模块支持（v10.48.0+），如果只是通过热更把新版 JS 下发到了旧版原生包上，调用会返回 `false`——此时应用仍在运行热更 bundle，并没有恢复到内置包。\n:::\n\n***\n\n#### function switchVersion()\n\n立即重启应用，并加载已经下载完毕的版本。\n\n> 注意!不可依赖`progress`来判断下载完成，必须要在`await downloadUpdate()`之后再调用此方法。\n\n如果配置了 `beforeReload`，会传入 `{ type: \"switchVersion\", hash }` 并等待它完成后再重启；当 `beforeReload` 返回 `false`、抛出异常或 Promise reject 时，会取消本次重启。\n\n***\n\n#### function switchVersionLater()\n\n在下一次启动应用的时候加载已经下载完毕的版本。\n\n> 注意!不可依赖`progress`来判断下载完成，必须要在`await downloadUpdate()`之后再调用此方法。\n\n此方法不会立即销毁当前 RN 实例，因此不会触发 `beforeReload`。\n\n***\n\n#### function parseTestQrCode(qrCode: string)\n\n解析测试二维码，一般用于给 QA 人员测试热更新。如果在应用中已有扫码功能，则可以在应用中扫描 pushy 后台的测试二维码来测试任意版本的热更包。\n注意如果你使用自定义的(更新策略（updateStrategy）)\\[[https://pushy.reactnative.cn/docs/integration#updatestrategy%E6%9B%B4%E6%96%B0%E5%BA%94%E7%94%A8%E7%AD%96%E7%95%A5\\]，请务必从](https://pushy.reactnative.cn/docs/integration#updatestrategy%E6%9B%B4%E6%96%B0%E5%BA%94%E7%94%A8%E7%AD%96%E7%95%A5]，请务必从) `useUpdate()` 中获取 `updateInfo` ，而不要依赖 checkUpdate 方法的返回值，否则扫码不会有后续动作。\n\n![testqrcode](/static/image/testqrcode.24c22c1ebe.png)\n注意使用此方法，上述界面中的\"使用 Deep Link\"选项 **请不要\n** 勾选。\n代码示例：\n\n```js\n<Camera\n  onReadCode={({ nativeEvent: { codeStringValue } }) => {\n    // 识别到二维码后先关闭相机\n    setShowCamera(false);\n    // 先解析是否是pushy的测试二维码\n    if (parseTestQrCode(codeStringValue)) {\n      // 如果是pushy的测试二维码，则不再做其他业务扫码逻辑\n      return;\n    }\n    // 如果不是，继续处理其他业务扫码逻辑\n  }}\n/>\n```\n\n***\n\n### Android 方法\n\n#### UpdateContext.setCustomInstanceManager(ReactInstanceManager instanceManager)\n\n如果是集成/混编 Android 方案，则可以使用此方法传入你自行创建的 ReactInstanceManager。自`v5.5.8`版本起可用。\n\n示例：\n\n```java\nimport cn.reactnative.modules.update.UpdateContext\n\nmReactInstanceManager = ReactInstanceManager.builder()\n                // ...各种setter，但注意不要调用setBundleAssetName\n                .setJSBundleFile(UpdateContext.getBundleUrl(mContext, \"assets://index.android.bundle\"))\n                .build();\nUpdateContext.setCustomInstanceManager(mReactInstanceManager);\n```\n",
      "sections": [
        {
          "heading": "API参考",
          "level": 1,
          "offset": 0,
          "anchor": "api参考",
          "length": 9
        },
        {
          "heading": "JavaScript 方法",
          "level": 3,
          "offset": 9,
          "anchor": "javascript-方法",
          "length": 19
        },
        {
          "heading": "new Pushy(options: PushyOptions)",
          "level": 4,
          "offset": 28,
          "anchor": "new-pushyoptions-pushyoptions",
          "length": 3887
        },
        {
          "heading": "版本健康度事件上报",
          "level": 4,
          "offset": 3915,
          "anchor": "版本健康度事件上报",
          "length": 592
        },
        {
          "heading": "beforeReload 示例：重启前清理原生 SDK",
          "level": 4,
          "offset": 4507,
          "anchor": "beforereload-示例重启前清理原生-sdk",
          "length": 807
        },
        {
          "heading": "useUpdate()",
          "level": 4,
          "offset": 5314,
          "anchor": "useupdate",
          "length": 2507
        },
        {
          "heading": "async function checkUpdate()",
          "level": 4,
          "offset": 7821,
          "anchor": "async-function-checkupdate",
          "length": 1025
        },
        {
          "heading": "async function downloadUpdate()",
          "level": 4,
          "offset": 8846,
          "anchor": "async-function-downloadupdate",
          "length": 125
        },
        {
          "heading": "async function downloadAndInstallApk(url)",
          "level": 4,
          "offset": 8971,
          "anchor": "async-function-downloadandinstallapkurl",
          "length": 512
        },
        {
          "heading": "function markSuccess()",
          "level": 4,
          "offset": 9483,
          "anchor": "function-marksuccess",
          "length": 87
        },
        {
          "heading": "currentVersionInfo",
          "level": 4,
          "offset": 9570,
          "anchor": "currentversioninfo",
          "length": 342
        },
        {
          "heading": "async function getCurrentVersionInfo()",
          "level": 4,
          "offset": 9912,
          "anchor": "async-function-getcurrentversioninfo",
          "length": 238
        },
        {
          "heading": "function restartApp()",
          "level": 4,
          "offset": 10150,
          "anchor": "function-restartapp",
          "length": 150
        },
        {
          "heading": "async function resetToPackagedBundle(options?)",
          "level": 4,
          "offset": 10300,
          "anchor": "async-function-resettopackagedbundleoptions",
          "length": 766
        },
        {
          "heading": "function switchVersion()",
          "level": 4,
          "offset": 11066,
          "anchor": "function-switchversion",
          "length": 255
        },
        {
          "heading": "function switchVersionLater()",
          "level": 4,
          "offset": 11321,
          "anchor": "function-switchversionlater",
          "length": 174
        },
        {
          "heading": "function parseTestQrCode(qrCode: string)",
          "level": 4,
          "offset": 11495,
          "anchor": "function-parsetestqrcodeqrcode-string",
          "length": 867
        },
        {
          "heading": "Android 方法",
          "level": 3,
          "offset": 12362,
          "anchor": "android-方法",
          "length": 16
        },
        {
          "heading": "UpdateContext.setCustomInstanceManager(ReactInstanceManager instanceManager)",
          "level": 4,
          "offset": 12378,
          "anchor": "updatecontextsetcustominstancemanagerreactinstancemanager-instancemanager",
          "length": 532
        }
      ]
    },
    {
      "slug": "bestpractice",
      "title": "场景实践",
      "path": "pages/docs/bestpractice.md",
      "url": "https://pushy.react-native.cn/docs/bestpractice",
      "resourceUri": "pushy-docs://docs/bestpractice",
      "content": "# 场景实践\n\n#### 优化原生和热更包体积\n\n##### iOS 原生包优化(ipa)\n\n对于同一份 archive（其版本号、编译时间和内置 bundle 已固定，不会受导出方式所影响），可以用不同选项多次导出 ipa，选择其中最小的上传到 pushy 服务器作为热更基准包。\n\n![bitcode](/static/image/exportipa.91920cd4bf.png)\n##### Android 原生包优化(apk)\n\napk 的优化主要考虑两个方向：\n\n- [启用 proguard 压缩混淆源码](https://reactnative.cn/docs/signed-apk-android#%E5%90%AF%E7%94%A8proguard%E6%9D%A5%E5%87%8F%E5%B0%91apk%E7%9A%84%E5%A4%A7%E5%B0%8F%EF%BC%88%E5%8F%AF%E9%80%89%EF%BC%89)。但这一步可能导致一些使用反射的代码运行时报错，启用后需要充分测试每个页面和功能，以及需要阅读一些第三方关于 proguard 的特别设置说明。\n- [分开编译不同的 cpu 架构](https://reactnative.cn/docs/signed-apk-android#%E9%92%88%E5%AF%B9%E4%B8%8D%E5%90%8C%E7%9A%84-cpu-%E6%9E%B6%E6%9E%84%E7%94%9F%E6%88%90-apk-%E4%BB%A5%E5%87%8F%E5%B0%8F-apk-%E6%96%87%E4%BB%B6%E7%9A%84%E5%A4%A7%E5%B0%8F)。找到`android/app/build.gradle`中的 cpu 架构部分，如下所示启用`enable`选项：\n\n```diff\nsplits {\n    abi {\n        reset()\n-       enable enableSeparateBuildPerCPUArchitecture\n+       enable true        // 启用单独的 cpu 架构编译\n        universalApk false  // If true, also generate a universal APK\n    }\n}\n```\n\n如此一来会在编译目录中输出多个 apk 文件，分发和上传到热更新服务时只需要使用`app-arm64-v8a-release.apk`文件，可以大幅减小 apk 的大小。\n\n##### 热更新包优化(ppk)\n\n热更新包的主要内容是 js 包和其所引用的静态资源（主要是图片）。\n\n- js 包成分分析。可以借助一些第三方工具（如[react-native-bundle-visualizer](https://github.com/IjzerenHein/react-native-bundle-visualizer)）来分析 js 文件中哪些占比较大，是否可以用其他库替换等（如 dayjs 替换 moment，lodash-es 替换 lodash）。\n- 图片优化。\n  - 在保证体验的情况下，使用一些工具对图片进行裁剪压缩。\n  - 如不需要图片的透明像素，可以考虑将 png 格式转为 jpg 格式。\n  - 还可以进一步考虑压缩比更高的图片格式，例如 webp 格式（需要第三方原生插件例如[react-native-webp-format](https://github.com/Aleksefo/react-native-webp-format#readme)），heif 格式（iOS 11 和 Android 10 以上原生支持）等。这里有个图片格式对比可供参考 [https://compare.rokka.io/\\_compare/#heif=40\\&jpeg=80\\&webp=80\\&av1=40\\&width=800](https://compare.rokka.io/_compare/#heif=40\\&jpeg=80\\&webp=80\\&av1=40\\&width=800)。\n\n#### 有很多渠道包需要热更，如何操作比较方便？\n\n1. 如果渠道包的`js代码和初始资源`有差别（无论多么细微的差别都会生成不同的 jsbundle），那么只能单独生成 apk，分别上传和绑定。可以考虑写一些脚本自动调用 cli 来执行批量操作。\n2. 如果渠道包的`js代码和初始资源`完全一致，可以考虑使用[Flavor 构建](https://developer.android.com/studio/build/build-variants?hl=zh-cn)，或其他一些动态生成渠道包的方案（比如[腾讯的 VasDolly](https://github.com/Tencent/VasDolly)，[美团的 walle](https://github.com/Meituan-Dianping/walle)等），这样所有的渠道包基于同一个基础 apk 生成（因而会有相同的编译时间戳和 jsbundle）。这样可以只用上传一个基础 apk，对此 apk 的热更操作可以对所有渠道包生效。\n3. 如果您是`高级版`、`专业版`或`大客户VIP版`客户，也可以考虑在管理后台的应用设置中启用`忽略编译时间戳`。此选项仅对比版本号，不校验时间戳，可提供更宽容的热更策略，但可能消耗更多流量。\n\n#### 如何支持 aab 格式的原生包？\n\n将 react-native-update-cli 更新到 v2.6.0 以上版本，即可使用 pushy parseAab 和 pushy uploadAab 命令来支持 aab 格式的原生包。\n\n如果同一个 Android 版本既需要上架 Google Play 的 AAB，也需要给其他渠道分发 APK，建议不要分两次构建。请在项目根目录的`package.json`中配置一个 npm script，让 CI 和本地发布都通过同一个入口，在同一次 Gradle 调用中同时执行`assembleRelease`和`bundleRelease`。如果已有`scripts`字段，只需要追加其中一项：\n\n```json\n{\n  \"scripts\": {\n    \"package:android:release\": \"cd android && ./gradlew clean assembleRelease bundleRelease\"\n  }\n}\n```\n\n```bash\n$ npm run package:android:release\n```\n\n这样生成的`android/app/build/outputs/apk/release/app-release.apk`和`android/app/build/outputs/bundle/release/app-release.aab`来自同一次 release 构建，编译时间戳保持一致。之后按渠道分发对应格式即可：Google Play 使用 AAB，直装包或第三方渠道使用 APK。如果项目使用 flavor，请把 npm script 中的任务名改为实际 variant，例如`assembleProdRelease`和`bundleProdRelease`。\n\n#### 测试与回滚\n\n自 v10.11.2 版本开始，可以使用以下两种快捷扫码方案来测试热更，而无需提前进行绑定：\n\n![testqrcode](/static/image/testqrcode.24c22c1ebe.png)\n- 首先请添加测试用的 [DeepLink](/docs/getting-started.md#%E6%B7%BB%E5%8A%A0%E6%B5%8B%E8%AF%95%E7%94%A8%E7%9A%84-deep-link) 功能\n\n先参考上述链接在原生端添加启用测试用的 [DeepLink](/docs/getting-started.md#%E6%B7%BB%E5%8A%A0%E6%B5%8B%E8%AF%95%E7%94%A8%E7%9A%84-deep-link)，重新打包。JS 代码中无需任何改动，只需在上述界面中勾选“使用 Deep Link”，填入您应用的协议名，例如\"pushy://\"，然后使用系统相机或系统内置的扫一扫功能扫码（注意不能使用微信扫码），即可自动调起应用并触发更新。\n\n- 若应用自带扫码功能\n\n请参考 [parseTestQrCode](/docs/api.md#function-parsetestqrcodeqrcode-string) 方法的说明。\n\n<details>\n<summary>若您的应用不具有上述两项功能，或 pushy 版本低于 v10.11.2，则可以参考如下测试方式（不推荐）</summary>\n\n先发布一个**测试包**，再发布一个除了版本号以外均完全相同的**正式包**。\n\n例如，假设我们有一个正式包，版本为`1.6.0`，那么可以修改版本号重新打包一个`1001.6.0`，以一个明显不太正常的版本号来标识它是一个测试版本，同时后几位相同，可以表明它和某个正式版本存在关联（内容/依赖一致）。\n\n在每次往发布包发起热更新之前，先对**测试包**`1001.6.0`进行更新操作，基本测试通过之后，再在网页后台上将热更包重新绑定到**正式包**`1.6.0`上。如果在测试包中发现了重大问题，你就可以先进行修复，更新测试确认通过后再部署到正式线上环境。这样，可以最大程度的避免发生线上事故。\n\n</details>\n\n若您用来测试的原生包当前已经有发布的热更版本且更新策略是立即应用，那么和扫码测试之间可能产生策略冲突，比如扫码更新后又立即强制更新到发布版本，对测试带来不便。此时可以考虑在扫码更新之后，利用 beforeCheckUpdate 回调临时禁止更新检查，直到下次启动再恢复。\n\n```js\n\n// isFirstTimeDebug 这个标志位需要 v10.37.0+ 版本，表示当前是扫码热更后的第一次启动\nimport { isFirstTimeDebug } from 'react-native-update/src/core';\n\nconst pushyClient = new Pushy({\n  beforeCheckUpdate: () => {\n    if (isFirstTimeDebug) {\n      // 如果当前是扫码热更后的第一次启动，则跳过热更检查\n      return false;\n    }\n    return true;\n  },\n})\n```\n\n万一确实发生线上事故需要回滚的话，先立即对原生包或者整个应用设置暂停热更，然后更改绑定到之前正常的版本，或者利用版本控制系统回滚代码到正常的状态，然后重新生成热更包并推送。\n\n#### 元信息(Meta Info)的使用\n\n在发布热更新版本时，或者在网页端，你可以编辑版本的元信息。这是一段在检查更新时可以获得的字符串，你可以在其中按你所想的格式（一般建议用[JSON 格式](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/JSON)）保存一些信息。\n\n比如我们可以在元信息中约定字段标志`silent`，表示需要静默更新。当我们上传热更包填写 metainfo 时，以[JSON 格式](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/JSON)输入：\n\n```json\n{ \"silent\": true }\n```\n\n> 请注意，我们并不对输入做任何格式校验和约束，请自行校验输入是否正确。\n\n此时在客户端检查更新时，能获取到我们刚刚输入的元信息，但它并不具备任何功能，只是一个字符串而已。所以我们其实需要**预先**在更新流程中加入对应的处理逻辑：\n\n```js\n// 调用 useUpdate() 获取 updateInfo\nif (updateInfo.expired) {\n  // ... 原生包版本过期，下载或跳转下载页面\n} else if (updateInfo.upToDate) {\n  // ... 没有更新，弹提示或忽略\n} else {\n  // 有更新，一般来说我们在这里给用户弹窗提示，让用户选择是否更新\n  // 那么静默更新的本质其实就是不弹窗，直接执行，所以可以在这里加入额外的判断流程\n  // ...\n}\n```\n\n我们在原有的更新流程中加入元信息的读取和判断：\n\n```js\nlet metaInfo = {};\ntry {\n  // 注意 JSON 输入有可能有错误，需要用 try 语句来避免应用被带崩\n  metaInfo = JSON.parse(updateInfo.metaInfo);\n} catch (e) {\n  // 异常处理，忽略或上报？\n}\n\nif (metaInfo.silent) {\n  // 如果热更包携带有 silent 字段，不询问用户，直接执行更新\n  switchVersion();\n} else {\n  // 否则还是走之前的询问流程\n  // Alert.alert('提示', '检查到新的版本.......\n}\n```\n\n又比如，可能某个版本包含一些重要的公告内容，所以还可以在上面插入一个公告字段等等。如何使用元信息，完全取决于您的想象力！\n",
      "sections": [
        {
          "heading": "场景实践",
          "level": 1,
          "offset": 0,
          "anchor": "场景实践",
          "length": 8
        },
        {
          "heading": "优化原生和热更包体积",
          "level": 4,
          "offset": 8,
          "anchor": "优化原生和热更包体积",
          "length": 17
        },
        {
          "heading": "iOS 原生包优化(ipa)",
          "level": 5,
          "offset": 25,
          "anchor": "ios-原生包优化ipa",
          "length": 169
        },
        {
          "heading": "Android 原生包优化(apk)",
          "level": 5,
          "offset": 194,
          "anchor": "android-原生包优化apk",
          "length": 913
        },
        {
          "heading": "热更新包优化(ppk)",
          "level": 5,
          "offset": 1107,
          "anchor": "热更新包优化ppk",
          "length": 673
        },
        {
          "heading": "有很多渠道包需要热更，如何操作比较方便？",
          "level": 4,
          "offset": 1780,
          "anchor": "有很多渠道包需要热更如何操作比较方便",
          "length": 552
        },
        {
          "heading": "如何支持 aab 格式的原生包？",
          "level": 4,
          "offset": 2332,
          "anchor": "如何支持-aab-格式的原生包",
          "length": 789
        },
        {
          "heading": "测试与回滚",
          "level": 4,
          "offset": 3121,
          "anchor": "测试与回滚",
          "length": 1498
        },
        {
          "heading": "元信息(Meta Info)的使用",
          "level": 4,
          "offset": 4619,
          "anchor": "元信息meta-info的使用",
          "length": 1117
        }
      ]
    },
    {
      "slug": "cli",
      "title": "命令行工具",
      "path": "pages/docs/cli.md",
      "url": "https://pushy.react-native.cn/docs/cli",
      "resourceUri": "pushy-docs://docs/cli",
      "content": "# 命令行工具\n\n### 安装\n\n```\n$ npm install -g react-native-update-cli\n```\n\n### 命令\n\n#### pushy bundle\n\n生成热更资源包。会自动探测项目中是否使用了 expo、taro 等框架并自动调用对应的命令行打包。如果探测不准确或使用对应命令行打包时报错，可尝试使用 `--rncli` 参数来指定官方命令行打包。\n\n- platform: ios|android|harmony 对应的平台\n- entryFile: 入口脚本文件\n- intermediaDir: 临时文件输出目录\n- output: 最终 ppk 文件输出路径\n- dev: 是否打包开发版本\n- sourcemap: 是否生成 sourcemap(需 cli 版本 1.11.0+)\n- no-interactive: 不进行交互式提示\n- rncli: 指定使用官方命令行打包 (需 cli 版本 1.40.0+)\n- expo: 指定使用 expo 的命令行打包 (需 cli 版本 1.40.0+)\n- taro: 指定使用 taro 的命令行打包 (需 cli 版本 1.40.0+)\n\n从 v1.44.2 版本开始，新增了以下直接上传发布的参数（等同于打包完成后连续调用 `pushy publish` 命令）：\n\n- name: 指定热更新版本的名字(热更自身的版本号)\n- description: 指定热更新版本的描述信息，可以对用户进行展示\n- metaInfo: 指定热更新版本的元信息，可以用来保存一些额外信息，具体用法可参考[场景实践](/docs/bestpractice.md#%E5%85%83%E4%BF%A1%E6%81%AFmeta-info%E7%9A%84%E4%BD%BF%E7%94%A8)。\n\n从 v1.46.1 版本开始，在传入 `name`， `description`， `metaInfo` 参数的同时，可继续叠加以下参数（等同于在调用`pushy publish` 命令后，再连续调用 `pushy update` 命令）：\n\n- packageId: 要绑定的原生包 ID （多选一）\n- packageVersion: 要绑定的原生包版本名（多选一）\n- minPackageVersion: 要绑定的最低原生包版本，大于等于此版本的将逐个绑定（多选一）\n- maxPackageVersion: 要绑定的最高原生包版本，小于等于此版本的将逐个绑定（多选一）\n- packageVersionRange: 要绑定的原生包版本范围（多选一），范围格式遵循 semver 的语法，可参考 [https://quickref.cnxiaobai.com/docs/semver.html](https://quickref.cnxiaobai.com/docs/semver.html) 或 [https://devhints.io/semver](https://devhints.io/semver)\n\n示例：\n\n```bash\n\npushy bundle --platform android --name \"1.0.0\" --description \"热更新版本 1.0.0\" --metaInfo '{\"key\": \"value\"}' --packageVersionRange \">=1.0 <3.0\"\n```\n\n***\n\n#### pushy parseIpa \\[ipaFile]\n\n解析 ipa 文件并输出一些相关信息，如版本号，编译时间戳等。\n\n***\n\n#### pushy parseApk \\[apkFile]\n\n解析 apk 文件并输出一些相关信息，如版本号，编译时间戳等。\n\n***\n\n#### pushy parseApp \\[appFile]\n\n解析 app 文件并输出一些相关信息，如版本号，编译时间戳等。\n\n***\n\n#### pushy parseAab \\[aabFile]\n\n解析 aab 文件并输出一些相关信息，如版本号，编译时间戳等。\n\n***\n\n#### pushy diff \\[origin]\\[next]\n\n提供两个 ppk 文件，生成从 origin 到 next 版本的差异更新包。\n\n- output: diff 文件输出路径\n\n***\n\n#### pushy diffFromApk \\[apkFile]\\[next]\n\n提供一个 apk 文件和一个 ppk 文件，生成从 apk 文件到 next 版本的差异更新包。\n\n如果使用热更新开放平台，你不需要自己执行此命令。\n\n- output: diff 文件输出路径\n\n***\n\n#### pushy diffFromApp \\[appFile]\\[next]\n\n提供一个 app 文件和一个 ppk 文件，生成从 app 文件到 next 版本的差异更新包。\n\n如果使用热更新开放平台，你不需要自己执行此命令。\n\n- output: diff 文件输出路径\n\n***\n\n#### pushy diffFromIpa \\[ipaFile]\\[next]\n\n提供一个 ipa 文件和一个 ppk 文件，生成从 ipa 文件到 next 版本的差异更新包。\n\n如果使用热更新开放平台，你不需要自己执行此命令。\n\n- output: diff 文件输出路径\n\n***\n\n#### pushy login \\[email]\\[pwd]\n\n登录热更新开放平台。你需要先登录才能使用下面的命令。\n\n***\n\n#### pushy logout\n\n登出并清除本地的登录信息\n\n***\n\n#### pushy me\n\n查看自己是否已经登录，以及昵称等信息。\n\n***\n\n#### pushy createApp\n\n创建应用并立刻绑定到当前工程。这项操作也可以在网页管理端进行。\n\n- platform: ios|android|harmony 对应的平台\n- name: 应用名称\n- downloadUrl: 应用安装包的下载地址\n\n***\n\n#### pushy deleteApp \\[appId]\n\n删除已有应用。所有已创建的应用包、热更新版本都会被同时删除。这项操作也可以在网页管理端进行。\n\n- appId: 要删除的应用 ID\n\n***\n\n#### pushy apps\n\n查看当前已创建的全部应用。这项操作也可以在网页管理端进行。\n\n- platform: ios|android|harmony 对应的平台\n\n***\n\n#### pushy selectApp \\[appId]\n\n绑定应用到当前工程。\n\n- platform: ios|android|harmony 对应的平台\n\n***\n\n#### pushy uploadIpa \\[ipaFile]\n\n上传 ipa 文件到开放平台。（cli 需 1.24.0 +）\n\n- config: 指定读取的配置文件路径（默认 `update.json`）（需 cli 2.16.1+）\n- appId: 指定上传的目标应用 ID（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n- appKey: 指定上传的目标应用 Key（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n\n***\n\n#### pushy uploadApk \\[apkFile]\n\n上传 apk 文件到开放平台。（cli 需 1.24.0 +）\n\n- config: 指定读取的配置文件路径（默认 `update.json`）（需 cli 2.16.1+）\n- appId: 指定上传的目标应用 ID（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n- appKey: 指定上传的目标应用 Key（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n\n***\n\n#### pushy uploadApp \\[appFile]\n\n上传 app 文件到开放平台。（cli 需 1.24.0 +）\n\n- config: 指定读取的配置文件路径（默认 `update.json`）（需 cli 2.16.1+）\n- appId: 指定上传的目标应用 ID（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n- appKey: 指定上传的目标应用 Key（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n\n***\n\n#### pushy uploadAab \\[aabFile]\n\n上传 aab 文件到开放平台。（cli 需 2.6.0 +）\n\n- config: 指定读取的配置文件路径（默认 `update.json`）（需 cli 2.16.1+）\n- appId: 指定上传的目标应用 ID（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n- appKey: 指定上传的目标应用 Key（会覆盖配置文件中的配置）（需 cli 2.16.1+）\n\n***\n\n#### pushy extractApk \\[aabFile]\n\n从 aab 文件中提取通用 apk 文件。（cli 需 2.6.0+）\n\n用法：`pushy extractApk aab后缀文件 [--output apk文件] [--includeAllSplits] [--splits 分包名列表]`\n\n- output: 指定导出的 apk 文件路径（默认与 aab 同目录同名 `.apk`）\n- includeAllSplits: 提取时包含全部 split（默认仅包含 `base`）\n- splits: 指定额外包含的 split 名称，多个值用英文逗号分隔\n\n示例：\n\n```bash\npushy extractApk android/app/build/outputs/bundle/release/app-release.aab --output app-release.apk\npushy extractApk android/app/build/outputs/bundle/release/app-release.aab --splits config.zh,arm64_v8a\npushy extractApk android/app/build/outputs/bundle/release/app-release.aab --includeAllSplits\n```\n\n***\n\n#### pushy packages\n\n查看已经上传的原生包。这项操作也可以在网页管理端进行。\n\n- platform: ios|android|harmony 对应的平台\n\n***\n\n#### pushy deletePackage\n\n删除已经上传的原生包。\n\n- appId: 要删除的原生包所属的应用 ID\n- packageId: 要删除的原生包 ID\n\n***\n\n#### pushy publish \\[ppkFile]\n\n发布新的热更新版本（ppk 文件）。\n\n- platform: ios|android|harmony 对应的平台\n- name: 当前热更新版本的名字(版本号)\n- description: 当前热更新版本的描述信息，可以对用户进行展示\n- metaInfo: 当前热更新版本的元信息，可以用来保存一些额外信息，具体用法可参考[场景实践](/docs/bestpractice.md#%E5%85%83%E4%BF%A1%E6%81%AFmeta-info%E7%9A%84%E4%BD%BF%E7%94%A8)。\n\n从 v1.46.1 版本开始，支持以下参数（等同于在 调用`pushy publish` 命令后，再连续调用 `pushy update` 命令）：\n\n- packageId: 要绑定的原生包 ID （多选一）\n- packageVersion: 要绑定的原生包版本名（多选一）\n- minPackageVersion: 要绑定的最低原生包版本，大于等于此版本的将逐个绑定（多选一）\n- maxPackageVersion: 要绑定的最高原生包版本，小于等于此版本的将逐个绑定（多选一）\n- packageVersionRange: 要绑定的原生包版本范围（多选一），范围格式遵循 semver 的语法，可参考 [https://quickref.cnxiaobai.com/docs/semver.html](https://quickref.cnxiaobai.com/docs/semver.html) 或 [https://devhints.io/semver](https://devhints.io/semver)\n\n示例：\n\n```bash\n\npushy publish .pushy/output/android.1750423283653.ppk --platform android --name \"1.0.0\" --description \"热更新版本 1.0.0\" --metaInfo '{\"key\": \"value\"}' --packageVersionRange \">=1.0 <3.0\"\n```\n\n***\n\n#### pushy versions\n\n分页列举可用的版本。这项操作也可以在网页管理端进行。\n\n- platform: ios|android|harmony 对应的平台\n\n***\n\n#### pushy deleteVersion\n\n删除已经发布的热更新版本。\n\n- appId: 要删除的热更新版本所属的应用 ID\n- versionId: 要删除的热更新版本 ID\n\n***\n\n#### pushy update\n\n为一个原生包版本绑定发布一个热更新版本。这项操作也可以在网页管理端进行。以下参数中`packageId`，`packageVersion`，`minPackageVersion`，`maxPackageVersion`和`packageVersionRange`中多选一即可。\n\ncli 2.4.0 版本开始可以同时绑定一个全量和一个灰度版本。2.4.0 之前全量和灰度版本会互相覆盖，后设置的生效。\n\n- platform: ios|android|harmony 对应的平台\n- versionId: 要绑定的热更新版本 ID\n- rollout: 灰度发布范围（整数取值 1 - 100，对应 1% - 100% 终端设备覆盖率），默认为 100（需 cli 版本 1.31.0+）\n- dryRun: 是否只进行预览，不进行实际绑定（需 cli 版本 1.45.4+）\n- packageId: 要绑定的原生包 ID （多选一）\n- packageVersion: 要绑定的原生包版本名（多选一，需 cli 版本 1.7.2+）\n- minPackageVersion: 要绑定的最低原生包版本，大于等于此版本的将逐个绑定（多选一，需 cli 版本 1.27.0+）\n- maxPackageVersion: 要绑定的最高原生包版本，小于等于此版本的将逐个绑定（多选一，需 cli 版本 1.27.0+）\n- packageVersionRange: 要绑定的原生包版本范围（多选一，需 cli 版本 1.45.4+），范围格式遵循 semver 的语法，可参考 [https://quickref.cnxiaobai.com/docs/semver.html](https://quickref.cnxiaobai.com/docs/semver.html) 或 [https://devhints.io/semver](https://devhints.io/semver)\n\n示例：\n\n```bash\n❯ pushy update --versionId 211343 --platform android --packageVersionRange \">=1.0 <3.0\" --dryRun\nreact-native-update-cli: 1.45.4 （最新：1.45.4）\nreact-native-update: 10.28.11 （最新：10.28.11）\n以下是 dry-run 模拟运行结果，不会实际执行任何操作：\n已将热更包 211343 绑定到原生版本 1.28.1 (id: 75219)\n已将热更包 211343 绑定到原生版本 1.28 (id: 75184)\n已将热更包 211343 绑定到原生版本 1.5 (id: 73396)\n已将热更包 211343 绑定到原生版本 2.0 (id: 68219)\n已将热更包 211343 绑定到原生版本 1.0 (id: 68158)\n操作完成，共已绑定 5 个原生版本\n```\n",
      "sections": [
        {
          "heading": "命令行工具",
          "level": 1,
          "offset": 0,
          "anchor": "命令行工具",
          "length": 9
        },
        {
          "heading": "安装",
          "level": 3,
          "offset": 9,
          "anchor": "安装",
          "length": 58
        },
        {
          "heading": "命令",
          "level": 3,
          "offset": 67,
          "anchor": "命令",
          "length": 8
        },
        {
          "heading": "pushy bundle",
          "level": 4,
          "offset": 75,
          "anchor": "pushy-bundle",
          "length": 1393
        },
        {
          "heading": "pushy parseIpa \\[ipaFile]",
          "level": 4,
          "offset": 1468,
          "anchor": "pushy-parseipa-ipafile",
          "length": 70
        },
        {
          "heading": "pushy parseApk \\[apkFile]",
          "level": 4,
          "offset": 1538,
          "anchor": "pushy-parseapk-apkfile",
          "length": 70
        },
        {
          "heading": "pushy parseApp \\[appFile]",
          "level": 4,
          "offset": 1608,
          "anchor": "pushy-parseapp-appfile",
          "length": 70
        },
        {
          "heading": "pushy parseAab \\[aabFile]",
          "level": 4,
          "offset": 1678,
          "anchor": "pushy-parseaab-aabfile",
          "length": 70
        },
        {
          "heading": "pushy diff \\[origin]\\[next]",
          "level": 4,
          "offset": 1748,
          "anchor": "pushy-diff-originnext",
          "length": 103
        },
        {
          "heading": "pushy diffFromApk \\[apkFile]\\[next]",
          "level": 4,
          "offset": 1851,
          "anchor": "pushy-difffromapk-apkfilenext",
          "length": 146
        },
        {
          "heading": "pushy diffFromApp \\[appFile]\\[next]",
          "level": 4,
          "offset": 1997,
          "anchor": "pushy-difffromapp-appfilenext",
          "length": 146
        },
        {
          "heading": "pushy diffFromIpa \\[ipaFile]\\[next]",
          "level": 4,
          "offset": 2143,
          "anchor": "pushy-difffromipa-ipafilenext",
          "length": 146
        },
        {
          "heading": "pushy login \\[email]\\[pwd]",
          "level": 4,
          "offset": 2289,
          "anchor": "pushy-login-emailpwd",
          "length": 66
        },
        {
          "heading": "pushy logout",
          "level": 4,
          "offset": 2355,
          "anchor": "pushy-logout",
          "length": 38
        },
        {
          "heading": "pushy me",
          "level": 4,
          "offset": 2393,
          "anchor": "pushy-me",
          "length": 41
        },
        {
          "heading": "pushy createApp",
          "level": 4,
          "offset": 2434,
          "anchor": "pushy-createapp",
          "length": 138
        },
        {
          "heading": "pushy deleteApp \\[appId]",
          "level": 4,
          "offset": 2572,
          "anchor": "pushy-deleteapp-appid",
          "length": 104
        },
        {
          "heading": "pushy apps",
          "level": 4,
          "offset": 2676,
          "anchor": "pushy-apps",
          "length": 92
        },
        {
          "heading": "pushy selectApp \\[appId]",
          "level": 4,
          "offset": 2768,
          "anchor": "pushy-selectapp-appid",
          "length": 87
        },
        {
          "heading": "pushy uploadIpa \\[ipaFile]",
          "level": 4,
          "offset": 2855,
          "anchor": "pushy-uploadipa-ipafile",
          "length": 229
        },
        {
          "heading": "pushy uploadApk \\[apkFile]",
          "level": 4,
          "offset": 3084,
          "anchor": "pushy-uploadapk-apkfile",
          "length": 229
        },
        {
          "heading": "pushy uploadApp \\[appFile]",
          "level": 4,
          "offset": 3313,
          "anchor": "pushy-uploadapp-appfile",
          "length": 229
        },
        {
          "heading": "pushy uploadAab \\[aabFile]",
          "level": 4,
          "offset": 3542,
          "anchor": "pushy-uploadaab-aabfile",
          "length": 228
        },
        {
          "heading": "pushy extractApk \\[aabFile]",
          "level": 4,
          "offset": 3770,
          "anchor": "pushy-extractapk-aabfile",
          "length": 609
        },
        {
          "heading": "pushy packages",
          "level": 4,
          "offset": 4379,
          "anchor": "pushy-packages",
          "length": 94
        },
        {
          "heading": "pushy deletePackage",
          "level": 4,
          "offset": 4473,
          "anchor": "pushy-deletepackage",
          "length": 94
        },
        {
          "heading": "pushy publish \\[ppkFile]",
          "level": 4,
          "offset": 4567,
          "anchor": "pushy-publish-ppkfile",
          "length": 961
        },
        {
          "heading": "pushy versions",
          "level": 4,
          "offset": 5528,
          "anchor": "pushy-versions",
          "length": 93
        },
        {
          "heading": "pushy deleteVersion",
          "level": 4,
          "offset": 5621,
          "anchor": "pushy-deleteversion",
          "length": 100
        },
        {
          "heading": "pushy update",
          "level": 4,
          "offset": 5721,
          "anchor": "pushy-update",
          "length": 1304
        }
      ]
    },
    {
      "slug": "faq",
      "title": "常见问题",
      "path": "pages/docs/faq.mdx",
      "url": "https://pushy.react-native.cn/docs/faq",
      "resourceUri": "pushy-docs://docs/faq",
      "content": "# 常见问题\n\n如果本页面没能回答您的疑问，您可以去[issues 区](https://github.com/reactnativecn/react-native-update/issues)或 QQ 群 729013783 提问，或给我们发[邮件](mailto:hi@charmlot.com)。\n\n### 业务问题\n\n#### 热更新究竟能否上架？\n\n您可能听说过各种说法，但大量实践表明，热更新目前能够顺利上架 AppStore 和其他各种应用市场。唯一需要注意的是，在审核期间请不要发布热更新，不要让审核人员看到各种更新相关的提示和弹窗，即可顺利通过。\n\n***\n\n#### 是否可以在海外使用？\n\n可以。Pushy 本身在国内外都部署有可用的 CDN 节点，能够满足大多数出海应用的热更新分发要求。\n\n如果您的项目对海外合规、数据存放位置、运营主体隔离等方面还有额外要求，则可以考虑使用由不同公司实体独立运营的全球服务 [Cresc](https://cresc.dev/)。\n\n***\n\n#### 哪些修改可以热更新？哪些不能？\n\n我们把对应用的修改分为两类：\n\n- 不可热更新 —— 原生修改，即所有需要`编译`后才能生效的修改：\n  - 任何在 iOS 或者 Android 目录中的修改、增删。\n  - 任何含有原生代码的第三方组件的更新、修改。\n- 可以热更新 —— 非原生修改，即所有`无需编译`，刷新即可生效的修改：\n\n  - js 代码修改，包括第三方纯 js 组件的更新、修改。\n  - 可以在 js 代码中 require/import 的资源文件，例如图片。\n\n需要注意的是，即便资源文件可以热更新，但这些热更新后的资源文件会以`file://`协议的形式提供访问，某些读取资源文件的第三方可能并不支持`file://`协议。\n\n***\n\n#### 热更新成功完成，但是重启后又回滚了是怎么回事？\n\n可以正常更新，但是重启后回滚，一般有两种可能的情况：\n\n- 没有正确[配置 bundleUrl](/docs/getting-started.md#配置-bundle-url)\n- （仅有 v10 以下版本存在此可能性，v10 及更高版本不存在此情况）没有正确[调用 markSuccess](https://v9--pushy-site.netlify.app/docs/integration#%E9%A6%96%E6%AC%A1%E5%90%AF%E5%8A%A8%E5%9B%9E%E6%BB%9A)\n\n如果你确定上述两个步骤都正确无误，请在[issues 区](https://github.com/reactnativecn/react-native-update/issues)给我们留言反馈。\n\n***\n\n#### 热更新报错：“热更新已暂停，原因：编译时间戳与服务器记录不一致。”\n\n当您每次编译产生一个原生包时，其中都会记录一个编译时间戳`buildTime`(可以使用`pushy parseIpa a.ipa`或是`pushy parseApk a.apk`命令来查看)。如果您需要把这个包发给客户并希望使用热更新功能，那么就需要使用 upload 命令来上传到我们的服务器，而服务器端会记录这个包的版本号和编译时间戳以便后续比对记录。\n\n举例来说，假如我已经上传了一个 1.0 版本原生包（`1A包`），但后来由于种种原因我又重新打包了（`1B包`）但没有上传，这两个包有相同的版本号，但会有不同的编译时间戳。此时有几种可能的情况：\n\n1. 我忘记上传过`1A包`，`1B包`没有实质更改且还没有客户安装。此时应当放弃`1B包`，只让客户下载安装`1A包`。\n2. 我忘记上传过`1A包`，又生成了`1B包`且已有客户安装。此时`1A包`客户可以获取热更新，`1B包`客户无法获取热更新。要使两批客户都能获取热更新，要么想办法通知`1B包`客户装回`1A包`，要么重新发布一个`高于1.0`版本的原生包且上传到 pushy 服务器，并删除`1.0`版本的记录，此时`1A包`和`1B包`的客户都会收到版本过期需要下载新版本的通知。\n3. 我知道上传过`1A包`，但我有原生方面的修改需要打了一个新的`1B包`。此时若将`1B包`上传到 pushy 服务器会提示版本验证错误，因为已经存在有相同的`1.0`版本。所以需要首先更改原生版本号再次打包，并`首先`将此新版本上传到服务器，`然后`分发给客户。这样`1.0版本`和`新版本`可以并存且都可以获得热更新。\n\n请谨记，任何时候生成原生包，请 1)先修改原生版本号，2)并上传到 pushy 服务器，才可以正常获得热更新。\n\n:::info\n如果您是`高级版`、`专业版`或`大客户VIP版`客户，也可以考虑在管理后台的应用设置中启用`忽略编译时间戳`。此选项仅对比版本号，不校验时间戳，可提供更宽容的热更策略，但可能消耗更多流量，适合需要管理多个分发渠道的客户。\n:::\n\n***\n\n### 付费问题\n\n#### 我应该选择哪个业务版本呢？\n\n其实完全无需考虑，先从最高配额的`专业版`开始体验（新用户注册验证通过后，将自动获得 7 天`专业版`试用），各个版本的使用流程完全一致！当试用时间到期后，若当前版本的配额不能满足需求，可随时补差价升级到更高配额的版本。升级自动按天数计算差价，绝无额外费用。\n\n目前仅有一项额外的付费专用功能，即`忽略编译时间戳`设置（在管理后台的应用设置中），此选项仅对比版本号，不校验时间戳，可提供更宽容的热更策略，但可能消耗更多流量，适合需要管理多个分发渠道的客户。此功能目前仅支持`高级版`、`专业版`和`大客户VIP版`。\n\n***\n\n#### 我可以试用付费版本先评估一下效果吗？\n\n新用户注册验证通过后，将自动获得 7 天`专业版`试用。如您需要更长的评估时间，可将注册用户名、邮箱、公司（或个人）名称发送至 [hi@charmlot.com](mailto:hi@charmlot.com) ，标题注明“pushy 评估试用延长”，我们会为您酌情延长评估时间。\n试用期间只能选择续费专业版或升级到更高版本，如需购买其他版本，请等待试用期过期，或者联系客服去除试用。\n\n***\n\n#### 配额具体如何计算？\n\n配额主要分为 4 种：\n\n1. 可创建的应用数量，注意 iOS 和 Android 版本记做不同的应用。\n2. 原生包数量及大小，不同应用分开计算。原生包指通过`pushy uploadIpa/uploadApk`命令上传到 Pushy 服务器上作为热更新起始版本的完整 apk/ipa 安装包。\n3. 热更包数量及大小，不同应用分开计算。热更包指通过`pushy bundle`所命令生成的 ppk 文件。注意这不是用户实际下载的更新文件，用户下载的是通过比对生成的增量 diff 文件（比 ppk 文件要小得多）。\n4. 每日总热更查询次数，所有应用累加计算。每次用户打开应用时，会向服务器查询是否有新的热更新。这个查询次数会被计入每日总热更查询次数。当日超过限额次数后，接口将不再返回新的热更新信息，直到次日重置。\n\n> 这里有一些对[包大小的优化建议](/docs/bestpractice.md#优化原生和热更包体积)可供参考。\n\n以上 2、3 条为账户下每个应用单独计算，第 4 条为账户下所有应用累加计算。具体配额和费用由于运营成本可能会有调整，请以[价格表](/pricing.md)页面为准。已购买的用户在续费前不受调整影响。\n\n***\n\n#### 达到配额后还可以正常使用吗？\n\n当应用、原生包、热更包数量达到配额限制后，您将无法执行相应的新增操作。但用户端仍可正常检查更新，下载已发布的更新。您可选择升级到更高配额，或是通过删除已不再使用的应用、原生包、热更包来使数量降低到配额以内，以便继续执行新增操作。\\\n若原生包、热更包大小超过限额，则会导致上传失败。您可以选择升级到更高配额，或是想办法进行[精简优化](/docs/bestpractice.md#优化原生和热更包体积)。\n若当日热更检查次数超过限额，则会导致接口返回空数据。您可以选择升级到更高配额，或是优化热更策略，减少用户端检查更新的频率。\n\n***\n\n#### 我可以单独升级某项配额吗？\n\n如果您需要单独升级某些配额（需要达到高级版以上），请通过邮件 [hi@charmlot.com](mailto:hi@charmlot.com) 或 QQ 34731408 与我们联系，我们将根据您的个性化需求为您定制方案和报价。\n\n***\n\n#### 付费业务到期后不续费，现有应用还能否正常使用？\n\n到期后按免费版额度执行。如您现有应用已超出配额，则必须通过删除操作降低到配额以内，方可执行新的操作（如创建应用，上传热更等），但不影响用户获取之前已发布的热更。\n\n***\n\n#### 如何获取付费的专人技术支持？\n\n请将您的用户名和订单截图发送至 [hi@charmlot.com](mailto:hi@charmlot.com) ，同时提供您希望的技术联系方式（QQ、微信等），核实后会有技术专员添加您为好友。\n\n***\n\n#### 如何开具发票？\n\n请将具体开票需求发送至 [hi@charmlot.com](mailto:hi@charmlot.com) ，并附上注册邮箱和订单截图。我们默认会回复普通电子发票到`注册邮箱`，类目为软件服务。如需指定接受邮箱或手机，请在`邮件正文`中注明。如需要邮寄纸质发票请注明邮寄地址，邮费为到付。\n如邮件发送后三个工作日内未收到回复，请联系 QQ 客服 34731408。\n\n***\n\n#### 可以使用银行转账付款吗？\n\n可以的。请对照[价格表](/pricing.md)，将对应版本的款项转账至：\n\n| 公司名称  | 武汉青罗网络科技有限公司     |\n| ----- | ---------------- |\n| 账号    | 8888888048825564 |\n| 开户行名称 | 浙江网商银行           |\n| 开户地区  | 浙江省杭州市           |\n| 支行名称  | 浙江网商银行股份有限公司     |\n| 联行号   | 323331000001     |\n\n转账完成后请截图发送至 [hi@charmlot.com](mailto:hi@charmlot.com) ，并写明注册邮箱，我们将在一个工作日内开通对应服务。\n\n***\n\n#### 可以按月付款吗？\n\n需要月付的客户请联系 QQ 客服 34731408，月付价格为年付价格 / 8。如标准版当前价格为 960 元每年，则月付价格为 120 元每月。\n月付客户需累计支付满 500 元后才能开具发票。`大客户VIP版`不支持月付。\n\n如仅需增加检查额度，高级版及以上用户可以在管理后台单独购买检查额度。每 10 万次 / 日检查额度为 100 元每月，购买后账号会自动转为定制版，并支持自行续费。\n\n***\n\n#### 大客户方案\n\n如果您的应用有大规模的活跃用户，日均所有应用累计总查询次数超过 100 万次，则建议选择我们专门针对大规模应用优化的`大客户VIP版`。\n`大客户VIP版`在应用数量、原生包数量、热更包数量等方面配额与`专业版`相同。\n\n当前价格如下表：\n\n| 服务版本 | 日均总热更查询次数 | 价格（元/年） |\n| ---- | --------- | ------- |\n| VIP1 | 1000 万次   | 30000   |\n| VIP2 | 5000 万次   | 60000   |\n| VIP3 | 1 亿次      | 120000  |\n| VIP4 | 1 亿次以上    | 请联系客服   |\n\n具体试用和对接请联系 QQ 客服 34731408\n",
      "sections": [
        {
          "heading": "常见问题",
          "level": 1,
          "offset": 0,
          "anchor": "常见问题",
          "length": 153
        },
        {
          "heading": "业务问题",
          "level": 3,
          "offset": 153,
          "anchor": "业务问题",
          "length": 10
        },
        {
          "heading": "热更新究竟能否上架？",
          "level": 4,
          "offset": 163,
          "anchor": "热更新究竟能否上架",
          "length": 126
        },
        {
          "heading": "是否可以在海外使用？",
          "level": 4,
          "offset": 289,
          "anchor": "是否可以在海外使用",
          "length": 163
        },
        {
          "heading": "哪些修改可以热更新？哪些不能？",
          "level": 4,
          "offset": 452,
          "anchor": "哪些修改可以热更新哪些不能",
          "length": 336
        },
        {
          "heading": "热更新成功完成，但是重启后又回滚了是怎么回事？",
          "level": 4,
          "offset": 788,
          "anchor": "热更新成功完成但是重启后又回滚了是怎么回事",
          "length": 391
        },
        {
          "heading": "热更新报错：“热更新已暂停，原因：编译时间戳与服务器记录不一致。”",
          "level": 4,
          "offset": 1179,
          "anchor": "热更新报错热更新已暂停原因编译时间戳与服务器记录不一致",
          "length": 920
        },
        {
          "heading": "付费问题",
          "level": 3,
          "offset": 2099,
          "anchor": "付费问题",
          "length": 10
        },
        {
          "heading": "我应该选择哪个业务版本呢？",
          "level": 4,
          "offset": 2109,
          "anchor": "我应该选择哪个业务版本呢",
          "length": 285
        },
        {
          "heading": "我可以试用付费版本先评估一下效果吗？",
          "level": 4,
          "offset": 2394,
          "anchor": "我可以试用付费版本先评估一下效果吗",
          "length": 224
        },
        {
          "heading": "配额具体如何计算？",
          "level": 4,
          "offset": 2618,
          "anchor": "配额具体如何计算",
          "length": 545
        },
        {
          "heading": "达到配额后还可以正常使用吗？",
          "level": 4,
          "offset": 3163,
          "anchor": "达到配额后还可以正常使用吗",
          "length": 291
        },
        {
          "heading": "我可以单独升级某项配额吗？",
          "level": 4,
          "offset": 3454,
          "anchor": "我可以单独升级某项配额吗",
          "length": 142
        },
        {
          "heading": "付费业务到期后不续费，现有应用还能否正常使用？",
          "level": 4,
          "offset": 3596,
          "anchor": "付费业务到期后不续费现有应用还能否正常使用",
          "length": 117
        },
        {
          "heading": "如何获取付费的专人技术支持？",
          "level": 4,
          "offset": 3713,
          "anchor": "如何获取付费的专人技术支持",
          "length": 126
        },
        {
          "heading": "如何开具发票？",
          "level": 4,
          "offset": 3839,
          "anchor": "如何开具发票",
          "length": 203
        },
        {
          "heading": "可以使用银行转账付款吗？",
          "level": 4,
          "offset": 4042,
          "anchor": "可以使用银行转账付款吗",
          "length": 350
        },
        {
          "heading": "可以按月付款吗？",
          "level": 4,
          "offset": 4392,
          "anchor": "可以按月付款吗",
          "length": 218
        },
        {
          "heading": "大客户方案",
          "level": 4,
          "offset": 4610,
          "anchor": "大客户方案",
          "length": 346
        }
      ]
    },
    {
      "slug": "getting-started",
      "title": "安装配置",
      "path": "pages/docs/getting-started.mdx",
      "url": "https://pushy.react-native.cn/docs/getting-started",
      "resourceUri": "pushy-docs://docs/getting-started",
      "content": "# 安装配置\n\n:::info\n请注意，当前版本的api经过了完全重构，与之前的版本(v10.0以下)不兼容。如果你需要查看之前版本的文档，请点击[这里](https://v9--pushy-site.netlify.app/)\n:::\n\n首先你应该有一个基于 React Native 开发的应用，我们把具有 package.json 的目录叫做你的`应用根目录`。\n如果你还没有初始化应用，请参阅[开始使用 React Native](https://reactnative.cn/docs/getting-started)。\n\n我们假设你已经拥有了开发 React Native 应用的一切环境，包括`Node.js`、`Xcode`、`Android SDK`等等。\n\n:::tip 推荐优先使用 Skill 自动接入\n如果你正在使用支持 Skills 的 AI 编程工具，建议先完成 [安装与使用 Skill](/docs/skills.md)，再让 AI 根据当前工程自动执行依赖安装、原生配置检查与接入改造。本页保留手动步骤，适合定制工程、混编项目或作为 AI 产出校对清单。\n:::\n\n\n### 安装\n在你的项目根目录下运行以下命令（如果你使用 yarn 等其他包管理器，请自行替换命令）：\n\n**React Native**\n\n```bash\n# 先全局安装命令行工具\nnpm i -g react-native-update-cli\n\n# 然后在项目目录中安装热更新模块\nnpm i react-native-update\n\n# 如果没有使用 expo，则进入 iOS 目录安装 iOS 模块\ncd ios && pod install\n\n```\n\n\n**Expo**\n\n```bash\n# 先全局安装命令行工具\nnpm i -g react-native-update-cli\n\n# 然后在项目目录中安装热更新模块\nnpm i react-native-update\n\n# 如果使用 expo, 要求 expo 50 或更高版本，需要使用 prebuild 命令预构建项目\nnpx expo prebuild\n\n# 然后进入 iOS 目录安装 iOS 模块\ncd ios && pod install\n\n```\n:::warning\n注意：如果使用\nexpo，请不要同时安装`expo-updates`，否则会导致热更新功能冲突。另外，expo 51\n版本以下的新架构支持并不完整，可能无法正常使用。如需使用新架构，建议使用尽可能新的\nexpo 版本。\n:::\n\n:::info\n如果下载极慢或者显示网络失败，请设置使用淘宝镜像 `npx nrm use taobao`\n:::\n:::warning\n注意请不要混用`npm/yarn/pnpm`等包管理器及对应的`lock`文件，团队成员请坚持使用同一包管理器，且仅保留统一格式的`lock`文件\n:::\n:::info\n请记得，任意在 ios 和 android 目录下的修改，一定要重新编译（使用 npx\nreact-native run-ios 或 run-android 命令编译，或在 Xcode/Android Studio\n中重新编译）才能生效。\n:::\n### 手动 link\n如果 RN 版本 >= 0.60 则不需要此手动 link 步骤。\n:::warning\n注意：如果是混编 RN\n项目，或monorepo，或任何其他自定义的情况，由于自定义的配置可能不完整或不适应RN的目录结构，导致自动\nlink 的功能可能不能正常工作。此时即便 RN 版本 >= 0.60，你可能也需要执行手动\nlink 操作。\n:::\n#### iOS\n\nRN < 0.60且使用CocoaPods（推荐）\n\n1. 在 ios/Podfile 中添加\n```\npod 'react-native-update', path: '../node_modules/react-native-update'\n```\n2. 在项目的 ios 目录下运行`pod install`\n3. 重新编译\n\nRN < 0.60且不使用CocoaPods\n\n1. 在 XCode 中的 Project Navigator 里,右键点击`Libraries` ➜ `Add Files to [你的工程名]`\n2. 进入`node_modules` ➜ `react-native-update` ➜ `ios 并选中`RCTPushy.xcodeproj\\`\n3. 在 XCode 中的 project navigator 里,选中你的工程,在 `Build Phases` ➜ `Link Binary With Libraries` 中添加 `libRCTPushy.a`、`libz.tbd`、`libbz2.1.0.tbd`\n4. 继续在`Build Settings`里搜索`Header Search Path`，添加`$(SRCROOT)/../node_modules/react-native-update/ios`，勾选`recursive`。\n5. 在`Build Phases`添加一个`New Run Script Phase`运行脚本，内容如下\n```\n#!/bin/bash\nset -x\nDEST=\"../node_modules/react-native-update/ios/\"\ndate +%s > \"$DEST/pushy_build_time.txt\"\n```\n7. 尝试编译一下，顺利的话就会在`../node_modules/react-native-update/ios/`文件夹下面生成一个`pushy_build_time.txt`文件。然后在`Copy Bundle Resources`里把生成的`pushy_build_time.txt`文件添加进去。\n\n#### Android\n\nRN < 0.60 或其他不能自动 link 的情况\n\n1. 在`android/settings.gradle`中添加如下代码:\n\n   ```\n   include ':react-native-update'\n   project(':react-native-update').projectDir = new File(rootProject.projectDir, \t'../node_modules/react-native-update/android')\n   ```\n\n2. 在`android/app/build.gradle`的 dependencies 部分增加如下代码:\n\n   ```\n   implementation project(':react-native-update')\n   ```\n\n3. 打开`android/app/src/main/java/[...]/MainApplication.java`,\n- 在文件开头增加 `import cn.reactnative.modules.update.UpdatePackage;`\n- 在`getPackages()` 方法中增加 `new UpdatePackage()`(注意上一行可能要增加一个逗号)\n\n### 配置 Bundle URL\n如果你使用 `expo` 48 或更高版本，且 `react-native-update` >= 10.28.2，则可以自动配置 bundle url，请直接去往[下一个步骤](#添加测试用的-deep-link)。\n如果你没有使用 `expo`，或 `expo` 版本低于 48，则需要按以下步骤手动配置。\n#### iOS\n在你的 AppDelegate.mm 或 AppDelegate.m 或 AppDelegate.swift 文件（不同 RN 版本可能后缀名不同）中增加如下代码：\n:::warning\n注意：如果你是混编原生的项目，务必注意**不能直接指定 bundleURL 来初始化**\nrootView，需要先使用`initWithDelegate`方法来初始化\nbridge，然后使用`initWithBridge`方法来初始化\nrootView。否则热更新功能可能无法正常工作。\n:::\n\n**Objective-C**\n\n```c\n// ... 其它代码\n#import \"AppDelegate.h\"\n\n#import \"RCTPushy.h\"  // <-- import头文件，注意要放到if条件外面\n\n// 可能项目里有一些条件编译语句，例如有些版本RN自带的flipper\n// #if DEBUG\n// 注意**不要**在这里面引入\"RCTPushy.h\"\n// #import <FlipperKit/FlipperClient.h>\n// ...\n// #endif\n\n\n// rn 版本 >= 0.74 需要修改 bundleURL 方法\n- (NSURL *)bundleURL\n{\n#if DEBUG\n  // 原先DEBUG这里的写法不作修改\n  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@\"index\"];\n#else\n  return [RCTPushy bundleURL];  // <--  把这里非DEBUG的情况替换为热更新bundle\n#endif\n}\n\n\n// rn 版本 < 0.74 需要修改sourceURLForBridge方法\n- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge\n{\n#if DEBUG\n  // 原先DEBUG这里的写法不作修改\n  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@\"index\"];\n#else\n  return [RCTPushy bundleURL];  // <--  把这里非DEBUG的情况替换为热更新bundle\n#endif\n}\n\n```\n\n\n**Swift**\n\n```swift\nimport UIKit\nimport React\nimport React_RCTAppDelegate\nimport ReactAppDependencyProvider\nimport react_native_update    // <-- 在这里引入，需要 pushy v10.22.0+ 版本\n\n\n@main\nclass AppDelegate: RCTAppDelegate {\n  // ... 其他代码\n\n  override func bundleURL() -> URL? {\n#if DEBUG\n    // 原先 DEBUG 这里的写法不作修改(所以 DEBUG 模式下不可热更新)\n    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: \"index\")\n#else\n    RCTPushy.bundleURL()  // <--  把这里非 DEBUG 的情况替换为热更新 bundle\n#endif\n  }\n}\n```\n\n#### Android\n在 MainApplication 中增加如下代码（如果是混编原生的项目或其他原因没有使用 ReactApplication，请[使用此 api 集成](/docs/api.md#updatecontextsetcustominstancemanagerreactinstancemanager-instancemanager))：\n\n**Kotlin（RN 0.82 或以上）**\n\n```kotlin\n// ... 其它代码\n\n// ↓↓↓请注意不要少了这句import\nimport cn.reactnative.modules.update.UpdateContext\n// ↑↑↑\n\nclass MainApplication : Application(), ReactApplication {\n\n  override val reactHost: ReactHost by lazy {\n    getDefaultReactHost(\n      context = applicationContext,\n      packageList =\n        PackageList(this).packages.apply {\n          // Packages that cannot be auto-linked yet can be added manually here, for example:\n          // add(MyReactNativePackage())\n        },\n      // ↓↓↓ 添加下面这一段参数\n      jsBundleFilePath = UpdateContext.getBundleUrl(this),\n      // ↑↑↑\n    )\n  }\n\n  // ...其他代码\n}\n\n\n```\n\n\n**Kotlin（RN 0.81 或以下）**\n\n```kotlin\n// ... 其它代码\n\n// ↓↓↓请注意不要少了这句import\nimport cn.reactnative.modules.update.UpdateContext\n// ↑↑↑\n\nclass MainApplication : Application(), ReactApplication {\n\n  override val reactNativeHost: ReactNativeHost =\n      object : DefaultReactNativeHost(this) {\n\n        // ↓↓↓将下面这一段添加到 DefaultReactNativeHost 内部！\n        override fun getJSBundleFile(): String? {\n          return UpdateContext.getBundleUrl(this@MainApplication)\n        }\n        // ↑↑↑\n\n        // ...其他代码\n      }\n}\n```\n\n\n**Java**\n\n```java\n// ... 其它代码\n\n// ↓↓↓请注意不要少了这句import\nimport cn.reactnative.modules.update.UpdateContext;\n// ↑↑↑\n\npublic class MainApplication extends Application implements ReactApplication {\n\n  private final ReactNativeHost mReactNativeHost =\n    // 老版本 RN 这里可能是 new ReactNativeHost(this)\n    new DefaultReactNativeHost(this) {\n\n    // ↓↓↓将下面这一段添加到 DefaultReactNativeHost 内部！\n    @Override\n    protected String getJSBundleFile() {\n        return UpdateContext.getBundleUrl(MainApplication.this);\n    }\n    // ↑↑↑\n\n    // ...其他代码\n  }\n}\n```\n\n:::info\n请记得，任意在 ios 和 android 目录下的修改，一定要重新编译（npx react-native\nrun-ios 或 run-android 命令编译，或在 Xcode/Android Studio\n中重新编译）才能生效。\n:::\n#### Harmony\n在 `harmony/entry/src/main/cpp/CMakeLists.txt` 中，`add_library(rnoh_app ...)` 之后增加如下配置即可：\n```cmake\nset(PUSHY_CPP_DIR \"${NODE_MODULES}/react-native-update/harmony/pushy/src/main/cpp\")\ntarget_include_directories(rnoh_app PRIVATE \"${PUSHY_CPP_DIR}\")\ntarget_sources(rnoh_app PRIVATE \"${PUSHY_CPP_DIR}/PushyTurboModule.cpp\")\n```\n在 `harmony/entry/src/main/cpp/PackageProvider.cpp` 中增加如下依赖：\n```cpp\n#include \"RNOH/PackageProvider.h\"\n#include \"PushyPackage.h\"\nusing namespace rnoh;\n\nstd::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {\n    return {\n         std::make_shared<PushyPackage>(ctx)\n    };\n}\n```\n在 `harmony/entry/oh-package.json5` 中增加如下依赖：\n```json5\n\"dependencies\": {\n  \"pushy\": \"file:../../node_modules/react-native-update/harmony/pushy.har\",\n}\n```\n在 `harmony/hvigor/hvigor-config.json5` 中增加如下配置：\n```json5\n{\n  dependencies: {\n    pushy: \"file:../../node_modules/react-native-update/harmony\",\n  },\n}\n```\n在 `harmony/entry/hvigorfile.ts` 中增加如下配置：\n```ts\nimport {hapTasks} from '@ohos/hvigor-ohos-plugin';\nimport {reactNativeUpdatePlugin} from 'pushy/hvigor-plugin';\n\nexport default {\n  system: hapTasks /* Built-in plugin of Hvigor. It cannot be modified. */,\n  plugins: [\n    reactNativeUpdatePlugin(),\n  ] /* Custom plugin to extend the functionality of Hvigor. */,\n};\n```\n在 `harmony/entry/src/main/ets/RNPackagesFactory.ts` 代码如下：\n```ts\nimport type {\n  RNPackageContext,\n  RNPackage,\n} from \"@rnoh/react-native-openharmony/ts\";\nimport { PushyPackage } from \"pushy/ts\";\n\nexport function createRNPackages(ctx: RNPackageContext): RNPackage[] {\n  return [new PushyPackage(ctx)];\n}\n```\n在 `harmony/entry/src/main/ets/pages/Index.ets` 中增加如下代码：\n```ts\n// ... 其它代码\n\n// ↓↓↓请注意不要少了这句import\nimport { PushyFileJSBundleProvider } from 'pushy/src/main/ets/PushyFileJSBundleProvider';\n// ↑↑↑\n\n@Entry\n@Component\nstruct Index {\n  @StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined\n  @State shouldShow: boolean = false\n\n  aboutToAppear(): void {\n    this.shouldShow = true\n  }\n\n  onBackPress(): boolean | undefined {\n    // NOTE: this is required since `Ability`'s `onBackPressed` function always\n    // terminates or puts the app in the background, but we want Ark to ignore it completely\n    // when handled by RN\n    this.rnohCoreContext!.dispatchBackPress()\n\n    // this.preferences = preferences.getPreferencesSync(this.context, {name:'update'});\n    return true\n  }\n\n  build() {\n    Column() {\n      if (this.rnohCoreContext && this.shouldShow) {\n        RNApp({\n          // ... 其他代码\n          jsBundleProvider: new TraceJSBundleProviderDecorator(\n            new AnyJSBundleProvider([\n              // MetroJSBundleProvider.fromServerIp('127.0.0.1'),\n              // new ResourceJSBundleProvider(rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),\n              // ↓↓↓将下面这一段添加到 AnyJSBundleProvider 内部！\n              new PushyFileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),\n\n              // 注意无论是否使用 hermes 格式，请保持 bundle 文件名为 bundle.harmony.js\n              new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')\n            ]),\n            this.rnohCoreContext.logger),\n        })\n      }\n    }\n    .height('100%')\n    .width('100%')\n  }\n}\n```\n:::info\n请记得，任意在 `ios`、`android` 或 `harmony` 目录下的修改，都需要重新编译后才能生效。\n:::\n### 覆盖 android 的 onCreate\n如果你有安装 `react-native-screens` (使用 `react-navigation` 一般都会要求安装)，则安卓端在热更后重启可能会白屏。此时需要在 Android 的 `MainActivity` 中设置 `RNScreensFragmentFactory`，以确保 Fragment 恢复流程一致，避免崩溃。注意不要把这段覆盖写在 `MainActivityDelegate` 里，而是直接放在 `MainActivity` 中。\n更多说明可以参考 `react-native-screens` 的 [readme](https://github.com/software-mansion/react-native-screens?tab=readme-ov-file#android)\n\n**Kotlin**\n\n```kotlin\n// android/app/src/main/java/[...]/MainActivity.kt\nimport android.os.Bundle\nimport com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory\n\nclass MainActivity : ReactActivity() {\n  // ...其他代码\n\n  override fun onCreate(savedInstanceState: Bundle?) {\n    supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()\n    super.onCreate(savedInstanceState)\n    // 如果你用的版本比较老，没有 RNScreensFragmentFactory，则使用下面的写法\n    // super.onCreate(null)\n  }\n}\n```\n\n\n**Java**\n\n```java\n// android/app/src/main/java/[...]/MainActivity.java\nimport android.os.Bundle;\nimport com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory;\n\npublic class MainActivity extends ReactActivity {\n  // ...其他代码\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    getSupportFragmentManager().setFragmentFactory(new RNScreensFragmentFactory());\n    super.onCreate(savedInstanceState);\n    // 如果你用的版本比较老，没有 RNScreensFragmentFactory，则使用下面的写法\n    // super.onCreate(null);\n  }\n}\n```\n\n### 添加测试用的 Deep Link\n此步骤可以极其便利的在和用户完全一致的环境中安全地测试热更新，无需额外的设备注册步骤，也不影响现有的热更新流程。\n\n**Android**\n\n在 `android/app/src/main/AndroidManifest.xml` 中添加如下代码：\n```xml\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n  <application>\n\n    <!-- 其他代码 -->\n\n    <!-- ↓↓↓ 注意 activity 的 launchMode 需要设置为 singleTask -->\n    <activity\n      android:launchMode=\"singleTask\">\n      <!-- ↑↑↑ -->\n\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\" />\n        <category android:name=\"android.intent.category.LAUNCHER\" />\n      </intent-filter>\n\n      <!-- ↓↓↓ 添加如下 intent-filter， 注意和上面的 intent-filter 是不同的 action 和 category -->\n      <intent-filter>\n        <action android:name=\"android.intent.action.VIEW\" />\n        <category android:name=\"android.intent.category.DEFAULT\" />\n        <category android:name=\"android.intent.category.BROWSABLE\" />\n        <!-- 添加你自定义的 scheme -->\n        <data android:scheme=\"你自定义的协议名，请不要和常见协议名或其他app冲突\" />\n      </intent-filter>\n      <!-- ↑↑↑ -->\n\n    </activity>\n  </application>\n</manifest>\n```\n\n\n**iOS**\n\n请参考 [React Native 的文档](https://reactnative.cn/docs/next/linking#%E5%90%AF%E7%94%A8-deep-links)\n\n***\n在完成上述配置并编译，并且日后上传了热更包，在后台能看到热更版本之后，你就可以将鼠标移到网页后台的二维码图标上，如下图所示：\n<img alt=\"Deep Link二维码\" src={image0} />\n勾选`使用 Deep Link`选项，在后面的输入框中填入你自定义的协议名，在二维码刷新后，即可在设备上直接使用系统相机或扫码功能（注意不能使用微信扫码）来扫描，即可自动触发热更检查功能。\n第一次扫码后可能只是触发服务器任务队列，会弹出提示 10 秒后重新扫码，此时会获得热更包，触发内置或自定义的更新策略。\n注意如果你使用自定义的更新策略，请务必从`useUpdate()`中获取`updateInfo`，而不要依赖`checkUpdate`方法的返回值，否则扫码不会有后续动作。\n### 禁用 android 的 crunch 优化\nandroid 会在生成 apk 时自动对 png 图片进行压缩，此操作既耗时又影响增量补丁的生成。为了保证补丁能正常生成，您需要在`android/app/build.gradle`中关闭此操作：\n```groovy\n// 在 android/app/build.gradle 文件中\n\nandroid {\n    // ...\n    signingConfigs {\n      // ...\n    }\n    buildTypes {\n        release {\n            // ...\n            // 添加下面这行以禁用crunch\n            crunchPngs false\n        }\n    }\n}\n\n```\n### 禁用 aab 包的资源分割（如果你需要在 Google Play 上架）\n如果你需要打 aab 包以在 Google Play 上架，且 react-native-update 版本 低于 10.36.0，则需要禁用 aab 包的资源分割。若已升级到 10.36.0+ 版本，则不需要此设置。\n```groovy\n// 在 android/app/build.gradle 文件中\n\nandroid {\n    bundle {\n        density {\n            // 请禁用资源分割\n            // 否则热更后可能遇到图片无法显示的问题\n            // v10.36.0 版本之后不需要此设置\n            enableSplit = false\n        }\n    }\n}\n\n```\n### 登录与创建应用\n首先请在 [https://pushy-admin.reactnative.cn](https://pushy-admin.reactnative.cn) 注册帐号，然后在你的项目根目录下运行以下命令：\n```bash\n$ pushy login\nemail: <输入你的注册邮箱>\npassword: <输入你的密码>\n```\n这会在项目文件夹下创建一个`.update`文件，注意不要把这个文件上传到 Git 等 CVS 系统上。你可以在`.gitignore`末尾增加一行`.update`来忽略这个文件。\n登录之后可以创建应用。注意 iOS 平台、安卓平台、鸿蒙平台需要分别创建：\n```bash\n$ pushy createApp --platform ios\nApp Name: <输入应用名字>\n$ pushy createApp --platform android\nApp Name: <输入应用名字>\n$ pushy createApp --platform harmony\nApp Name: <输入应用名字>\n```\n:::info\n应用的名字可以相同，这没有关系。\n:::\n如果你已经在网页端或者其它地方创建过应用，也可以直接选择应用：\n```bash\n$ pushy selectApp --platform ios\n1) 鱼多多(ios)\n2) 招财旺(ios)\n\nTotal 2 ios apps\nEnter appId: <输入应用前面的编号>\n```\n选择或者创建过应用后，你将可以在文件夹下看到`update.json`文件，其内容类似如下形式：\n```bash\n{\n    \"ios\": {\n        \"appId\": 1,\n        \"appKey\": \"<一串随机字符串>\"\n    },\n    \"android\": {\n        \"appId\": 2,\n        \"appKey\": \"<一串随机字符串>\"\n    },\n    \"harmony\": {\n        \"appId\": 3,\n        \"appKey\": \"<一串随机字符串>\"\n    }\n}\n```\n你可以安全的把`update.json`上传到 Git 等 CVS 系统上，与你的团队共享这个文件，它不包含任何敏感信息。当然，他们在使用任何功能之前，都必须首先输入`pushy login`进行登录。\n至此应用的创建/选择就已经成功了。下一步，你需要给代码添加相应的功能，请参阅[代码集成](/docs/integration.md)。\n",
      "sections": [
        {
          "heading": "安装配置",
          "level": 1,
          "offset": 0,
          "anchor": "安装配置",
          "length": 502
        },
        {
          "heading": "安装",
          "level": 3,
          "offset": 502,
          "anchor": "安装",
          "length": 79
        },
        {
          "heading": "先全局安装命令行工具",
          "level": 1,
          "offset": 581,
          "anchor": "先全局安装命令行工具",
          "length": 47
        },
        {
          "heading": "然后在项目目录中安装热更新模块",
          "level": 1,
          "offset": 628,
          "anchor": "然后在项目目录中安装热更新模块",
          "length": 45
        },
        {
          "heading": "如果没有使用 expo，则进入 iOS 目录安装 iOS 模块",
          "level": 1,
          "offset": 673,
          "anchor": "如果没有使用-expo则进入-ios-目录安装-ios-模块",
          "length": 81
        },
        {
          "heading": "先全局安装命令行工具",
          "level": 1,
          "offset": 754,
          "anchor": "先全局安装命令行工具-1",
          "length": 47
        },
        {
          "heading": "然后在项目目录中安装热更新模块",
          "level": 1,
          "offset": 801,
          "anchor": "然后在项目目录中安装热更新模块-1",
          "length": 45
        },
        {
          "heading": "如果使用 expo, 要求 expo 50 或更高版本，需要使用 prebuild 命令预构建项目",
          "level": 1,
          "offset": 846,
          "anchor": "如果使用-expo-要求-expo-50-或更高版本需要使用-prebuild-命令预构建项目",
          "length": 71
        },
        {
          "heading": "然后进入 iOS 目录安装 iOS 模块",
          "level": 1,
          "offset": 917,
          "anchor": "然后进入-ios-目录安装-ios-模块",
          "length": 455
        },
        {
          "heading": "手动 link",
          "level": 3,
          "offset": 1372,
          "anchor": "手动-link",
          "length": 184
        },
        {
          "heading": "iOS",
          "level": 4,
          "offset": 1556,
          "anchor": "ios",
          "length": 929
        },
        {
          "heading": "Android",
          "level": 4,
          "offset": 2485,
          "anchor": "android",
          "length": 569
        },
        {
          "heading": "配置 Bundle URL",
          "level": 3,
          "offset": 3054,
          "anchor": "配置-bundle-url",
          "length": 173
        },
        {
          "heading": "iOS",
          "level": 4,
          "offset": 3227,
          "anchor": "ios-1",
          "length": 1543
        },
        {
          "heading": "Android",
          "level": 4,
          "offset": 4770,
          "anchor": "android-1",
          "length": 1997
        },
        {
          "heading": "Harmony",
          "level": 4,
          "offset": 6767,
          "anchor": "harmony",
          "length": 3381
        },
        {
          "heading": "覆盖 android 的 onCreate",
          "level": 3,
          "offset": 10148,
          "anchor": "覆盖-android-的-oncreate",
          "length": 1399
        },
        {
          "heading": "添加测试用的 Deep Link",
          "level": 3,
          "offset": 11547,
          "anchor": "添加测试用的-deep-link",
          "length": 1512
        },
        {
          "heading": "禁用 android 的 crunch 优化",
          "level": 3,
          "offset": 13059,
          "anchor": "禁用-android-的-crunch-优化",
          "length": 370
        },
        {
          "heading": "禁用 aab 包的资源分割（如果你需要在 Google Play 上架）",
          "level": 3,
          "offset": 13429,
          "anchor": "禁用-aab-包的资源分割如果你需要在-google-play-上架",
          "length": 384
        },
        {
          "heading": "登录与创建应用",
          "level": 3,
          "offset": 13813,
          "anchor": "登录与创建应用",
          "length": 1108
        }
      ]
    },
    {
      "slug": "integration",
      "title": "代码集成",
      "path": "pages/docs/integration.mdx",
      "url": "https://pushy.react-native.cn/docs/integration",
      "resourceUri": "pushy-docs://docs/integration",
      "content": "# 代码集成\n\n:::info\n请注意，当前版本的api经过了完全重构，与之前的版本(v10.0以下)不兼容。如果你需要查看之前版本的文档，请点击[这里](https://v9--pushy-site.netlify.app/)\n:::\n\n安装配置完成后，确定应用编译顺利通过，下面我们来进行代码集成。\n\n:::tip 推荐做法\n优先使用 [安装与使用 Skill](/docs/skills.md) 让 AI 自动完成 `UpdateProvider` 包裹、客户端初始化与常见策略配置。本页保留为手动接入参考，也适合用来校对 AI 生成的改动。\n:::\n\n### 获取 appKey\n\n检查更新时必须提供你的`appKey`，这个值保存在`update.json`中（使用`pushy createApp`或`pushy selectApp`命令后会自动生成），并且根据平台不同而不同。你可以用如下的代码获取`appKey`：\n\n```javascript\nimport { Platform } from \"react-native\";\n\nimport _updateConfig from \"./update.json\";\nconst { appKey } = _updateConfig[Platform.OS];\n```\n\n也可以在网页端的应用设置中查看应用的 appKey。\n\n### 初始化服务\n\n```js\nimport { UpdateProvider, Pushy } from \"react-native-update\";\n\n// 唯一必填参数是appKey，其他选项请参阅 api 文档\nconst pushyClient = new Pushy({\n  appKey,\n  // 注意，默认情况下，在开发环境中不会检查更新\n  // 如需在开发环境中调试更新，请设置debug为true\n  // 但即便打开此选项，也仅能检查、下载热更，并不能实际应用热更。实际应用热更必须在release包中进行。\n  // debug: true\n});\n\n// 在根组件外加上 UpdateProvider 后导出\nexport default function Root() {\n  // 注意，在使用 UpdateProvider 的当前组件中，无法直接调用 useUpdate\n  // 只有当前组件的子组件才能调用 useUpdate\n  return (\n    <UpdateProvider client={pushyClient}>\n      {/* ↓ 整个应用的根组件放到 UpdateProvider 中 */}\n      <App />\n    </UpdateProvider>\n  );\n}\n```\n\n如没有特别的自定义需求，那么到此热更新已经可以开始正常运作（如需在应用内执行 apk 更新，还需配置[安装权限](/docs/api.md#async-function-downloadandinstallapkurl)）。在默认的配置下，在 App 启动，以及从后台切换到前台时会触发更新检查，弹出提示的内容也固定。\n\n如需简单调整检查和更新策略，可参考以下内置的策略参数：\n\n#### checkStrategy（检查更新策略）\n\n用于控制自动检查更新的触发时机：\n\n- `\"both\"`（默认值）：在 App 启动时和从后台切换到前台时都会检查更新\n- `\"onAppStart\"`：仅在 App 启动时检查更新\n- `\"onAppResume\"`：仅在 App 从后台切换到前台时检查更新\n- `null`：不自动检查更新，必须手动调用 `checkUpdate` 方法（需 v10.4.2+ 版本）\n\n示例：\n\n```js\nconst pushyClient = new Pushy({\n  appKey,\n  checkStrategy: \"onAppStart\", // 仅在启动时检查\n});\n```\n\n#### updateStrategy（更新应用策略）\n\n用于控制检测到更新后的自动下载和应用行为：\n\n- `\"alwaysAlert\"`：调试环境（`__DEV__`）的默认值，使用系统 alert 提示热更，并在有报错时弹出提示\n- `\"alertUpdateAndIgnoreError\"`：生产环境的默认值，有热更时使用系统 alert 提示，但不弹出报错提示\n- `\"silentAndNow\"`：自动静默下载并立即应用热更（会立即重启应用）\n- `\"silentAndLater\"`：自动静默下载，但仅在用户杀掉 App 后重启时应用更新\n- `null`：不自动下载和应用更新，需完全自定义热更界面\n\n示例：\n\n```js\nconst pushyClient = new Pushy({\n  appKey,\n  updateStrategy: \"silentAndLater\", // 静默下载，下次启动时更新\n});\n```\n\n检查策略和更新策略是一个完整更新流水线的上下游，两者可以独立开关，自由搭配，以便不同程度的介入检查频率的控制，或是下载相关的界面交互。\n\n:::tip\n如果应用接入了 Sentry profiling、性能采样或类似会在原生线程中持续工作的 SDK，并且使用 `\"silentAndNow\"`、手动调用 `switchVersion()` 或 `restartApp()` 这类立即重启路径，建议升级 `react-native-update` 到 v10.42.2+，并配置 [`beforeReload`](/docs/api.md) 在重启前完成停止采样和 flush。\n:::\n\n下面的章节提供了一个自定义更新策略和界面交互的参考实现。\n\n### 自定义更新界面\n\n默认配置下，pushy 会以系统 alert 的形式来弹出更新提示，如需自定义更新界面，首先请关闭默认的 updateStrategy 更新策略，并打开 debug 选项以便调试：\n\n```diff\nconst pushyClient = new Pushy({\n  appKey,\n+  updateStrategy: null,\n+  debug: true,\n});\n```\n\n所有更新相关的数据可以通过一个单一的[`useUpdate()`](/docs/api.md#useupdate)hook 函数来获取，然后可以根据其提供的数据来自行渲染自定义的界面，如下面的例子：\n\n```js\nimport { Text, View, TouchableOpacity } from 'react-native';\nimport { useUpdate } from \"react-native-update\";\nimport { Icon, PaperProvider, Snackbar, Banner } from \"react-native-paper\";\nfunction App() {\n  const {\n    client,\n    checkUpdate,\n    downloadUpdate,\n    switchVersionLater,\n    switchVersion,\n    updateInfo,\n    packageVersion,\n    currentHash,\n    progress: { received, total } = {},\n  } = useUpdate();\n  const [showUpdateBanner, setShowUpdateBanner] = useState(false);\n  const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(false);\n  const snackbarVisible = showUpdateSnackbar && updateInfo?.update;\n  return (\n    <View style={styles.container}>\n      <Text>\n        更新下载进度：{received} / {total}\n      </Text>\n      <TouchableOpacity\n        onPress={() => {\n          checkUpdate();\n          setShowUpdateSnackbar(true);\n        }}\n      >\n        <Text>点击这里检查更新</Text>\n      </TouchableOpacity>\n      {snackbarVisible && (\n        <Snackbar\n          visible={true}\n          onDismiss={() => {\n            setShowUpdateSnackbar(false);\n          }}\n          action={{\n            label: \"更新\",\n            onPress: async () => {\n              setShowUpdateSnackbar(false);\n              if (await downloadUpdate()) {\n                setShowUpdateBanner(true);\n              }\n            },\n          }}\n        >\n          <Text>有新版本({updateInfo.name})可用，是否更新？</Text>\n        </Snackbar>\n      )}\n      <Banner\n        style={{ width: \"100%\", position: \"absolute\", top: 0 }}\n        visible={showUpdateBanner}\n        actions={[\n          {\n            label: \"立即重启\",\n            onPress: switchVersion,\n          },\n          {\n            label: \"下次再说\",\n            onPress: () => {\n              switchVersionLater();\n              setShowUpdateBanner(false);\n            },\n          },\n        ]}\n        icon={({ size }) => (\n          <Icon name=\"checkcircleo\" size={size} color=\"#00f\" />\n        )}\n      >\n        更新已完成，是否立即重启？\n      </Banner>\n    </View>\n  );\n}\n```\n\n其中`checkUpdate`方法可以用来手动触发更新检查。虽然这个方法会返回[`updateInfo`](/docs/api.md#async-function-checkupdate)（仅限 v10.26.0+ 版本），但我们仍然推荐优先使用`useUpdate()`来获取`updateInfo`。\n\n:::info\n依赖`useUpdate()`而不是`checkUpdate`来获取`updateInfo`，这样做虽然一开始可能觉得不太直观，但可以将**检查逻辑**和**更新逻辑**完全解耦，使更新流程上的各个组件不需要互相依赖和影响。\n比如检查更新的按钮只管调用`checkUpdate`，某个显示小红点的组件只管从 `useUpdate()` 中获取`updateInfo`，而主要的下载流程可以写一个单独的`useEffect`，这几者之间并不需要考虑先后顺序、组件层级或者传递数据。\n又比如你可能在多处都有检查更新的调用，比如 app 启动时、前后台切换时，又或者使用 deeplink 和扫码，这些不同的检查逻辑也不用重复去实现后续的更新逻辑。\n:::\n\n`updateInfo` 有三种情况：\n\n1. `{expired: true}`：该应用原生包已过期（三种情况：1. 主动设置为过期状态，2. 主动删除，3. 从未上传），开发者应该在 pushy 的管理后台添加一个更新下载链接，并自行提示用户下载。如需在应用内执行 apk 更新，还需配置[安装权限](/docs/api.md#async-function-downloadandinstallapkurl)。\n\n2. `{upToDate: true}`：当前已经更新到最新，无需进行更新。\n\n3. `{update: true}`：当前有新版本可以更新。info 的`name`、`description`字段可以用于提示用户，而`metaInfo`字段则可以根据你的需求自定义其它属性(如是否静默更新、是否强制更新等等)，具体用法可参考[场景实践](/docs/bestpractice.md#%E5%85%83%E4%BF%A1%E6%81%AFmeta-info%E7%9A%84%E4%BD%BF%E7%94%A8)。另外还有几个字段，包含了补丁包的下载地址等。 pushy 会首先尝试耗费流量更少的更新方式。\n\n当返回的`updateInfo`中`update`字段为 true 时，即可调用`downloadUpdate`方法来下载更新，此时可以获取到下载的进度数据`progress`。下载完成后（注意!不可依赖`progress`来判断下载完成，必须要`await downloadUpdate()`之后）可以调用`switchVersion`来立即重启更新，也可以使用`switchVersionLater`来标记下次启动时更新。\n\n### 统计数据\n\n初始化 Pushy 客户端时可以传入自定义的 logger 函数，其中可以自己记录日志或上报统计数据，比如下面的例子使用 Google Analytics 来上报事件：\n\n```ts\nimport { getAnalytics, logEvent } from \"firebase/analytics\";\nconst analytics = getAnalytics();\n\nconst pushyClient = new Pushy({\n  appKey,\n  logger: ({ type, data }) => {\n    logEvent(analytics, \"pushy_\" + type, data);\n  },\n});\n```\n\n以上提及的所有 api 的说明文档可在[这里](/docs/api.md)查看。还有一些其他常见的场景可以参考[场景实践](/docs/bestpractice.md)。\n\n现在，你的应用已经可以通过 pushy 服务检查版本并进行更新了。下一步，你可以开始尝试发布应用包和版本，请参阅[发布热更新](/docs/publish.md)。\n",
      "sections": [
        {
          "heading": "代码集成",
          "level": 1,
          "offset": 0,
          "anchor": "代码集成",
          "length": 280
        },
        {
          "heading": "获取 appKey",
          "level": 3,
          "offset": 280,
          "anchor": "获取-appkey",
          "length": 316
        },
        {
          "heading": "初始化服务",
          "level": 3,
          "offset": 596,
          "anchor": "初始化服务",
          "length": 770
        },
        {
          "heading": "checkStrategy（检查更新策略）",
          "level": 4,
          "offset": 1366,
          "anchor": "checkstrategy检查更新策略",
          "length": 320
        },
        {
          "heading": "updateStrategy（更新应用策略）",
          "level": 4,
          "offset": 1686,
          "anchor": "updatestrategy更新应用策略",
          "length": 746
        },
        {
          "heading": "自定义更新界面",
          "level": 3,
          "offset": 2432,
          "anchor": "自定义更新界面",
          "length": 3536
        },
        {
          "heading": "统计数据",
          "level": 3,
          "offset": 5968,
          "anchor": "统计数据",
          "length": 504
        }
      ]
    },
    {
      "slug": "intro",
      "title": "Pushy - 专业的热更新解决方案",
      "path": "pages/docs/intro.mdx",
      "url": "https://pushy.react-native.cn/docs/intro",
      "resourceUri": "pushy-docs://docs/intro",
      "content": "# Pushy - 专业的热更新解决方案\n\n:::info\n**现已全面支持鸿蒙系统以及 React Native 新架构！**\n:::\n\nPushy (npm 包名: [`react-native-update`](https://www.npmjs.com/package/react-native-update)\n [![npm version](https://badge.fury.io/js/react-native-update.svg)](https://www.npmjs.com/package/react-native-update)) 是面向 React Native 应用提供的**专业热更新服务**\n，由 [React Native 中文网](https://reactnative.cn)\n独家研发并持续维护。我们致力于为全球 React Native 开发者提供最快速、最稳定、最经济的热更新体验。\n:::tip 推荐接入方式\n如果你正在使用支持 Skills 的 AI 编程工具，推荐优先阅读 [安装与使用 Skill](/docs/skills.md)，先安装 `react-native-update` Skill，再让 AI 根据你的项目结构自动完成大部分接入改动。手动文档更适合混编项目、monorepo 或需要逐项校对的场景。\n:::\n\n## 🚀 为什么选择 Pushy？\n\n### 极致的更新体验\n\n- **🎯 超小更新包**：基于 bsdiff/hdiff 算法的增量更新，通常只需**几十至几百 KB**（相比其他全量更新服务的几十 MB，节省流量 99%+），并针对 Hermes 字节码做了**独家深度优化**（见下方实测数据）\n- **⚡️ 极速分发**：国内用户使用阿里云高速 CDN，国外用户智能分流至 Cloudflare，在各地区都使用覆盖率最好的 CDN 服务商，更新成功率高达 **99.9%+**\n- **🛡️ 安全可靠**：内置崩溃回滚机制，出现问题自动回退，确保应用稳定运行\n- **🌐 全平台覆盖**：完美支持 <span style={{whiteSpace: 'nowrap'}}><AppleOutlined style={{fontSize: '16px', color: '#555', marginRight: '6px'}} /> **iOS**</span> <span style={{whiteSpace: 'nowrap'}}><AndroidOutlined style={{fontSize: '16px', color: '#3DDC84', marginRight: '6px'}} /> **Android**</span>, 且业内独家第一方支持 <span style={{whiteSpace: 'nowrap'}}><HarmonyOSOutlined style={{fontSize: '16px', color: '#000', marginRight: '6px'}} /> **鸿蒙（HarmonyOS）**</span>\n- **🔍 深耕 React Native 生态**：\n  - ✅ **Expo** - 无缝集成\n  - ✅ **新架构** - 第一时间跟进支持\n  - ✅ **Hermes** - 完整支持字节码格式\n- **📖 核心逻辑完全开源**：客户端、命令行工具以及管理界面代码都[完全开源](https://github.com/reactnativecn)，接受社区审计，代码透明可信，你可以随时迁移到自建服务器或其他服务\n- **📊 发布全程可控**：后台自带**版本统计、按比例灰度发布、健康度监控**，每次发布的覆盖进度和稳定性一目了然，无需自建数据链路\n- **🔒 数据安全**：可以自行插入数据埋点，完全掌控自己的数据\n- **💬 专业人工支持**：提供付费专人技术支持，有问必答，确保你的应用稳定运行\n- **🏆 多年稳定运营**：从 2016 年开始运营至今，服务众多知名企业和应用\n\n### 📊 实测数据：针对 Hermes 深度优化的增量算法\n\nHermes 字节码中充满\"偏移量表\"——JS 一处小改动会让其后所有偏移整体位移，普通二进制 diff 会因此把补丁体积急剧放大。Pushy 独家引入了 **HBC（Hermes 字节码）结构感知的可逆变换**，从根源上消除这种放大。\n\n在真实 React Native 0.86 应用上的实测结果（字节码约 4.4 MB，[评测代码与数据完全公开可复现](https://github.com/sunnylqm/hbc-diff-benchmark)）：\n\n| 迭代场景            | 全量更新   | 传统增量（bsdiff） | Pushy 增量（Hermes 专项优化） |\n| --------------- | ------ | ------------ | --------------------- |\n| 修改一行文案          | 1.9 MB | 93.7 KB      | **63.5 KB**（再省 32%）   |\n| 新增小功能（约 60 行）   | 1.9 MB | 411.6 KB     | **285.5 KB**（再省 31%）  |\n| 新增中等功能（约 300 行） | 1.9 MB | 551.6 KB     | **398.4 KB**（再省 28%）  |\n\n- 相比全量更新节省 **95% 以上**的流量，相比传统 bsdiff 增量**再省约 30%**\n- **改动越小、收益越大**——恰好是热修复中最高频的场景\n- 安全兜底：变换前对字节码结构做全量校验，任何不匹配自动回退到普通增量路径；Hermes 版本升级时**客户端零改动**自动兼容\n\n## 💰 性价比之选\n\n相比同类服务，Pushy 不仅功能更强大，价格也更亲民：\n\n| 对比项       | Pushy        | Expo Update |\n| --------- | ------------ | ----------- |\n| **起步价**   | ¥66/月        | \\~¥136/月    |\n| **流量计费**  | 不单独计费        | 超出需额外付费     |\n| **更新包大小** | 几十至几百 KB（增量） | 几十 MB（全量）   |\n| **国内速度**  | ⭐⭐⭐⭐⭐ 极快     | ⭐⭐ 较慢       |\n| **鸿蒙支持**  | ✅ 支持         | ❌ 不支持       |\n\n:::warning\n**注意**：Microsoft App Center（Code Push）已于 2025 年 3 月 31 日正式停止服务。如果你正在使用 Code Push，现在正是迁移到 Pushy 的最佳时机！\n:::\n\n使用 Pushy 后，你的团队将获得：\n\n- ✅ **节省 90% 以上的流量成本**\n- ✅ **发版速度提升 10 倍以上**（无需等待应用商店审核）\n- ✅ **Bug 修复时间缩短至分钟级**\n- ✅ **用户更新率提升至 99%+**（相比应用商店的自然更新率）\n- ✅ **开发迭代更加灵活自由**\n\n### 简单易用\n\n```js\n// 三行代码即可集成\nimport { UpdateProvider, Pushy } from \"react-native-update\";\n\nconst pushyClient = new Pushy({ appKey });\n\n<UpdateProvider client={pushyClient}>\n  <App />\n</UpdateProvider>\n```\n\n内置多种更新策略，开箱即用：\n\n- 🔕 **静默更新** - 后台自动下载，用户无感知\n- 💬 **提示更新** - 友好的系统弹窗提醒\n- ⚙️ **自定义策略** - 完全可控的更新流程\n\n## 🚦 开始使用\n\n推荐按下面的顺序完成接入：\n\n1. **[安装与使用 Skill](/docs/skills.md)** - 推荐先让 AI 自动完成接入改造\n2. **[安装配置](/docs/getting-started.md)** - 补齐依赖与原生侧配置\n3. **[代码集成](/docs/integration.md)** - 校对或手动定制 `UpdateProvider` 接入\n4. **[发布更新](/docs/publish.md)** - 一条命令推送新版本\n\n:::info\n不确定是否适合你的项目？查看[常见问题](/docs/faq.md)或者可以去[issues 区](https://github.com/reactnativecn/react-native-update/issues)或 QQ 群 729013783 提问，或给我们发[邮件](mailto:hi@charmlot.com)。\n:::\n\n立即开始使用 Pushy，让你的 React Native 应用拥有极致的热更新体验！\n",
      "sections": [
        {
          "heading": "Pushy - 专业的热更新解决方案",
          "level": 1,
          "offset": 0,
          "anchor": "pushy-专业的热更新解决方案",
          "length": 591
        },
        {
          "heading": "🚀 为什么选择 Pushy？",
          "level": 2,
          "offset": 591,
          "anchor": "为什么选择-pushy",
          "length": 20
        },
        {
          "heading": "极致的更新体验",
          "level": 3,
          "offset": 611,
          "anchor": "极致的更新体验",
          "length": 1125
        },
        {
          "heading": "📊 实测数据：针对 Hermes 深度优化的增量算法",
          "level": 3,
          "offset": 1736,
          "anchor": "实测数据针对-hermes-深度优化的增量算法",
          "length": 754
        },
        {
          "heading": "💰 性价比之选",
          "level": 2,
          "offset": 2490,
          "anchor": "性价比之选",
          "length": 620
        },
        {
          "heading": "简单易用",
          "level": 3,
          "offset": 3110,
          "anchor": "简单易用",
          "length": 304
        },
        {
          "heading": "🚦 开始使用",
          "level": 2,
          "offset": 3414,
          "anchor": "开始使用",
          "length": 470
        }
      ]
    },
    {
      "slug": "mcp",
      "title": "MCP 服务",
      "path": "pages/docs/mcp.mdx",
      "url": "https://pushy.react-native.cn/docs/mcp",
      "resourceUri": "pushy-docs://docs/mcp",
      "content": "# MCP 服务\n\n把 Pushy 接进你的 AI 客户端（Claude Desktop、IDE、自建 Agent 等），让它直接查你的发布状态，再结合 GitHub、Sentry、CI 一起排查问题。全程只读，不会改动任何发布数据。\n\n:::tip AI 模型服务\n本站同时提供 [AI 模型转发服务](https://ai.cresc.dev/model-plaza)：提供纯正官方 GPT 和 Claude 最前沿模型，价格实惠、服务稳定、绝不掺水、数据安全。\n:::\n\n## 典型场景\n\n**排查某台设备为什么没收到更新**\n\n> 我的应用（testApp）1.2.0 这个原生包，有台安卓设备一直没拿到更新，帮我看看\n\nAI 会查出这个包当前绑定的版本、重放一次更新判定、检查增量产物是否就绪，然后告诉你卡在哪一步 —— 灰度没命中、包被暂停，还是补丁还没生成完。\n\n**发版前后确认状态**\n\n> 看下 1.2.0 现在发的哪个版本，灰度比例是多少\n\n**结合其他工具定位问题**\n\n> 这个版本昨天开始报错变多，对比一下 Sentry 上的异常和 Pushy 上的发布记录\n\n## 快速开始\n\n### 1. 创建令牌\n\n在 [Pushy 管理后台](https://pushy-admin.reactnative.cn) 打开「MCP 服务」，填写名称与客户端名称，**勾选允许访问的应用**，创建后立即复制令牌（只显示一次）。\n\n### 2. 配置客户端\n\n以 Claude Desktop 为例：\n\n```json\n{\n  \"mcpServers\": {\n    \"pushy\": {\n      \"type\": \"http\",\n      \"url\": \"https://update.reactnative.cn/api/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer pushy_mcp_你的令牌\"\n      }\n    }\n  }\n}\n```\n\n### 3. 直接提问\n\n连上之后按上面的场景提问即可，不需要记工具名。\n\n## 能查到什么\n\n| 能力   | 说明                     |\n| ---- | ---------------------- |\n| 应用列表 | 令牌授权范围内的应用             |\n| 发布拓扑 | 每个原生包当前绑定的版本、灰度版本与灰度比例 |\n| 更新判定 | 用一组客户端参数重放判定，给出结论与原因   |\n| 产物状态 | 增量补丁是否生成、生成任务是否失败      |\n\n## 注意事项\n\n- **只读**：不会发布版本、不会暂停应用、不会修改任何配置；\n- **按应用授权**：建议只勾选需要排查的应用；令牌可随时在后台撤销，立即生效；\n- **数据去向**：查询结果会返回给你授权的 AI 客户端，它可能继续发送给你选择的模型供应商 —— Pushy 不控制该客户端与供应商的数据处理策略；\n- 返回内容不含邮箱、IP、设备标识、密钥与支付信息。\n\n## 关于灰度\n\n灰度是否命中由客户端本地按设备计算，服务端只给出候选版本。排查时提供设备 uuid 才能算出这台设备实际会拿到哪个版本；uuid 只用于当次计算，不会被记录。\n",
      "sections": [
        {
          "heading": "MCP 服务",
          "level": 1,
          "offset": 0,
          "anchor": "mcp-服务",
          "length": 239
        },
        {
          "heading": "典型场景",
          "level": 2,
          "offset": 239,
          "anchor": "典型场景",
          "length": 256
        },
        {
          "heading": "快速开始",
          "level": 2,
          "offset": 495,
          "anchor": "快速开始",
          "length": 9
        },
        {
          "heading": "1. 创建令牌",
          "level": 3,
          "offset": 504,
          "anchor": "1-创建令牌",
          "length": 119
        },
        {
          "heading": "2. 配置客户端",
          "level": 3,
          "offset": 623,
          "anchor": "2-配置客户端",
          "length": 248
        },
        {
          "heading": "3. 直接提问",
          "level": 3,
          "offset": 871,
          "anchor": "3-直接提问",
          "length": 38
        },
        {
          "heading": "能查到什么",
          "level": 2,
          "offset": 909,
          "anchor": "能查到什么",
          "length": 215
        },
        {
          "heading": "注意事项",
          "level": 2,
          "offset": 1124,
          "anchor": "注意事项",
          "length": 191
        },
        {
          "heading": "关于灰度",
          "level": 2,
          "offset": 1315,
          "anchor": "关于灰度",
          "length": 89
        }
      ]
    },
    {
      "slug": "publish",
      "title": "发布热更新",
      "path": "pages/docs/publish.mdx",
      "url": "https://pushy.react-native.cn/docs/publish",
      "resourceUri": "pushy-docs://docs/publish",
      "content": "# 发布热更新\n\n现在你的应用已经具备了检测更新的功能，下面我们来尝试发布并更新它。流程可参考下图：\n\n```mermaid\nflowchart TD\n    codebase[\"🖥️&nbsp;&nbsp;项目代码库\"]\n    subgraph 发布原生基准版本\n    tagNativeVersion[\"🏷️&nbsp;&nbsp;(在 git 上)标记原生版本号\"]\n    newNativeVersion[\"🗂️&nbsp;&nbsp;新的原生基准版本\"]\n    nativePackage[\"📦&nbsp;&nbsp;原生完整包(apk、aab或ipa文件)\"]\n    tagNativeVersion--\"🔨&nbsp;&nbsp;编译\"-->nativePackage\n    nativePackage--\"⬆️&nbsp;&nbsp;使用<br/>pushy uploadApk/uploadAab/uploadIpa<br/>命令上传\"-->newNativeVersion\n    end\n    subgraph 发布热更新版本\n    tagBundleVersion[\"🏷️&nbsp;&nbsp;(在 git 上)标记热更新版本号\"]\n    bundlePackage[\"🎁&nbsp;&nbsp;js代码与资源包(ppk文件)\"]\n    tagBundleVersion--\"🔨&nbsp;&nbsp;使用<br/>pushy bundle<br/>命令生成并上传\"-->bundlePackage\n    someNativeVersions[\"🗂️&nbsp;&nbsp;一个或多个原生基准版本\"]\n    bundlePackage--\"🖇️&nbsp;&nbsp;绑定\"-->someNativeVersions\n    end\n    user[\"👨‍👩‍👧‍👦&nbsp;&nbsp;安装有对应原生基准版本的用户\"]\n    codebase--\"✏️&nbsp;&nbsp;改动js代码，<br/>或添加、更新js组件，<br/>或添加、更新js代码中引用的图片等资源\"-->发布热更新版本\n    codebase--\"🖊️&nbsp;&nbsp;改动原生代码、设置，<br/>或添加、更新原生组件，<br/>或添加、更新原生代码中引用的图片等资源\"-->发布原生基准版本\n    发布热更新版本--\"📲&nbsp;&nbsp;推送增量热更新(diff文件)\"-->user\n```\n\n流程总结如下：\n\n1. 我们需要先打包一个原生 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`）。\n2. 然后在基准版本之上迭代业务逻辑（增删 js 代码，增删图片等静态资源），使用`pushy bundle`命令来生成和发布热更新版本，而不需要重新打包。建议使用 git tag 功能来标记热更版本号（例如`v1.0.1`）。\n3. 如果迭代过程中有原生方面的修改，则需要发布并上传新的原生基准版本（重复步骤 1，但需要设置不同的原生版本号）。可以只保留一个原生基准版本，也可以多版本同时维护。\n\n## 发布原生基准版本\n\n### iOS\n\n首先参考[文档-在设备上运行](https://reactnative.cn/docs/running-on-device)，确定你正在使用离线包。然后点击菜单。\n\n按照正常的发布流程打包`.ipa`文件：\n\n1. Xcode 中运行设备选真机或 Generic iOS Device\n2. 菜单中选择 Product - Archive\n3. Archive 完成后选择`Export`生成.ipa 文件\n4. 然后运行如下命令上传到 pushy 服务器以供后续版本比对之用\n\n```bash\n$ pushy uploadIpa <ipa后缀文件>\n```\n\n此 ipa 的`CFBundleShortVersionString`字段(位于`ios/项目名/Info.plist`中)会被记录为原生版本号`packageVersion`。\n\n随后你可以选择往 AppStore 上传这个版本（可以重新 export 并调整相关选项，但请不要重新 archive），也可以先通过[Test flight](https://developer.apple.com/cn/testflight/)或[蒲公英](https://www.pgyer.com/doc/view/build_ipa)等渠道进行真机安装测试。请注意：暂不支持通过 Xcode 直接进行热更新测试。\n\n如果后续需要再次 archive 打包（例如修改原生代码或配置。如果只是修改 js 代码则不需要重新打包。），请先**更改版本号**，并在打包完成后再次`uploadIpa`到服务器端记录，否则后续生成的相同版本的原生包会由于[编译时间戳不一致而`无法获取热更新`](/docs/faq.md#热更新报错：热更新已暂停，原因：buildtime-mismatch。)。\n\n### Android\n\n首先参考[文档-打包 APK](https://reactnative.cn/docs/signed-apk-android)设置签名，然后在 android 文件夹下运行`./gradlew assembleRelease`或`./gradlew aR`，你就可以在`android/app/build/outputs/apk/release/app-release.apk`中找到你的应用包。\n\n如果你需要同时向 Google Play 等渠道分发 `.aab`，并向其他渠道分发 `.apk`，建议在项目根目录的`package.json`中配置一个 npm script，在同一次 Gradle 调用中同时执行`assembleRelease`和`bundleRelease`。这样 APK 与 AAB 会复用同一份 release 构建产物，内置 bundle 与编译时间戳保持一致，后续按渠道分发对应格式即可。如果已有`scripts`字段，只需要追加其中一项：\n\n```json\n{\n  \"scripts\": {\n    \"package:android:release\": \"cd android && ./gradlew clean assembleRelease bundleRelease\"\n  }\n}\n```\n\n```bash\n$ npm run package:android:release\n```\n\n产物路径如下：\n\n```text\nandroid/app/build/outputs/apk/release/app-release.apk\nandroid/app/build/outputs/bundle/release/app-release.aab\n```\n\n如果项目使用了 flavor，请按实际 variant 调整 npm script 中的任务名，例如`assembleProdRelease`和`bundleProdRelease`。不要先单独执行一次`assembleRelease`，再在另一次 Gradle 命令中执行`bundleRelease`，否则两个包可能带有不同的编译时间戳。\n\n然后根据实际分发格式运行对应命令\n\n```bash\n$ pushy uploadApk android/app/build/outputs/apk/release/app-release.apk\n# 如果你实际分发的是 aab 格式的包，则使用：\n$ pushy uploadAab android/app/build/outputs/bundle/release/app-release.aab\n```\n\n即可上传对应的 Android 原生包以供后续版本比对之用。此包的`versionName`字段(位于`android/app/build.gradle`中)会被记录为原生版本号`packageVersion`。\n\n随后你可以选择往应用市场发布这个版本，也可以先往设备上直接安装 apk 文件以进行测试。若同一个版本同时产出了 APK 与 AAB，请根据渠道要求分发对应格式：Google Play 通常使用 AAB，其他直装或第三方渠道通常使用 APK。\n\n如果后续需要再次打包（例如修改原生代码或配置。如果只是修改 js 代码则不需要重新打包。），请先**更改版本号**，并再次上传对应原生包到服务器端记录，否则后续生成的相同版本的原生包会由于[编译时间戳不一致而`无法获取热更新`](/docs/faq.md#热更新报错：热更新已暂停，原因：buildtime-mismatch。)。\n\n### Harmony\n\n首先下载鸿蒙开发IDE DevEco-Studio，然后通过Build => Build Hap(s)/App(s) => Build App(s)，你就可以在`harmony/build/outputs/default/harmony-default-unsigned.app`中找到你的应用包。\n\n然后运行如下命令\n\n```bash\n$ pushy uploadApp harmony/build/outputs/default/harmony-default-unsigned.app\n```\n\n即可上传 app 以供后续版本比对之用。此 app 的`versionName`字段(位于`harmony/AppScope/app.json5`中)会被记录为原生版本号`packageVersion`。\n\n随后你可以选择往华为应用市场发布这个版本，也可以先往设备上通过命令`hdc shell`命令安装这个 app 文件以进行测试。\n\n如果后续需要再次打包（例如修改原生代码或配置。如果只是修改 js 代码则不需要重新打包。），请先**更改版本号**，并再次`uploadApp`到服务器端记录，否则后续生成的相同版本的原生包会由于[编译时间戳不一致而`无法获取热更新`](/docs/faq.md#热更新报错：热更新已暂停，原因：buildtime-mismatch。)。\n\n## 发布热更新版本\n\n你可以尝试修改一行代码(譬如将版本一修改为版本二)，然后使用`pushy bundle --platform <ios|android|harmony>`命令来生成新的热更新版本。\n\n:::info\n如果你使用了较新版本的`expo`或其他没有`index.js`的框架，执行`bundle`命令时会报错。此时请手动创建一个`index.js`文件，在其中引用框架自身的入口文件即可。具体入口文件的路径如何，请参考框架的说明文档或者`package.json`中的`main`字段。例如针对`expo`的`index.js`可能是如下这样写：\n\n```js\nimport \"expo-router/entry\";\n```\n\n:::\n\n```bash\n$ pushy bundle --platform android\nBundling with React Native version:  0.22.2\n<各种进度输出>\nBundled saved to: build/output/android.1459850548545.ppk\nWould you like to publish it?(Y/N)\n```\n\n如果想要立即上传，此时输入 Y。当然，你也可以在将来使用`pushy publish --platform android build/output/android.1459850548545.ppk`来上传刚才打包好的热更新包。\n\n```\n  Uploading [========================================================] 100% 0.0s\nEnter version name: <输入热更新版本名字，如1.0.0-rc>\nEnter description: <输入热更新版本描述>\nEnter meta info: {\"ok\":1}\nOk.\nWould you like to bind packages to this version?(Y/N)\n```\n\n此时版本已经提交到 pushy 服务，但用户暂时看不到此更新，你需要先将特定的原生包版本绑定到此热更新版本上。\n\n此时输入 Y 立即绑定，你也可以在将来使用`pushy update --platform <ios|android|harmony>`来对已上传的热更包和原生包进行绑定。除此以外，你还可以在网页端操作，简单的将对应的原生包版本拖到需要的热更新版本下即可。\n\n```\n┌────────────┬──────────────────────────────────────┐\n│ Package Id │               Version                │\n├────────────┼──────────────────────────────────────┤\n│   46272    │ 2.0(normal)                          │\n├────────────┼──────────────────────────────────────┤\n│   45577    │ 1.0(normal)                          │\n└────────────┴──────────────────────────────────────┘\n共 2 个包\n输入原生包 id: 46272\n```\n\n版本绑定完毕后，服务器会在几秒内生成差量补丁，客户端就可以获取到更新了。\n\n后续要继续发布新的热更新，只需反复执行`pushy bundle`命令即可，不需要重新打包。\n\n恭喜你，至此为止，你已经完成了植入代码热更新的全部工作。\n\n## 灰度发布\n\n灰度发布（又称金丝雀发布、渐进式发布）是一种降低热更新发布风险的策略，通过逐步扩大更新范围来验证新版本的稳定性。\n\n### 什么是灰度发布\n\n灰度发布是指在正式全量发布热更新之前，先将更新推送给一小部分用户（如 5%、10%），观察这部分用户的使用情况后，再逐步扩大更新比例，直到最终推送给所有用户。\n\n### 灰度发布的作用\n\n- **降低风险**：如果新版本存在 bug，只会影响小部分用户，可以及时发现并回滚\n- **验证稳定性**：通过小范围用户的真实使用反馈，验证新版本在各种设备和网络环境下的表现\n- **平滑过渡**：避免突然的全量更新对服务器造成的压力峰值\n- **快速止损**：一旦发现问题，可以立即停止灰度，将影响范围控制在最小\n\n### 工作原理\n\n当你设置了灰度比例（如 10%）后，检查更新时会根据每个用户的设备 UUID 进行哈希计算，确定该用户是否在灰度范围内：\n\n- 灰度范围内的用户会收到新版本的更新推送\n- 灰度范围外的用户会收到上一个全量版本（如果有），或显示为已是最新\n- 同一用户的灰度状态是稳定的，不会因为多次检查更新而变化\n\n### 使用方法\n\n#### 通过网页端操作\n\n1. 登录 [Pushy 管理后台](https://update.reactnative.cn)\n2. 选择对应的应用和原生包版本\n3. 点击\"发布\"按钮\n4. 选择灰度发布比例\n\n#### 通过命令行操作\n\n请查看[命令行工具文档中的 rollout 参数](/docs/cli.md#pushy-update)\n\n### 注意事项\n\n:::warning\n**重要提示**：灰度版本与全量版本是独立的绑定关系。\n:::\n\n- **同时只能有一个灰度版本**：每个原生包版本只能绑定一个灰度热更版本（比例小于100%）和一个全量热更版本\n- **优先级**：如果同时存在灰度版本和全量版本，灰度范围内命中的用户会收到灰度版本，没有命中的用户会收到全量版本\n- **提升为全量**：将灰度比例设置为 100% 后，该版本会自动提升为全量版本，替换原有的全量版本\n- **客户端版本要求**：`react-native-update` >= 10.32.0 才能支持同时发布灰度和全量版本。\n",
      "sections": [
        {
          "heading": "发布热更新",
          "level": 1,
          "offset": 0,
          "anchor": "发布热更新",
          "length": 1789
        },
        {
          "heading": "发布原生基准版本",
          "level": 2,
          "offset": 1789,
          "anchor": "发布原生基准版本",
          "length": 13
        },
        {
          "heading": "iOS",
          "level": 3,
          "offset": 1802,
          "anchor": "ios",
          "length": 780
        },
        {
          "heading": "Android",
          "level": 3,
          "offset": 2582,
          "anchor": "android",
          "length": 1032
        },
        {
          "heading": "如果你实际分发的是 aab 格式的包，则使用：",
          "level": 1,
          "offset": 3614,
          "anchor": "如果你实际分发的是-aab-格式的包则使用",
          "length": 503
        },
        {
          "heading": "Harmony",
          "level": 3,
          "offset": 4117,
          "anchor": "harmony",
          "length": 604
        },
        {
          "heading": "发布热更新版本",
          "level": 2,
          "offset": 4721,
          "anchor": "发布热更新版本",
          "length": 1598
        },
        {
          "heading": "灰度发布",
          "level": 2,
          "offset": 6319,
          "anchor": "灰度发布",
          "length": 67
        },
        {
          "heading": "什么是灰度发布",
          "level": 3,
          "offset": 6386,
          "anchor": "什么是灰度发布",
          "length": 94
        },
        {
          "heading": "灰度发布的作用",
          "level": 3,
          "offset": 6480,
          "anchor": "灰度发布的作用",
          "length": 174
        },
        {
          "heading": "工作原理",
          "level": 3,
          "offset": 6654,
          "anchor": "工作原理",
          "length": 160
        },
        {
          "heading": "使用方法",
          "level": 3,
          "offset": 6814,
          "anchor": "使用方法",
          "length": 10
        },
        {
          "heading": "通过网页端操作",
          "level": 4,
          "offset": 6824,
          "anchor": "通过网页端操作",
          "length": 106
        },
        {
          "heading": "通过命令行操作",
          "level": 4,
          "offset": 6930,
          "anchor": "通过命令行操作",
          "length": 68
        },
        {
          "heading": "注意事项",
          "level": 3,
          "offset": 6998,
          "anchor": "注意事项",
          "length": 286
        }
      ]
    },
    {
      "slug": "skills",
      "title": "推荐：先安装 Skill，再让 AI 自动集成",
      "path": "pages/docs/skills.mdx",
      "url": "https://pushy.react-native.cn/docs/skills",
      "resourceUri": "pushy-docs://docs/skills",
      "content": "# 推荐：先安装 Skill，再让 AI 自动集成\n\n首次接入 Pushy 时，推荐先安装 `react-native-update` Skill，再让支持 Skills 的 AI 编程工具根据你的项目结构自动完成大部分集成改动。只有当工程结构特殊、需要精细控制，或者想逐项校对生成代码时，再回到后续手动文档。\n\n:::tip AI 模型服务\n本站同时提供 [AI 模型转发服务](https://ai.cresc.dev/model-plaza)：提供纯正官方 GPT 和 Claude 最前沿模型，价格实惠、服务稳定、绝不掺水、数据安全。\n:::\n\n## Skill 信息\n\n- Skill 名称：`react-native-update`\n- 源仓库：`reactnativecn/react-native-update-skill`\n\n## 推荐工作流\n\n1. 安装 `react-native-update` Skill。\n2. 在 AI 工具中打开你的 React Native / Expo 项目根目录。\n3. 直接提出接入需求，让 AI 根据现有工程自动改造。\n4. 用 [安装配置](/docs/getting-started.md) 和 [代码集成](/docs/integration.md) 作为校对基线。\n\n## 安装（推荐优先）\n\n安装到当前环境支持的所有 Agent：\n\n```bash\nnpx skills add reactnativecn/react-native-update-skill --skill react-native-update -a '*'\n```\n\n仅安装到 Claude Code：\n\n```bash\nnpx skills add reactnativecn/react-native-update-skill --skill react-native-update -a claude-code\n```\n\n## 更新 Skill\n\n```bash\nnpx skills check\nnpx skills update\n```\n\n## 可以直接这样对 AI 说\n\n安装后，可以直接向 AI 助手提出接入需求，例如：\n\n- 在 Expo 项目中接入 `react-native-update`\n- 在 class 组件根节点中完成接入改造\n- 保留当前导航和状态管理结构，自动包裹 `UpdateProvider`\n- 配置白名单灰度发布策略\n- 排查“热更新未生效”问题\n\n## Skill 可自动协助的内容\n\n- RN CLI / Expo 接入流程\n- `update.json` 与 `appKey` 配置检查\n- iOS / Android 关键改动提示\n- 更新策略建议（`checkStrategy` / `updateStrategy`）\n- 常见冲突排查（如 `expo-updates`）\n- class 组件与白名单示例\n\n## 何时改用手动文档\n\n- 工程是混编原生项目、monorepo 或目录结构高度定制\n- 自动 link 或原生构建本身已经异常，需要逐项定位\n- 团队希望将 AI 生成结果与手动基线逐行比对\n\n## 相关链接\n\n- 仓库：\n  <a href=\"https://github.com/reactnativecn/react-native-update-skill\" target=\"_blank\" rel=\"noopener noreferrer\">\n    [https://github.com/reactnativecn/react-native-update-skill](https://github.com/reactnativecn/react-native-update-skill)\n  </a>\n- ClawHub 页面（可选）：\n  <a href=\"https://clawhub.ai/skills/react-native-update\" target=\"_blank\" rel=\"noopener noreferrer\">\n    [https://clawhub.ai/skills/react-native-update](https://clawhub.ai/skills/react-native-update)\n  </a>\n",
      "sections": [
        {
          "heading": "推荐：先安装 Skill，再让 AI 自动集成",
          "level": 1,
          "offset": 0,
          "anchor": "推荐先安装-skill再让-ai-自动集成",
          "length": 277
        },
        {
          "heading": "Skill 信息",
          "level": 2,
          "offset": 277,
          "anchor": "skill-信息",
          "length": 95
        },
        {
          "heading": "推荐工作流",
          "level": 2,
          "offset": 372,
          "anchor": "推荐工作流",
          "length": 195
        },
        {
          "heading": "安装（推荐优先）",
          "level": 2,
          "offset": 567,
          "anchor": "安装推荐优先",
          "length": 267
        },
        {
          "heading": "更新 Skill",
          "level": 2,
          "offset": 834,
          "anchor": "更新-skill",
          "length": 61
        },
        {
          "heading": "可以直接这样对 AI 说",
          "level": 2,
          "offset": 895,
          "anchor": "可以直接这样对-ai-说",
          "length": 172
        },
        {
          "heading": "Skill 可自动协助的内容",
          "level": 2,
          "offset": 1067,
          "anchor": "skill-可自动协助的内容",
          "length": 185
        },
        {
          "heading": "何时改用手动文档",
          "level": 2,
          "offset": 1252,
          "anchor": "何时改用手动文档",
          "length": 99
        },
        {
          "heading": "相关链接",
          "level": 2,
          "offset": 1351,
          "anchor": "相关链接",
          "length": 486
        }
      ]
    }
  ]
}
