【Bug已解决】[Mobile] unimodule.json makes Expo exclude onnxruntime-react-native from autolinking - Nati…
【Bug已解决】[Mobile] unimodule.json makes Expo exclude onnxruntime-react-native from autolinking - NativeModules.Onnxruntime is null 解决方案
一、现象长什么样
在 React Native + Expo 项目里集成 onnxruntime-react-native(ORT 的 RN 原生模块),iOS/Android 上运行时直接崩或拿不到原生模块:
import { NativeModules } from 'react-native';
console.log(NativeModules.Onnxruntime); // null !
// 后续 session.create 调用因 NativeModules 为 null 而崩溃
最小信号:
原生模块没被链接 -> NativeModules.Onnxruntime === null
Expo 自动链接(autolinking)名单里没有 onnxruntime-react-native
报错/崩溃在模块加载阶段,不是推理阶段
注意:这是 Expo 自动链接配置问题,不是 ORT 推理 bug。Expo 的 autolinking 依据某些配置文件决定哪些原生模块参与链接,onnxruntime-react-native 被漏掉了。
二、背景
React Native 0.60+ 用 autolinking(自动链接):你 npm install 一个带原生代码的库,构建时 RN 的 autolinking 工具会扫描 node_modules 里各包的配置文件,自动把它们的 iOS pod / Android gradle 依赖加进工程,不用手动 react-native link。
Expo 在此基础上有一套自己的模块配置(expo.modules、unimodule.json / expo-module.config 等),用来决定哪些库被 Expo 的预构建(prebuild)和原生宿主包含。
问题就出在:onnxruntime-react-native 这个包没有被正确登记进 Expo 的 autolinking 配置,于是:
- iOS 上,
pod install时它的 pod 没被加进Podfile,原生模块不编译进 app; - Android 上,它的
react-native-config/settings.gradle条目缺失,不编进 apk; - 运行时
NativeModules.Onnxruntime自然是null,因为原生端根本没这个模块。
unimodule.json 在这里是 Expo 旧版模块清单的格式(新版用 expo-module.config.js)。如果这份清单里没有列 onnxruntime-react-native,Expo 的 autolinking 就会把它排除。
三、根因
根因是 Expo 的 autolinking 配置(unimodule.json / 模块清单)没有包含 onnxruntime-react-native,导致预构建/链接阶段把它排除,原生模块不进 app,运行时 NativeModules.Onnxruntime 为 null:
- 清单漏登记:
onnxruntime-react-native包本身可能没在expo-module.config.js里声明自己是 Expo 兼容模块,或宿主项目的unimodule.json没列它,autolinking 扫不到。 - autolinking 排除:Expo prebuild 依据清单生成原生工程,缺登记 -> pod/gradle 不生成 -> 原生模块缺席。
- 运行时 null:JS 侧
NativeModules.Onnxruntime依赖原生端导出该模块,原生端没编进去,自然null,任何调用崩。 - 不是 ORT 推理错:模块根本没加载,连 session 都建不了。
所以这不是数值错,而是 Expo 原生模块链接配置缺口,导致 ORT RN 模块没编进 app。
四、最小可运行复现
下面用 JS 伪代码模拟“autolinking 清单过滤掉未登记模块,导致 NativeModules 为 null”:
// Expo autolinking 依据清单决定链接哪些模块
const EXPO_MODULE_MANIFEST = {
modules: [
"expo-camera",
"expo-file-system",
// onnxruntime-react-native 没在这里 -> 被排除
],
};
// autolinking 生成原生依赖时只取清单内的
function autolink(packages) {
return packages.filter((p) => EXPO_MODULE_MANIFEST.modules.includes(p));
}
const installed = ["onnxruntime-react-native", "expo-camera"];
const linked = autolink(installed);
console.log("被链接的模块:", linked); // 不含 onnxruntime-react-native
// 运行时原生端没编入 -> NativeModules 为 null
const NativeModules = {};
console.log("NativeModules.Onnxruntime:", NativeModules.Onnxruntime ?? null);
// -> null,因为没被链接
跑这个逻辑:onnxruntime-react-native 不在清单,autolink 过滤后不含它,运行时 NativeModules.Onnxruntime 为 null。这复现了“清单漏登记 -> 模块不链接 -> NativeModules 为 null”的机制。
五、解决方案(第一层:最小直接修复)
最小修复:把 onnxruntime-react-native 登记进 Expo 的 autolinking 配置,让原生模块被链接进 app。几种做法:
// 1) 在 onnxruntime-react-native 包里确保 expo-module.config.js 声明兼容
// onnxruntime-react-native/expo-module.config.js:
{
"platforms": ["ios", "android"],
"ios": { "modules": ["Onnxruntime"] }
}
// 2) 宿主项目显式包含(expo 配置)
// app.json / app.config.js:
{
"expo": {
"plugins": [
// 某些包需要在这里声明插件以触发链接
]
}
}
// 3) 重新生成原生工程
// npx expo prebuild --clean
// npx pod-install ios
这一步让 pod install / gradle 把 ORT RN 原生模块编进 app,运行时 NativeModules.Onnxruntime 不再是 null。
六、解决方案(第二层:结构性改进)
把“哪些 RN 原生模块必须被 Expo autolinking 包含”收口成唯一的配置对象 OrtExpoAutolinkPolicy,项目配置与 CI 读它:
from dataclasses import dataclass, field
from typing import Tuple
@dataclass(frozen=True)
class OrtExpoAutolinkPolicy:
"""onnxruntime-react-native 在 Expo autolinking 的单一事实来源。"""
# 必须被 autolinking 包含的 RN 原生模块
required_modules: Tuple[str, ...] = ("onnxruntime-react-native",)
# 各平台必须链接
platforms: Tuple[str, ...] = ("ios", "android")
# 包内必须声明的 expo 模块配置
package_must_declare: Tuple[str, ...] = ("expo-module.config.js",)
# prebuild 后必须验证原生模块存在
verify_after_prebuild: bool = True
def is_required(self, module: str) -> bool:
return module in self.required_modules
def describe(self) -> str:
return "onnxruntime-react-native 必须进 Expo autolinking,iOS/Android 都链接"
POLICY = OrtExpoAutolinkPolicy()
def check_manifest(manifest_modules: list, policy: OrtExpoAutolinkPolicy = POLICY) -> list:
missing = [m for m in policy.required_modules if m not in manifest_modules]
if missing and policy.verify_after_prebuild:
raise RuntimeError(f"Expo autolinking 缺失模块: {missing}")
return missing
所有 Expo 项目配置与 CI 读同一份 POLICY,ORT RN 模块不会被漏链接。
七、解决方案(第三层:断言 / CI 守护)
把“onnxruntime-react-native 被 Expo 链接、NativeModules 非空”做成断言。下面用 pytest 风格守护(复用第四节逻辑):
import pytest
def test_module_required(policy):
assert "onnxruntime-react-native" in policy.required_modules
assert policy.is_required("onnxruntime-react-native") is True
def test_platforms_covered(policy):
assert "ios" in policy.platforms
assert "android" in policy.platforms
def test_manifest_must_include(policy):
with pytest.raises(RuntimeError):
check_manifest(["expo-camera"]) # 缺 onnxruntime-react-native
def test_complete_manifest_passes(policy):
assert check_manifest(["onnxruntime-react-native", "expo-camera"]) == []
这四组断言锁住:(1) 模块必含;(2) iOS/Android 都覆盖;(3) 清单缺模块即失败;(4) 齐备通过。CI 跑通即代表 autolinking 配置不被漏。
八、排查清单
遇到 Expo 上 NativeModules.Onnxruntime 为 null:
- 看 autolinking 清单:
unimodule.json/expo-module.config.js里有没有 onnxruntime-react-native。 - 看 pod/ gradle 是否生成:
Podfile.lock/settings.gradle里有没有 ORT RN 原生依赖。 - 查包是否声明 expo 兼容:
onnxruntime-react-native里有没有expo-module.config.js。 - 临时修复:把它加进清单 +
expo prebuild --clean+pod install。 - 统一策略对象:用
OrtExpoAutolinkPolicy固化。 - CI 守护:断言模块在清单、平台覆盖、缺即失败。
- 不要手动 link 旧式:用 autolinking 正确声明,避免 RN 新版不认。
九、小结
[Mobile] unimodule.json makes Expo exclude onnxruntime-react-native from autolinking - NativeModules.Onnxruntime is null 的根因是:Expo 的 autolinking 配置(unimodule.json / 模块清单)没有包含 onnxruntime-react-native,预构建/链接阶段把它排除,原生模块不编进 iOS/Android app,运行时 NativeModules.Onnxruntime 为 null,任何调用崩溃。
最小修复是把该包登记进 Expo autolinking(包内声明 expo-module.config.js、宿主清单包含、expo prebuild --clean + pod install);结构性改进是用唯一的 OrtExpoAutolinkPolicy 固化必含模块;CI 用四组断言守护“模块必含、平台覆盖、缺即失败、齐备通过”。记住:RN 原生模块要进 Expo 的 autolinking 清单才会被链接,漏登记运行时就是 null。

更多推荐


所有评论(0)