要在 Android 上集成 React Native,你需要通过修改以下文件来配置原生项目:
- Gradle 文件:settings.gradle、顶层 build.gradle、app/build.gradle 和 gradle.properties,用于添加 React Native Gradle Plugin(RNGP)及其他属性。
- AndroidManifest.xml:用于添加必要权限。(了解更多)
- MainActivity:用于加载你的 React Native 应用。
配置 Gradle
1
首先编辑你的 settings.gradle 文件,并添加以下行(可参考 最小模板):
pluginManagement {
def reactNativeGradlePlugin = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
}.standardOutput.asText.get().trim()
).getParentFile().absolutePath
includeBuild(reactNativeGradlePlugin)
def expoPluginsPath = new File(
providers.exec {
workingDir(rootDir)
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
}.standardOutput.asText.get().trim(),
"../android/expo-gradle-plugin"
).absolutePath
includeBuild(expoPluginsPath)
}
plugins {
id("com.facebook.react.settings")
id("expo-autolinking-settings")
}
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
}
expoAutolinking.useExpoModules()
expoAutolinking.useExpoVersionCatalog()
includeBuild(expoAutolinking.reactNativeGradlePlugin)
使用自定义文件夹结构?
如果你使用的是自定义文件夹结构,那么为了让自动链接正常工作,你需要在 settings.gradle 中显式设置项目根目录。请修改以下行:
2
然后打开顶层 build.gradle 并包含这一行(如 最小模板 所示):
这可以确保 React Native Gradle 和 Expo 插件在你的项目中可用并被应用。
3
将以下行添加到你应用的 build.gradle 文件中(通常是 app/build.gradle — 你可以参考 最小模板文件):
使用自定义文件夹结构?
如果你使用的是自定义文件夹结构,那么你需要在 app/build.gradle 中调整 projectRoot 的值,使其指向你的 Expo 项目根目录。请修改以下行:
4
最后,打开你应用的 gradle.properties 文件,并添加以下几行(可参考 最小模板文件):
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
newArchEnabled=true
hermesEnabled=true
配置你的 manifest
1
首先,确保你的 AndroidManifest.xml 中包含 INTERNET 权限:
2
现在,在你的 debug AndroidManifest.xml 中启用 明文流量:
这是让你的应用通过 HTTP 与本地 Metro bundler 通信所必需的。你可以将最小模板中的 AndroidManifest.xml 文件作为参考:主 和 调试
将其与你的代码集成
现在,你需要添加一些原生代码来启动 React Native 运行时,并告诉它渲染你的 React 组件。
更新你的 Application 类
先更新你的 Application 类以初始化 React Native。你可以参考 最小模板 中的 MainApplication.kt:
创建一个 ReactActivity
创建一个新的 Activity,让它继承 ReactActivity 并承载 React Native 代码。这个 Activity 将负责启动 React Native 运行时并渲染 React 组件。你可以参考 最小模板中的 MainActivity.kt:
import android.os.Build
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import expo.modules.ReactActivityDelegateWrapper
class MyReactActivity : ReactActivity() {
override fun getMainComponentName(): String = "main"
override fun createReactActivityDelegate(): ReactActivityDelegate {
return ReactActivityDelegateWrapper(
this,
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
object : DefaultReactActivityDelegate(
this,
mainComponentName,
fabricEnabled
){})
}
}
将这个新的 Activity 添加到你的 AndroidManifest.xml 文件中,并确保将 MyReactActivity 的主题设置为 Theme.AppCompat.Light.NoActionBar(或任何不带 ActionBar 的主题),以避免你的应用在 React Native 屏幕上方渲染一个 ActionBar:
现在你的 Activity 已准备好运行一些 JavaScript 代码。
要在 iOS 上集成 React Native,你需要通过修改以下文件来配置原生 iOS 项目:
- Podfile:用于添加 React Native 依赖项。
- Xcode 项目:用于添加打包 JavaScript 代码的构建阶段。
- Info.plist:用于配置 React Native 所需的应用设置。
配置 CocoaPods
如果你的项目没有 Podfile,你可以参考 最小模板 创建一个:
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
require 'json'
platform :ios, '16.4'
install! 'cocoapods',
:deterministic_uuids => false
prepare_react_native_project!
target 'HelloWorld' do
use_expo_modules!
config_command = [
'npx',
'expo-modules-autolinking',
'react-native-config',
'--json',
'--platform',
'ios'
]
config = use_native_modules!(config_command)
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => true,
:app_path => "#{Pod::Config.instance.installation_root}/..",
:privacy_file_aggregation_enabled => true,
)
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
)
end
end
如果你的项目已经有 Podfile,你需要手动将 React Native 依赖合并到现有的 Podfile 中。
使用自定义文件夹结构?
如果你使用的是自定义文件夹结构,那么为了让自动链接正常工作,你需要在 Podfile 中显式设置项目根目录。请修改 Podfile 中以下行:
现在,运行以下命令:
运行 pod 命令会将 React Native 代码集成到你的应用中,使你的 iOS 文件能够导入 React Native 头文件。
配置你的 Xcode 项目
1
在执行 pod install 命令后,CocoaPods 会创建一个 Xcode 工作区 {Project}.xcworkspace,你需要打开 xcworkspace 项目,而不是传统的 xcodeproj 项目。或者,你也可以使用以下命令打开项目:
在 Xcode 项目导航器中,选择你的项目,然后在 TARGETS 下选择你的应用目标。在 Build Settings 中,使用搜索栏查找 ENABLE_USER_SCRIPT_SANDBOXING。如果它尚未设置,则将其值设为 No。这对于在 React Native 随附的 Hermes 引擎 的 Debug 和 Release 版本之间正确切换是必需的。
2
现在切换到 Build Phases 选项卡,并在 [CP] Embed Pods Frameworks 阶段之前添加一个新的 Run Script Phase。这个脚本将把你的 JavaScript 代码和资源打包到 iOS 应用中。
if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then
source "$PODS_ROOT/../.xcode.env"
fi
if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then
source "$PODS_ROOT/../.xcode.env.local"
fi
export PROJECT_ROOT="$PROJECT_DIR"/..
if [[ "$CONFIGURATION" = *Debug* ]]; then
export SKIP_BUNDLING=1
fi
if [[ -z "$ENTRY_FILE" ]]; then
export ENTRY_FILE="$("$NODE_BINARY" -e "require('expo/scripts/resolveAppEntry')" "$PROJECT_ROOT" ios absolute | tail -n 1)"
fi
if [[ -z "$CLI_PATH" ]]; then
# 使用 Expo CLI
export CLI_PATH="$("$NODE_BINARY" --print "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })")"
fi
if [[ -z "$BUNDLE_COMMAND" ]]; then
# 打包时默认使用的 Expo CLI 命令
export BUNDLE_COMMAND="export:embed"
fi
# 如果 .xcode.env.updates 存在则加载它,以便在需要时
# 取消设置 SKIP_BUNDLING
if [[ -f "$PODS_ROOT/../.xcode.env.updates" ]]; then
source "$PODS_ROOT/../.xcode.env.updates"
fi
# 加载本地更改,以便在需要时
# 覆盖默认值
if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then
source "$PODS_ROOT/../.xcode.env.local"
fi
`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"`
下次当你为 Release 构建应用时,React Native 代码将使用 Expo CLI 进行打包,并嵌入到应用中。
3
编辑你的 Info.plist 文件,并确保添加 UIViewControllerBasedStatusBarAppearance 键,其值为 NO,这对于确保状态栏由 React Native 正确管理是必需的。
将其与你的代码集成
现在,你需要添加一些原生代码来启动 React Native 运行时,并告诉它渲染你的 React 组件。
创建 ReactViewController
创建一个名为 ReactViewController.swift 的新文件,这将作为加载 React Native 视图并将其作为 view 的视图控制器。
ReactViewController.swift import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
class ReactNativeViewController: UIViewController {
var reactNativeFactory: RCTReactNativeFactory?
var reactNativeFactoryDelegate: RCTReactNativeFactoryDelegate?
override func viewDidLoad() {
super.viewDidLoad()
reactNativeFactoryDelegate = ReactNativeDelegate()
reactNativeFactoryDelegate!.dependencyProvider = RCTAppDependencyProvider()
reactNativeFactory = RCTReactNativeFactory(delegate: reactNativeFactoryDelegate!)
view = reactNativeFactory!.rootViewFactory.view(withModuleName: "HelloWorld")
}
}
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
在根视图控制器中展示 React Native 视图
最后,你可以展示你的 React Native 视图。为此,你需要一个新的视图控制器来承载一个可加载 JS 内容的视图。你已经有了初始的 ViewController,并且可以让它展示 ReactViewController。实现方式有多种,取决于你的应用。这个例子中,我们假设你有一个按钮会以模态方式展示 React Native。
import UIKit
class ViewController: UIViewController {
var reactViewController: ReactViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
let button = UIButton()
button.setTitle("打开 React Native", for: .normal)
button.setTitleColor(.systemBlue, for: .normal)
button.setTitleColor(.blue, for: .highlighted)
button.addAction(UIAction { [weak self] _ in
guard let self else { return }
if reactViewController == nil {
reactViewController = ReactViewController()
}
present(reactViewController!, animated: true)
}, for: .touchUpInside)
self.view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
button.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
])
}
}