# 安装与原生接入

按 React Native、Expo 或 HarmonyOS 选择步骤，让 Release 安装包具备加载更新的能力。

## 准备好这些 {#prerequisites}

- 已经能构建的 React Native、Expo 或 RNOH 工程。命令默认在包含 `package.json` 的**应用根目录**执行。
- Node.js ≥18.17 和工程原有的平台工具链；iOS 构建需要 macOS。
- 独立测试应用与设备。先跑通一个平台，再扩展。

SDK 声明 React ≥16.8、React Native ≥0.59，不代表所有版本与架构组合都经过验证。保留项目现有版本，根据现有 MainApplication / AppDelegate 文件选择下面的代码。

## 安装并取得 appKey {#install}

先选择工程类型，复制对应命令，在含 `package.json` 的目录执行。

```bash group="Install" tab="React Native" title="React Native"
npm install -g rn-update-cli
npm install rn-update
```

```bash group="Install" tab="Expo" title="Expo"
npm install -g rn-update-cli
npx expo install rn-update
```

iOS 项目继续在 `ios` 目录执行 `pod install`（使用 Bundler 时运行 `bundle exec pod install`），完成后回到应用根目录。Expo 的原生工程在[后续构建](/docs/expo#build)时生成。

再登录并选择应用：

```bash
pakta login
pakta createApp --platform android --name PaktaDemo
pakta selectApp --platform android
```


已有测试应用时跳过 `createApp`。iOS / HarmonyOS 分别使用 `ios` / `harmony`。选择成功后，根目录生成或更新 `update.json`。

### 哪个标识填在哪里 {#appkey}

| 标识 | 来源 | 使用位置 |
| --- | --- | --- |
| `appKey` | 应用详情或 `update.json` | SDK 的 `new Pakta({ appKey })` |
| `appId` | 应用详情或 `update.json` | CLI / 管理 API 选择应用 |
| `PAKTA_API_TOKEN` | 控制台 API 令牌入口 | 仅 CLI / CI，不放 App |

接下来只做与你工程对应的一节。

## Android：配置 bundle 加载入口 {#android}

打开 `android/app/src/main/java/你的包名/MainApplication.kt`。若文件使用 `reactHost` 与 `getDefaultReactHost`，在现有宿主中加入 `jsBundleFilePath`：

```kotlin title="MainApplication.kt · ReactHost"
import cn.reactnative.modules.update.UpdateContext

// 保留已有的 ReactHost、PackageList 和 getDefaultReactHost 导入。
override val reactHost: ReactHost by lazy {
  getDefaultReactHost(
    context = applicationContext,
    packageList = PackageList(this).packages,
    jsBundleFilePath = UpdateContext.getBundleUrl(this),
  )
}
```

若使用 `DefaultReactNativeHost` / `ReactNativeHost`，同样导入 `UpdateContext`，在已有 host 对象内覆盖方法，不再创建第二个宿主：

```kotlin title="MainApplication.kt · ReactNativeHost"
override fun getJSBundleFile(): String? =
  UpdateContext.getBundleUrl(this@MainApplication)
```

在现有 `android/app/build.gradle` 的 release 配置中关闭 PNG 压缩，减少跨构建资源字节差异：

```groovy title="android/app/build.gradle"
android {
  buildTypes {
    release {
      crunchPngs false
    }
  }
}
```

依赖由 React Native 自动链接。构建时间由 SDK 自动生成，保留原有签名和构建设置。

先[连接根组件](/docs/integration)，再从 `android` 目录运行 `./gradlew assembleRelease`；Windows 使用 `.\gradlew.bat assembleRelease`。有 flavor 时使用对应 Release task。

### 使用 Java 的工程 {#android-java}

打开 `android/app/src/main/java/你的包名/MainApplication.java`，文件顶部添加 `import cn.reactnative.modules.update.UpdateContext;`，在已有 `ReactNativeHost` 或 `DefaultReactNativeHost` 对象内添加：

```java title="MainApplication.java · inside the existing ReactNativeHost"
@Override
protected String getJSBundleFile() {
    return UpdateContext.getBundleUrl(MainApplication.this);
}
```

## iOS：Release 使用 Pakta bundle {#ios}

在 `ios` 目录安装 Pods：使用 Bundler 时执行 `bundle exec pod install`，否则执行 `pod install`。

```objc title="AppDelegate.mm"
#import "RCTPakta.h"

- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [RCTPakta bundleURL];
#endif
}
```

保留现有 Debug 入口、模块名与生命周期。Swift 模板让实际 bundle URL 提供者在 Release 返回 `RCTPakta.bundleURL()`，通过项目 Objective-C bridging header 暴露 `RCTPakta.h`，不要整份覆盖 AppDelegate。

完成[根组件接入](/docs/integration)，用 Xcode 打开 `.xcworkspace`，选择签名与设备，以 Release 构建验证。保留同次归档导出的 IPA 用于登记。

### Swift 与旧版 AppDelegate {#ios-swift}

Swift 工程先在 App target 的 Objective-C bridging header 中添加 `#import "RCTPakta.h"`。如果没有此文件，新建头文件并在 Build Settings 的 **Objective-C Bridging Header** 填入相对路径，例如 `YourApp/YourApp-Bridging-Header.h`。

找到现有提供 bundle URL 的方法，替换 Release 分支。新模板可能把方法放在 `ReactNativeDelegate` 中，应在那里修改：

```swift title="AppDelegate.swift · bundleURL"
override func bundleURL() -> URL? {
#if DEBUG
  return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
  return RCTPakta.bundleURL()
#endif
}
```

旧 Objective-C 模板若只有 `sourceURLForBridge:`，保留方法签名，把其中非 DEBUG 分支改为 `return [RCTPakta bundleURL];`。混编工程使用 bridge delegate 提供 URL，避免直接用固定文件路径创建 root view。

## Expo：构建包含原生模块的 App {#expo}

Expo Go 不包含 rn-update 原生模块。按顺序操作：

1. **安装依赖**：执行 `npx expo install rn-update`。需要自定义渠道时，在 `expo.plugins` 加入内置 Config Plugin。
2. **连接根布局**：在 `app/_layout.tsx` 用 `UpdateProvider` 包裹已有 `Stack` / `Slot`，客户端在组件外创建。
3. **构建原生 App**：运行 `npx expo run:android --variant release`，iOS 使用 `npx expo run:ios --configuration Release`，或项目已有 EAS production profile。
4. **安装并验证**：确认脱离 Metro 可启动；后续生成 OTA 使用 `bundle --expo`，平台仍为 `android` / `ios`。

完整步骤见[Expo 接入](/docs/expo)。

## HarmonyOS：按 RNOH 示例接线 {#harmonyos}

HarmonyOS 需要 HAR、ArkTS 与 C++ 注册，不能只安装 npm 包。逐项对照示例：

以下路径假设 `node_modules` 与 `harmony` 都在应用根目录。将配置合并到已有文件，保留现有插件和包列表；不要替换整个工程文件。

1. 给 entry 添加 Pakta HAR 依赖。
2. 给 entry 的 Hvigor 构建添加 `reactNativeUpdatePlugin()`。
3. 在 ArkTS 和 C++ 两处现有包列表中加入 Pakta。
4. 在 Release 的 `RNApp` 中优先加载 Pakta 更新，找不到更新时加载内置 `bundle.harmony.js`。

```json5 title="harmony/entry/oh-package.json5 · dependencies"
"pakta": "file:../../node_modules/rn-update/harmony/pakta.har"
```

```typescript title="harmony/entry/hvigorfile.ts"
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
import { reactNativeUpdatePlugin } from '../../node_modules/rn-update/harmony/hvigor-plugin';

export default {
  system: hapTasks,
  plugins: [reactNativeUpdatePlugin()],
};
```

```typescript title="harmony/entry/src/main/ets/RNPackagesFactory.ets"
import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';
import PaktaPackage from 'pakta';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [new PaktaPackage(ctx)];
}
```

```cmake title="harmony/entry/src/main/cpp/CMakeLists.txt · after add_library(rnoh_app ...)"
set(PAKTA_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules/rn-update/harmony/pakta/src/main/cpp")
target_include_directories(rnoh_app PRIVATE "${PAKTA_CPP_DIR}")
target_sources(rnoh_app PRIVATE "${PAKTA_CPP_DIR}/PaktaTurboModule.cpp")
```

```cpp title="harmony/entry/src/main/cpp/PackageProvider.cpp"
#include "RNOH/PackageProvider.h"
#include "PaktaPackage.h"
using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
  return {std::make_shared<PaktaPackage>(ctx)};
}
```

```typescript title="harmony/entry/src/main/ets/pages/Index.ets · imports"
import { PaktaFileJSBundleProvider } from 'pakta';
import { AnyJSBundleProvider, ResourceJSBundleProvider } from '@rnoh/react-native-openharmony';
```

```typescript title="Index.ets · RNApp jsBundleProvider"
jsBundleProvider: new AnyJSBundleProvider([
  new PaktaFileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
  new ResourceJSBundleProvider(
    this.rnohCoreContext.uiAbilityContext.resourceManager,
    'bundle.harmony.js'
  ),
]),
```

运行前确认 `this.rnohCoreContext` 已就绪。保留工程原有 RNApp 其他参数；Release 不要把 Metro 放在更新文件前面。

```bash title="应用根目录"
pakta bundle --platform harmony --output .pakta/output/harmony.ppk --no-interactive
```

在 DevEco Studio 同步、签名并构建原生 `.app`。确认生成的 rawfile bundle 与宿主资源路径一致，安装验证后用 `uploadApp` 登记。

完整工程在 [SDK 示例目录](https://github.com/pakta-team/rn-update/tree/main/Example)：`testHotUpdate`、`expoUsePakta`、`harmony_use_pakta`。示例的仓库相对路径需要改为你的依赖路径。

## 自托管：分别配置 SDK 和 CLI {#self-host}

托管服务用户跳过。SDK 连接公开更新接口，CLI 连接管理接口：

```tsx title="加入现有 Pakta 客户端选项"
server: {
  main: ['https://YOUR_HOST/api'],
  queryUrls: ['https://YOUR_CDN/endpoints.json'],
},
```

替换为真实部署地址；端点清单必须属于你的部署。登录 CLI 前设置主机：

```powershell title="PowerShell"
$env:RNU_SERVICE_URL = 'https://YOUR_HOST'
```

```bash title="Bash / zsh"
export RNU_SERVICE_URL=https://YOUR_HOST
```

## 手动链接：只处理未自动接入的工程 {#manual-link}

React Native 0.60 及以上通常自动链接，先运行 `npx react-native config` 检查 `rn-update` 是否被发现，iOS 再安装 Pods。已自动链接时跳过本节，重复注册会出错。

旧工程 Android 在 `android/settings.gradle` 引入 `node_modules/rn-update/android`，在 app 的 dependencies 中添加 `implementation project(':rn-update')`，再把 `new UpdatePackage()` 加入已有 getPackages 列表（导入 `cn.reactnative.modules.update.UpdatePackage`）。三处必须一起配置。

```groovy title="android/settings.gradle"
include ':rn-update'
project(':rn-update').projectDir = new File(rootProject.projectDir, '../node_modules/rn-update/android')
```

```groovy title="android/app/build.gradle · dependencies 内"
implementation project(':rn-update')
```

iOS 未自动发现依赖时，在已有 Podfile 的 App target 中添加 `pod 'rn-update', :path => '../node_modules/rn-update'`，再运行 `pod install`。完成链接后仍需配置前面的 bundle 加载入口。

## Android 页面恢复：使用 react-native-screens 时 {#android-activity}

使用 `react-native-screens` 的工程检查 `MainActivity` 的恢复配置，避免重载后恢复旧页面状态导致崩溃。下面适用于依赖已提供 `RNScreensFragmentFactory` 的版本，放在现有 MainActivity 类中，不能放进 MainActivityDelegate：

```kotlin title="MainActivity.kt · 添加 import 和 onCreate"
import android.os.Bundle
import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory

override fun onCreate(savedInstanceState: Bundle?) {
  supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
  super.onCreate(savedInstanceState)
}
```

已有 onCreate 时合并到原方法，不添加第二个；旧版本没有该类时，按[所用 screens 版本的安装说明](https://github.com/software-mansion/react-native-screens#android)处理，不直接导入不存在的类。

## 可选：用链接打开测试更新 {#deep-link}

已有扫码页面时直接调用[parseTestQrCode](/docs/api#function-parsetestqrcodeqrcode-string)。想从浏览器或相机打开 App 时，再配置一个你自己使用的 scheme，例如 `paktademo`。

Android 在已有 MainActivity 的 `<activity>` 内增加独立 intent-filter，保留原有启动 filter，并确认 activity 使用 `android:launchMode="singleTask"`：

```xml title="AndroidManifest.xml · MainActivity 内"
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="paktademo" />
</intent-filter>
```

iOS 在 App target 的 **Info → URL Types** 新增 URL Scheme `paktademo`，并保留工程现有 React Native Linking 的 AppDelegate 转发。Expo 在 `expo.scheme` 中设置同一名称。修改后重新构建安装包。

上传测试更新后，从控制台复制完整 hash，打开 `paktademo://update?type=__rnPaktaVersionHash&data=实际HASH`。Provider 识别后检查该更新；继续按 App 策略下载、生效。`testChannel: false` 会拒绝测试载荷。

## Android AAB 安装包 {#aab}

Google Play 使用 AAB 时，保留实际提交的 AAB，执行 `pakta parseAab 路径` 检查，再用 `pakta uploadAab 路径` 登记。不要用另一次构建的 APK 代替。

不要为了接入盲目关闭所有语言、密度、ABI 分割。先按实际分发包验证资源和更新；某个 split 缺失资源时，检查资源打包配置及对应原生版本。

## 下一步 {#verify}

依赖已安装、CLI 已选中正确平台应用、原生入口已配置。前往[让 App 检查更新](/docs/integration)，加上 Provider 与版本面板，再构建安装包。

Expo 本地构建参数参照 [Expo CLI 官方文档](https://docs.expo.dev/more/expo-cli/)。
