This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 57).
Expo 棕地开发
用于将 Expo 集成到现有原生应用中的工具包和 API。
expo-brownfield 是一个工具包,用于向现有的原生 Android 和 iOS 应用中添加 React Native 视图。它提供:
- 内置 API,用于原生应用和 React Native 应用之间的双向通信与导航
- 配置插件,用于在你的 Expo 项目中自动设置 brownfield 目标
- CLI,用于将构建产物发布到 Maven 仓库(Android)和 XCFrameworks(iOS)。
安装
If you are installing this in an existing React Native app, make sure to install expo in your project.
使用
通信 API
通信 API 使原生(宿主)应用与 React Native 之间能够进行基于消息的双向通信。
从 React Native 向原生发送消息
import * as Brownfield from 'expo-brownfield'; Brownfield.sendMessage({ type: 'MyMessage', data: { language: 'TypeScript', expo: true, platforms: ['android', 'ios'], }, });
在 React Native 中接收来自原生的消息
import * as Brownfield, { type MessageEvent } from 'expo-brownfield'; import { useEffect } from 'react'; function MyComponent() { useEffect(() => { const handleMessage = (event: MessageEvent) => { console.log('收到消息:', event); }; Brownfield.addMessageListener(handleMessage); return () => { Brownfield.removeMessageListener(handleMessage); }; }, []); // ... }
从原生向 React Native 发送消息
import expo.modules.brownfield.BrownfieldMessaging BrownfieldMessaging.sendMessage(mapOf( "type" to "MyAndroidMessage", "timestamp" to System.currentTimeMillis(), "data" to mapOf( "platform" to "android" ) ))
import ExpoBrownfield BrownfieldMessaging.sendMessage([ "type": "MyIOSMessage", "timestamp": Date().timeIntervalSince1970, "data": [ "platform": "ios" ] ])
在原生中接收来自 React Native 的消息
import expo.modules.brownfield.BrownfieldMessaging val listenerId = BrownfieldMessaging.addListener { event -> println("来自 React Native 的消息:$event") } // 稍后,移除监听器: BrownfieldMessaging.removeListener(listenerId)
import ExpoBrownfield let listenerId = BrownfieldMessaging.addListener { message in print("来自 React Native 的消息:\(message)") } // 稍后,移除监听器: BrownfieldMessaging.removeListener(id: listenerId)
在应用配置中进行配置
expo-brownfield 包提供了一个配置插件,可在使用连续原生生成(CNG)时用于配置 brownfield 集成。此插件允许你自定义 Expo 项目如何被打包并集成到现有的原生应用中。
Example app.json with config plugin
Configurable properties
发布构件
expo-brownfield CLI 会将 Android 构件推送到一个或多个 Maven 仓库,这些仓库通过应用配置中的 android.publishing 声明。支持的仓库类型如下:
remotePrivate 上的 url、username 和 password 接受普通字符串或 EnvValue 对象({ "variable": "SOME_ENV_VAR" }),并通过 Gradle 的 providers.environmentVariable(...) 在构建时解析值。请将密钥固定为环境变量,以免它们出现在提交的应用配置中。
发布到 GitHub Packages
GitHub Packages 托管了一个作用域限定于 GitHub 仓库的私有 Maven 仓库。身份验证使用 GITHUB_ACTOR(用户或机器人名称)以及具有 write:packages 和 read:packages 权限范围的个人访问令牌(PAT)。在 GitHub Actions 中,工作流的 GITHUB_TOKEN 已经拥有该工作流所属仓库所需的权限范围。
向 android.publishing 添加一个 remotePrivate 条目,并将其指向测试仓库的 GitHub Packages URL:
name 字段同时控制 Gradle Maven 仓库名称和 --repo CLI 参数值。使用 "name": "GitHubPackages" 时,发布版本的任务会变为 publishBrownfieldReleasePublicationToGitHubPackagesRepository(调试版本对应的任务为 publishBrownfieldDebugPublicationToGitHubPackagesRepository)。默认调用会同时运行以下任务:
仅发布融合后的构件
非融合发布流程会为每个自动链接的 Expo Android 模块发布一个 Maven 坐标(每次发布通常为 20 到 40 个构件)。如需改为发布一组精简且经过筛选的构件,请使用 --fused。融合模式会跳过发布插件按模块重新发布的循环,仅发布 fat AAR 兄弟构件。由于没有默认值,因此始终需要指定仓库(--repo)或显式任务(-t):
每次发布最多只会发布两个 Maven 坐标,与项目自动链接的 Expo 模块数量无关。其他所有内容(每个模块的 AAR、传递性 Maven 依赖)都会保留在远程仓库之外。基础库(AndroidX、RN 运行时、Kotlin stdlib、宿主已有的 Glide 替代方案)会继续作为外部依赖保留在 POM 中,而不会融合进 AAR。
融合模式
--fused 会通过两个额外的 Gradle 子项目发布内容,这两个子项目由 prebuild 始终生成 — :<libraryName>-fused-release 和 :<libraryName>-fused-debug — 每个子项目都通过 AGP 的 Fused Library 插件 生成一个 fat AAR(该功能目前处于 Preview 阶段;CLI 会临时强制这些构建使用 AGP 8.13)。这些子项目在普通构建期间不会执行任何操作:只有当 CLI 传入 -Pbrownfield.fused=true 时,它们的构建脚本才会激活,因此 npx expo run:android 和 IDE 同步不会产生额外的配置开销。如果你不通过 CLI、而是直接调用 fused Gradle 任务,请自行传入 -Pbrownfield.fused=true。
并非所有内容都会被融合进 AAR。构建会将以下三类依赖保留在外部,并在发布的 POM 和 Gradle Module Metadata 中将它们声明为普通 Maven 依赖,由宿主应用自行解析:
- brownfield 宿主基线 — React Native 运行时(
react-android、hermes-android、fbjni、soloader、yoga)、Kotlin 标准库,以及由宿主提供的 commons(Material Components、Guava、Fresco、OkHttp、Okio)。融合这些内容会与宿主已经携带的类重复。 androidx.*库 — AGP Fused Library 的类重写器无法解析其 styleable 中的android:框架属性,因此除非出于验证原因必须融合的链路(默认包括androidx.camera、androidx.media3),所有 AndroidX 都会保留在外部。- 自动检测到的 KMP 总模块 — 仅存在 pom 的坐标,其所有变体都会重定向到一个平台模块。
发布的元数据还会为 react-android 和 hermes-android 依赖边添加同级模块的构建类型,因此宿主应用始终会解析与 fused AAR 的原生库进行链接时相同的 React Native 变体,即使宿主应用以 debug 模式使用 release AAR。
当项目的依赖图有相应需求时,可以通过以下五个 Gradle 属性调整此行为:
从 android 目录直接调用 fused 发布任务时,通过 -P Gradle 属性传入这些配置:
注意:在进行融合之前,请检查宿主应用已经使用哪些库。如果宿主提供了自己的 Glide(
expo-image会融合 Glide)、Jetpack Compose(@expo/ui)或类似库,fused AAR 中的副本会在构建时与宿主中的副本冲突(类重复),或通过 POM 强制升级宿主中的版本。建议将这些 Expo 模块排除在项目之外,或者将共享组标记为brownfield.fused.host-provided并手动对齐版本。
GitHub Actions 工作流示例
通过 git tag brownfield-v1.0.0 && git push --tags 触发(或通过 Actions 选项卡手动触发)。工作流内置的 GITHUB_TOKEN 仅限于工作流所属的仓库,因此若要发布到第三方仓库,则需要改用存储在仓库密钥中的 PAT。
消费方设置(宿主 Android 应用)
宿主应用的 settings.gradle.kts(或各模块的 build.gradle.kts)声明相同的 GitHub Packages URL 和凭据。宿主应用的 CI/本地构建必须设置 GITHUB_ACTOR 和 GITHUB_TOKEN 环境变量,或从 ~/.gradle/gradle.properties 中读取。
releaseImplementation/debugImplementation 会自动配对各个变体。Gradle 会根据宿主构建类型选择匹配的同级变体。如果宿主应用只发布 release 构建,可以删除 debugImplementation 行,并使用 --fused --release 仅发布 release 同级变体。
宿主应用的一些要求和提示:
minSdk必须至少为 24(React Native 的最低要求)。如果宿主应用面向更低的 API 级别,那么在使用 AAR 时会在清单合并阶段失败。- 权限会从融合模块合并进来。 例如,与媒体相关的 Expo 模块会声明存储权限。如果宿主应用强制执行权限允许列表,请在宿主清单中使用
tools:node="remove"移除不需要的条目(或使用tools:replace解决属性冲突)。 - AAR 会携带发布时启用的每个 ABI 的原生库。 如果不进行过滤,宿主 APK 的体积会增加相当于四种 ABI 的 React Native 库大小。可以在发布时限制 ABI(在 Expo 项目的 gradle.properties 中设置
reactNativeArchitectures=arm64-v8a),或在宿主应用中使用ndk.abiFilters/APK 分包进行过滤。
CLI
expo-brownfield 库包含一个 CLI,用于构建并发布到 Maven 仓库(Android)和 XCFrameworks(iOS)。
命令
build:android
构建并将 brownfield 库及其依赖项发布到 Maven 仓库。
build:ios
构建 brownfield XCFramework,并将 Hermes XCFramework 复制到产物目录。
tasks:android
列出所有可用的发布任务和 Maven 仓库。
API
import * as Brownfield from 'expo-brownfield';
Hooks
Hook to observe and set the value of shared state for a given key.
Provides a synchronous API similar to useState.
[T | undefined, (value: T | (prev: T | undefined) => T) => void]A tuple containing the value and a function to set the value.
Methods
Gets the number of registered message listeners.
numberThe number of active message listeners.
Gets the value of shared state for a given key.
T | undefinedNavigates back to the native part of the app, dismissing the React Native view.
voidSends a message to the native side of the app. The message can be received by setting up a listener in the native code.
voidEnables or disables the native back button behavior. When enabled, pressing the back button will navigate back to the native part of the app instead of performing the default React Navigation back action.
voidSets the value of shared state for a given key.
voidEvent subscriptions
Adds a listener for messages sent from the native side of the app.
EventSubscriptionA subscription object that can be used to remove the listener.
Example
const subscription = addMessageListener((event) => { console.log('Received message from native:', event); }); // Later, to remove the listener: subscription.remove();
Adds a listener for changes to the shared state for a given key.
EventSubscriptionA subscription object that can be used to remove the listener.
Removes a specific message listener.
voidInterfaces
A subscription object that allows to conveniently remove an event listener from the emitter.