This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Expo Brownfield
用于将 Expo 集成到现有原生应用中的工具包和 API
expo-brownfield 是一个工具包,用于将 React Native 视图添加到现有的原生 Android 和 iOS 应用中。它提供:
- 内置 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('Received message:', 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("Message from React Native: $event") } // Later, to remove the listener: BrownfieldMessaging.removeListener(listenerId)
import ExpoBrownfield let listenerId = BrownfieldMessaging.addListener { message in print("Message from React Native: \(message)") } // Later, to remove the listener: 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" 时,发布 release 的任务会变为 publishBrownfieldReleasePublicationToGitHubPackagesRepository(debug 对应的任务则为 publishBrownfieldDebugPublicationToGitHubPackagesRepository)。默认调用会同时运行两者:
仅发布融合制品
非融合发布流程会为每个自动链接的 Expo Android 模块生成一个 Maven 坐标(每个版本通常有 20 到 40 个制品)。若要改为发布一小组经过筛选的制品,请使用 --fused。融合模式会跳过发布插件按模块逐个重新发布的循环,只生成胖 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 插件生成一个胖 AAR(这是一项预览功能;CLI 会暂时强制使用 AGP 8.13 进行这些构建)。在常规构建期间,这些子项目不会执行任何操作:其构建脚本仅在 CLI 传入 -Pbrownfield.fused=true 时才会启用,因此 npx expo run:android 和 IDE 同步不会产生额外的配置开销。如果不通过 CLI,而是直接调用融合 Gradle 任务,请自行传入 -Pbrownfield.fused=true。
并非所有内容都会融合进 AAR。构建会将三类依赖保留在外部,并在已发布的 POM 和 Gradle Module Metadata 中将它们声明为普通 Maven 依赖,由宿主应用自行解析:
- brownfield 宿主基线依赖 — React Native 运行时(
react-android、hermes-android、fbjni、soloader、yoga)、Kotlin stdlib,以及宿主提供的通用库(Material Components、Guava、Fresco、OkHttp、Okio)。融合这些依赖会重复包含宿主已经打包的类。 androidx.*库 — AGP Fused Library 的类重写器无法解析其样式表中的android:框架属性,因此除验证所需的依赖链(默认情况下为androidx.camera、androidx.media3)外,所有 AndroidX 库都会保留为外部依赖。- 自动检测到的 KMP 伞形模块 — 仅包含 POM 的坐标,其所有变体都会重定向到某个平台模块。
已发布的元数据还会使用同级项的构建类型标注 react-android 和 hermes-android 依赖边,确保宿主应用始终解析与融合 AAR 的原生库链接时所用版本相同的 React Native 变体,即使 debug 宿主使用的是 release AAR。
当项目的依赖图需要调整时,可使用以下五个 Gradle 属性来控制此行为:
从 android 目录直接调用融合发布任务时,可通过 -P Gradle 属性传入这些参数:
注意:融合之前,请检查宿主应用已经使用了哪些库。如果宿主自带 Glide(
expo-image会融合 Glide)、Jetpack Compose(@expo/ui)或类似库,融合 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.