expo自定义模块集成
项目背景
最近需要开发一个ios以及android双端的app应用呼了么。该项目是一款专注于夜间睡眠鼾声自动监测与声音记录的健康类手机应用。通过手机麦克风实时监测睡眠环境声音,采用智能声音识别算法自动检测打呼噜事件并触发录音,为用户提供全面的鼾声数据分析与睡眠质量评估。经过调研之后, EXPO 框架对于web端开发转app学习成本最小。
由于该项目需要实时监测呼声,并且智能保存带有呼声的录音片段。在基础设施逐步完善之后,端侧的模型监测推理也需要提上日程。然而当前的EXPO框架并没有用于机器学习的官方跨端推理SDK。因此需要使用EXPO框架自定义开发原生SDK集成。
由于第一次接触 Expo 原生模块开发,踩了不少坑,今天把整个排错和实现过程记录下来,希望能帮到有同样需求的朋友。
实践过程
初始化EXPO自定义模块
这里主要记录开发集成原生模块,expo项目的初始化这里不再赘述。直接从初始化自定义模块开始。
新建终端输入
1 | npx create-expo-module Calculator --local |
create-expo-module可以创建两种模块:本地模块和独立模块。
独立模块是独立的软件包。如果你想在多个应用中重复使用模块,或者放在单仓库包里,或者发布到 npm,就用独立模块。独立模块包括包元数据、各自的依赖和脚本,以及用于开发和测试模块的示例应用。
一个本地模块存在于单一的项目中。当你想为一个应用添加自定义原生代码,且不需要发布或作为单独包共享时,可以使用本地模块。本地模块使用应用的依赖和工具,并且由 Expo Autolinking 自动从项目的原生模块目录中发现。 也就是使用 –local
由于我这个是一个单体项目,并不需要多个应用使用,所以接下来我采用的是本地模块的方式
之后会出现一些配置项,主要是选择你要开发什么平台的sdk之类的,这个可以按照自己的情况选择。我这里先选择android的。
上下切换,按空格选择或者取消。回车确定。成功之后目录如下:
环境配置
由于我们需要进行原生的SDK开发,所以在进入下一步之前需要配置一下开发android必须的环境 ,如下。
JAVA
下载JDK的安装程序
链接: oracle中国
按照安装导向按照到自己的目录上,我选择的目录是本地的D盘。
配置JAVA_HOME
expo链接android sdk 模块需要JAVA的支持。默认会读取C盘上一个java目录。要改变使用JAVA的路径,就需要在环境变量当中配置JAVA_HOME,从而使得EXPO以及android sdk 优先使用环境变量中定义的JAVA。
配置如图所示即可(踩坑点1,后面踩坑记录统一讲述)
在Path路径下使用JAVA_HOME链接到bin目录
Android Studio
开发原生模块的时候需要用到android 的 sdk,以及构建工具。 这里需要注意一个点,当使用expo自定义模块的时候,自定义模块无法使用EXPO GO这个软件进行 扫码查看了(会报错)。因此还需要使用adb去做链接,或者开模拟器。 直接下载android可以快速的将这些环境都一并集齐。 这里有一个需要注意的地方,最好在初始化完毕的module模块上查看以下SDK的版本。安装android的时候选择这个的版本,可以比较好的避免有可能的版本不兼容等问题。
下载 Android Studio 的安装应用程序
选择好你的下载路径,这里不再赘述,可以像我一样规整以下目录

下载SDK
在选择安装的时候新建一个目录单独去装 studio,在同级目录下新建一个Android SDK目录,用于之后下载的SDK。
新安装的studio选择立即启动,初始化的时候studio会检测本机上有没有安卓开发的环境。没有的话会直接启动SDK Manager让你下载一个,注意,这里的SDK地址也需要另行设置,不建议装在C盘。
下载完毕之后打开设置的sdk manager 就会出现你已经安装好的sdk。(踩坑点2,在踩坑记录详细说明)
点击会看到安装的sdk


配置adb环境变量
下载完android 之后,它会将adb下载一份到你的platform-tools文件夹下

一样的,直接复制这个的地址到环境变量当中,终端输入 adb无报错即成功配置
开发流程
至此环境已经全部配备完毕。至于构筑的gradle以及kotlin编译。这些不需要我们管,在打包构建的时候expo会根据它自己的一套逻辑网上下载以及自动链接。
之前我们初始化的expo-module 就是我们之后要开发以及编写我们原生逻辑的地方。
作为开发而言,只要理清这几个文件之间的关系就可以尝试编写自己的逻辑代码。所以我们来说一下这几个文件都有什么用,是怎么组织到一块的。(以Calculator为例)
expo-module.config.json
这是模块的注册说明书,告诉 Expo 这个模块有哪些原生入口类。
这里就代表我这份代码这次开发针对的平台是android以及web端。
src/*.ts/src/*.tsx
这是 JavaScript / TypeScript 侧的桥接层,也是业务代码真正 import 的入口层。types.ts主要负责原生自定义视图的定义,职责是统一描述模块和原生视图的输入输出。他将这个View有什么props,原生模块事件以及以及js调用返回都统一绑定在这里。CalculatorModule.ts是原生模块的 TypeScript 桥接层它主要完成两个工作:
用 TypeScript 描述 JS 侧能看到的 API
比如PI、hello()、setValueAsync()。用
requireNativeModule('Calculator')去拿原生模块实例
这里的Calculator必须和 Android 原生代码里的Name("Calculator")对上。CalculatorModule.web.ts则是在web端里面的代替实现,expo会根据这个平台的不同自动了链接不同的文件填补,web的逻辑是直接写在这个文件的,不需要使用android桥接。这里不赘述
src/CalculatorView.tsx它对应的是一个”原生视图组件”,不是普通函数模块。它通过
requireNativeView('Calculator')拿到原生 View之后给 React 组件层暴露一个正常的 JSX 用法。src/CalculatorView.web.tsx同理,是web端的代替实现
android/...
这是 Android 原生实现,真正干活的 Kotlin 代码在这里。android/build.gradle
这是 Android 侧模块自己的构建配置。它的核心作用:
- 把这个模块声明为一个 Android Library,而不是一个完整 App
- 应用 Expo Modules Core 提供的 Gradle 能力
- 配置 Android 编译版本、命名空间、发布相关设置
这里有几个关键信息:
apply plugin: 'com.android.library'
说明它是库模块。
- `applyKotlinExpoModulesCorePlugin()`
说明它使用 Expo Modules API 的 Kotlin 支持。
- `useCoreDependencies()`
说明它依赖 `expo-modules-core` 提供的基础设施。
- `namespace "expo.modules.calculator"`
这是 Android 命名空间,也和后面的 Kotlin 包名保持一致。
这个文件决定的是: 这个原生模块如何被 Android 工程识别、编译和打包。
其他的不难理解,就是在ts文件行定义的那些跨端方法你要实现逻辑或者实现原生视图的地方。
EXPO通过src层给到react native一个可用方法,通过注册以及链接将这些方法的实现链接到android的逻辑来实现。
综上,以 Calculator 这个 Expo 本地模块为例,src 目录负责暴露给 JavaScript 的统一接口,android 目录负责真正的 Kotlin 原生实现,expo-module.config.json 负责告诉 Expo 这个模块应该如何被发现和注册,而 .web.ts / .web.tsx 文件则负责在浏览器环境下提供替代实现。最终,业务代码只需要 import 对应模块,就可以像调用普通 TypeScript 方法一样调用原生能力。
打包构建
开发完毕之后,expo的自定义模块需要打包构建之后才可以使用。需要注意的一点是,每次改原生代码后,记得重新构建原生 App,EXPO不支持原生代码改动之后热重载。
预构建
1
npx expo prebuild
连接上adb
打开手机开发者设置,使用USB允许调试或者使用wifi调试方式。
如果使用Wifi调试方式adb pair ip地址:端口输入配对码,配对完毕adb connect ip地址:端口号完成连接
构建
1
npx expo run:android
在这个途中会下载对应版本的构建gradle以及kotlin编译套件。(踩坑点3)前面要是踩坑的话,这一步会集中暴露出来。
等待跑码完毕,即可在手机上看到以及调试开发的app,也可以调用自己的自定义原生sdk了
踩坑记录
1. 踩坑点1:JAVA_HOME的配置
现象: 配置JAVA_HOME完成之后终端无法得到正确的使用java以及javac。
原因:Windows 10 系统使用环境变量的时候会出现两种视图,如下:

第一个视图在新建JAVA_HOME的时候,每一个的变量后面会默认给一个;分隔符。如果有这一个分隔符,在Path当中使用变量的方式引用环境变量会将;带入,从而导致路径出错。另外需要注意的是,修改了环境变量之后,记得关闭终端或者退出重新启动IDE,才能够读取最新的环境变量进行验证。
2. 踩坑点2:SDK下载
现象:使用正确版本的SDK,配置了ANDRIOD_HOME,在打包构建的时候仍然出现错误。会出现如下的错误
1 | Fix "Execution Failed for task :app:compileDebugJavaWithJavac" in Android Studio |
原因:下载的SDK缺失重要额构建jar包。确保sdk下面的目录有这些,不可缺少。
当我安装android studio的sdk时安装完毕之后内部有文件缺失,导致无法使用构建,从而报错。
3. 踩坑点3:构建途中僵尸进程
现象: 在构建 cmake的时候log出现报错**Could not connect to Kotlin compile daemon** 、 Task :react-native-screens:configureCMakeDebug[arm64-v8a] FAILED
分析: react-native-screens 的底层包含 C++ 代码,需要 CMake 和 Ninja 进行编译。它失败可能是因为刚刚的 Kotlin Daemon 崩溃导致的连锁反应。
原因:在多次尝试构建失败的时候,之前的构建deamen进程并没有被完全消灭,从而导致 Gradle 和 Kotlin 进程被卡住,从而无法加载以及正常编译。需要使用如下命令进入到android的目录之后,清除内存。之后再构架
1 | cd android |
报错通用解决方案
很多人都是使用npm来下载expo的包。 正确的应该是使用npx expo install,它会自动查找当前 Expo SDK 版本(如 SDK 52)对应的、经过验证的依赖版本。手动安装容易装上最新的(尚未适配的)版本,导致Gradle编译时疯狂窜稀。
在构建遇到问题了,可以使用expo-doctor运行来看一下。使用npx expo-doctor 下载,时候使用npx expo install --check。根据检测出来不通过的进行排查。
如果对自己的环境变量还是不放心,,可以在预构建出来的android目录下新建一个local.properties写上sdk,ndk,cmake的目录。
1 | sdk.dir=D\:\\dev\\Android\\Sdk |
总结
当使用expo自定义模块的方式构建原生模块的时候,需要保证使用构建的每一个环节使用的依赖以及sdk的版本要兼容。
了解expo是如何自动连接以及识别模块,以及RN侧与原生模块之间是怎么样的一个通路,对之后的debug以及开发认识有比较大的指引帮助。
参考资料
- Expo 官方文档: https://docs.expo.dev/modules/get-started/
- Expo Autolinking: https://docs.expo.dev/modules/autolinking/
- Expo 自定义原生代码总览: https://docs.expo.dev/workflow/customizing/
- Expo React Native + EAS 本地/云端实战真实构建避坑指南 https://juejin.cn/post/7600964907488608262

