☰
React Native + Expo 开发 Android 踩坑指南:从环境到上架
2026/10/11 12:06:04 网站建设 项目流程

1. 项目踩坑复盘:React Native + Expo 开发 Android 的正确打开方式

接手一个 Android 项目,技术栈锁定 React Native + Expo,这几乎是我最近两年做移动端最省心的一条路。很多人听到 Expo 第一反应是“玩具”,只能做 demo,不能做正式项目,但实际上 Expo 现在已经有一套相当成熟的工程化体系,尤其对 Android 开发来说,省掉的坑比想象中多得多。这篇文章不打算贴一堆官方文档,而是把我从初始化、写页面、接原生能力、到打包上架整个流程里踩过的坑和沉淀下来的经验梳理一遍,给准备用这套技术栈开工的开发者一个真实参考。

1.1 为什么不用纯原生或者裸 React Native

先说选型逻辑。Android 原生开发(Kotlin/Java)当然能做很底层的性能调优,但问题是团队资源有限时,一套代码只服务一个平台,成本是翻倍的。React Native 的价值在于跨平台,但是裸 React Native 需要自己配置 Android 工程、处理 Gradle 版本、维护原生依赖,新手很容易在环境阶段就劝退。Expo 的价值就是把这一层工程复杂度封装掉,你大部分时间只需要写 JavaScript/TypeScript,不需要碰 Android 原生目录,也不需要理解 Gradle 和 AndroidX 的依赖冲突。

我接手过一个某公司的小型移动端项目,Android 和 iOS 都要上,排期只有三个月,团队里没人深入搞过原生开发。如果选裸 React Native,光是环境搭建和原生依赖配置就得耗掉一两个星期,而且大概率会在构建阶段被各种版本号折磨。换成 Expo 之后,环境问题基本消失,npx expo start起来,手机装 Expo Go 扫一下二维码就能看到界面,这个即时反馈对开发效率的提升非常明显。所以如果你的项目不是强依赖某些特殊原生模块,Expo 完全可以承担正式产品的开发任务。

1.2 Expo 托管工作流与开发构建工作流怎么选

很多人在选 Expo 时会纠结“托管工作流”和“开发构建工作流”的区别。早期的 Expo 是纯托管模式,你只能用 Expo SDK 里提供的能力,如果想加自定义原生代码,就要弹出(eject)成裸 RN 项目,那次弹出基本等于告别托管。但现在 Expo 主推的是 development build + 本地原生模块的方式,你不需要永久锁定在 Expo 的封闭环境里,而是可以随时通过 prebuild 生成 android 原生项目,然后在上面做定制,再回到 Expo 的开发体验。

我的建议是:默认选择 development build 工作流。也就是说,你在项目里用expo-dev-client而不是单纯依赖 Expo Go。好处很明显:Expo Go 受限于包含的 SDK 能力,很多第三方原生库(比如带定位后台的、带特定蓝牙协议的)在 Expo Go 里跑不了,但 development build 可以把你需要的原生模块真正编译进开发 App。开发时你既享受 Expo 的快速刷新,又能用到完整原生能力,到了上架阶段再用 EAS Build 或者本地构建出正式包。

1.3 哪些项目适合用 Expo 做 Android

说实话,Expo 不是万能的,选之前要先判断项目类型。我个人的判断标准是:业务复杂度高于原生复杂度,这是 Expo 的甜蜜点。比如电商 App、社交类应用、信息管理工具、企业内部应用,大多数页面都是列表、表单、图片展示、网络请求,这些场景 Expo 完全 hold 住。如果项目里有大量自定义视频编解码、高性能游戏渲染、底层硬件通信,那还是得走原生或者裸 React Native,别在 Expo 上硬磨。

还有一个很现实的场景是个人开发者做 MVP。我需要快速验证产品想法,不想写两套原生代码,也不想花太多时间在配置上,Expo 无疑是效率最高的方案。我见过不少个人开发者的应用,从零到上架安卓市场只用了不到一个月,这在纯原生时代很难想象。所以我的结论是:先评估需求,如果你的 App 原生需求不重,React Native + Expo 就是性价比极高的组合。

2. 环境准备:从零跑通一个 Expo Android 项目

环境这关是很多人容易卡住的地方,但说实话 Expp 已经把这关的难度降到很低了。只要把几个基础工装好,后面基本顺畅。我会按我自己的实际操作流程来拆解,尽量把版本和参数说得具体一些。

2.1 开发机依赖:Node、JDK、Android Studio 版本怎么定

在开始 Android 开发之前,建议把 Node.js、JDK 和 Android Studio 装齐。Node 版本尽量用 18 及以上,因为新版 Expo SDK 和 Metro 打包器都依赖较新的 Node API。JDK 方面,我用的是 JDK 17,Expo 官方也推荐这个版本,太旧容易在 Gradle 编译时报错,太新(比如 21)反而可能与 Android Gradle Plugin 不完全兼容。Android Studio 主要用来安装安卓 SDK 和模拟器,但如果你打算用真机调试,Android Studio 也不是必须的,我更多是把它当作 SDK 管理工具。

装完这些之后,需要在环境变量里配置 Android SDK 路径。Linux 或 macOS 一般是ANDROID_HOME,Windows 则是在用户变量里新建ANDROID_HOME指向你的 SDK 目录。这个步骤很多人会漏掉,结果跑npx expo run:android时找不到 SDK,其实问题不在 Expo,只是系统找不到 Android SDK。配置好之后在终端里跑一下echo $ANDROID_HOME验证输出,能打印出路径就说明没问题。

2.2 初始化项目:模板选择与目录结构解读

初始化项目直接用官方脚手架,命令是:

npx create-expo-app@latest MyProject

如果你想要 TypeScript 环境,默认模板就是 TypeScript,这个不用额外选。初始化完成后,项目里面会有app.json、App.tsx、assets目录等。如果用 Expo Router,目录结构会变成app/目录放页面,这个对页面多的大型项目很友好。

以我常用的默认模板为例,核心目录是这样的:

  • app/:页面目录,Expo Router 约定式路由,文件名决定路由路径。
  • assets/:存放图片、字体、音频。
  • components/:自建组件(如果模板里有)。
  • constants/:主题色、布局常量。
  • package.json:依赖管理,注意这里面的依赖版本最好都让 Expo 统一管。
  • app.json:应用配置,比如应用名、包名、图标、启动图。

第一次跑项目,我习惯先执行npx expo start,然后把手机和电脑连到同一 WiFi,用 Expo Go 扫二维码。如果是在 Android 模拟器上跑,直接按a键就能自动打开。Expo 的这个“扫码即看”能力确实是降低门槛的核心。

2.3 真机调试与连接细节

真机调试里最容易踩的坑是手机连不上 Metro 开发服务器。正常情况下,只要手机和电脑在同一个局域网,Expo Go 会自动发现项目。但我遇到过几次公司网络隔离或路由器禁止设备互访的情况,这时候用 USB 线连接更好。Android 手机开启 USB 调试后,在终端执行:

adb reverse tcp:8081 tcp:8081

就能把手机的 8081 端口反向代理到电脑上,然后扫码访问localhost:8081,即使不在同一 WiFi 也能调试。这个方法在开发阶段非常实用,我用了很多次,比折腾路由器快多了。

如果你需要自定义原生模块,就不能直接用 Expo Go 了。这时可以先跑一次npx expo prebuild生成 android 原生目录,或者直接用npx expo run:android编译安装 development build。这种方式第一次构建会比较慢,因为要下载 Gradle 依赖,但构建一次之后,后续增量更新会快很多。

3. 核心开发与实现细节:从页面到原生能力

当项目跑通之后,重点就转到怎么写业务代码。这里我挑几个开发中绕不开的模块来讲,包括导航、状态管理、样式适配和原生能力接入。

3.1 导航方案:Expo Router 的约定式路由

如果项目是多页面的,推荐直接用 Expo Router,因为它是基于文件系统的路由,页面文件放在app/目录下自动形成路由关系。比如我在app/下建一个(tabs)目录,里面放index.tsx、settings.tsx,就自动形成一个底部 Tab 导航,不需要手动配置路由表。

示例,一个简单的底部 Tab 配置:

// app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { return ( <Tabs> <Tabs.Screen name="index" options={{ title: '首页' }} /> <Tabs.Screen name="settings" options={{ title: '设置' }} /> </Tabs> ); }

用 Expo Router 的好处是,页面间跳转直接用<Link href="/detail">组件或者router.push()方法就行,而且传参、动态路由都支持。实际项目中我一般搭配Stack来做层级页面,比如从首页进入详情页,只需要在根布局上声明<Stack>,路由层级会自动处理。这套方案对前端开发者特别友好,因为它结构很像 Web 项目,你不需要理解原生导航栈的细节。

3.2 状态管理与数据请求

状态管理我推荐轻量的方案。如果是中小型项目,直接用 Zustand 就够了,代码量少,心智负担低,没有 Redux 那么多样板代码。核心思路是:全局需要跨页面共享的登录状态、用户信息、主题配置放进 store,其他页面级状态留在组件内部。

安装 Zustand 后的一个非常简的数字:

import { create } from 'zustand'; const useUserStore = create((set) => ({ user: null, login: (userInfo) => set({ user: userInfo }), logout: () => set({ user: null }), }));

数据请求则要根据网络环境统一处理。我用 fetch 或 axios 都行,但建议封装一个基础请求函数,统一处理 baseURL、超时时间和鉴权 token。Android 开发中特别要注意明文 HTTP 请求的问题:安卓 9 以后默认禁止 HTTP 明文请求,只允许 HTTPS。如果你在调试阶段必须访问公司内网 HTTP 接口,需要在 Expo 里面配置usesCleartextTraffic,或者使用开发构建包时在 AndroidManifest 里允许明文流量。这个坑在真机调试时非常常见,页面显示白屏或者请求失败,第一反应往往是接口写错了,其实是系统的明文限制。

3.3 样式布局与 Android 适配细节

React Native 的样式系统基于 Yoga 引擎的 flexbox 布局,大部分 Web 经验可以迁移,但有一些 Android 特有的细节需要注意。最典型的是阴影样式:iOS 上可以用shadowColor、shadowOpacity等属性,但 Android 上这些属性默认不生效,必须用elevation来模拟阴影。如果你写了一个卡片组件,只给 iOS 写了阴影,Android 上会变成没有层次感的平板,所以每个阴影元素上都要同时写上:

{ shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 4, shadowOffset: { width: 0, height: 2 }, elevation: 4, }

另一个细节是安全区域。Android 全面屏和刘海屏很多,如果你的页面使用了绝对定位底部按钮,很容易被系统手势条遮挡。一般用react-native-safe-area-context来包裹根布局,然后用useSafeAreaInsets获取底部内边距。Expo 项目里通常已经内置了这个库,直接用就行。

3.4 原生能力接入:相机、文件存储与推送

接入原生能力是 Expo 的强项。比如打开相机拍照,只需要安装expo-camera,在组件里调用requestCameraPermissionsAsync请求权限,然后用CameraView渲染取景画面。整个过程不需要写一行 Java/Kotlin。

以访问相册选择图片为例,expo-image-picker是我用得比较多的库:

import * as ImagePicker from 'expo-image-picker'; const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, allowsEditing: true, quality: 0.8, }); if (!result.canceled) { const uri = result.assets[0].uri; // 上传或预览 }

需要留意的是,这些原生能力在 Expo Go 里很多已经支持,但如果是自定义原生模块组合,还是建议用 development build 测试,避免 Expo Go 里缺少某个原生库。

推送通知方面,Expo 提供expo-notifications,可以统一处理 Android 和 iOS 的消息展示。不过 Android 推送最终如果要走厂商通道,还是要接入官方推送服务,Expo 本身只是负责把通知的展示和调度封装好。实际项目中,我把expo-notifications用于本地通知的文案展示,网络推送则直接用后端下发 token 到推送服务,这样兼顾了效率和灵活性。

4. 构建打包上架:从 debug 到 release 的必经之路

很多开发者在 demo 阶段很顺利,一走到打包上架就懵了,因为要处理签名、AAB/APK、版本号、市场审核等一系列问题。这也是 Expo 真正体现价值的地方,它把最痛苦的构建工程变成了云端配置。

4.1 使用 EAS Build 构建 Android 安装包

EAS Build 是 Expo 官方的云端构建服务。首次使用前需要先安装命令行工具并登录:

npm install -g eas-cli eas login

然后在项目根目录下运行:

eas build:configure

这个命令会在项目中生成一个eas.json文件,里面定义了不同构建 profile。最常用的两个 profile 是preview和production。preview 用来生成可直接安装的 APK,给测试同事验证;production 用来生成上架用的 AAB。

构建命令:

eas build -p android --profile preview

执行之后,EAS 会把你的项目和配置上传到云端,然后在一台虚拟机上跑完整的原生构建。构建完成后会生成一个下载链接,手机上直接安装就能运行。这个过程最大的好处是不需要你自己配置 Android SDK、Gradle 和签名,一切都由 EAS 托管处理,对于不熟悉原生构建的开发者来说非常省心。

4.2 签名配置与版本号管理

Android 应用必须用签名证书来标识开发者身份。第一次用 EAS Build 时,它会自动生成一个签名证书并托管在 Expo 服务器上。如果你没有指定证书,EAS 会使用随机生成的一套证书,这就导致后续重新构建时可能出现签名不一致的问题。

我个人的习惯是:在eas.json里为 production profile 显式配置credentials,使用本地生成的自有密钥:

{ "cli": { "appVersionSource": "remote" }, "build": { "production": { "android": { "buildType": "app-bundle" } } } }

在生成密钥时,可以用 Android Studio 自带的工具或者在命令行执行生成命令,然后通过 EAS 上传。版本号管理方面,安卓要求versionCode是单调递增的整数,每次上架必比前一次大。很多开发者在第二次上架时忘记改versionCode,结果被市场平台拒绝。在 Expo 项目里,我用app.json里的android.versionCode来控制,每次发版前都检查一次。

4.3 上架应用市场前的检查清单

这里只说安卓市场上架,各个平台的审核要求会略有差异,但核心检查项都差不多。首要是隐私政策。应用如果采集任何用户信息,哪怕只是设备标识和日志,都必须有一个可供访问的隐私政策页面。Expo 项目里可以在应用中放一个静态页面,同时把地址填到市场后台。

其次是权限说明。安卓系统会在用户安装时展示权限列表,如果你实现了接入相机、定位、存储这些功能,市场后台也会要求提交权限使用说明。我在提交前会仔细核对app.json中的所有权限,比如expo-camera会自动加入相机权限,但如果你的业务根本没有用到相机,多余权限会影响审核通过率。

还有一点很容易忽略:64 位支持。处理器架构兼容性现在要求比较严格,如果你的 APK 只包含 32 位 so 库,可能没法上架。Expo 构建的 release 包默认支持 64 位,但如果你从网上找了一个老的 RN 第三方库,它只放了armeabi-v7a的 so 文件,就会导致包体内缺少arm64-v8a,审核直接发回。遇到这种情况,要么升级这个库,要么用react-native-skia之类的现代替代品,避免停在老版本上。

5. 高频问题与调试实录:Android 上的那些“坑”

写代码最耗时的不是写新功能,而是排查旧问题。下面这几个问题是我在 React Native + Expo 开发 Android 时遇到频率最高的,每个都对应一个可以直接抄的解决方案。

5.1 真机调试连不上 Metro 开发服务器

现象是手机打开 Expo Go 后一直停留在加载页,控制台提示Could not connect to development server。第一个排查点是手机和电脑是否在同一网络下,但很多时候网络没问题也连不上,这时用 USB 调试加端口反向代理最靠谱。

adb devices adb reverse tcp:8081 tcp:8081

执行后再在 Expo Go 里手动输入地址exp://localhost:8081,通常就能连上。如果还是不行,检查电脑防火墙是否放行了 8081 端口,Windows 上默认会弹防火墙窗口,记得允许 Node.js 和 Java 进程访问网络。另一个容易忽略的是,Metro 默认端口可能被其他程序占用,如果 8081 被占了,Expo CLI 会提示换一个端口,这时需要把手机上的地址也改到对应端口。

5.2 白屏与页面加载缓慢

Android 上出现白屏,先分情况:是启动时就白屏,还是页面跳转后白屏?如果是启动白屏,多半是因为在 JS Bundle 还没有下载完成时原生应用已经启动了,对于 development build 一般等一会儿就好。如果是 release 包白屏,大概率是打包时资源文件没打进去,或者某个原生模块在 release 模式下没有初始化成功。

加载缓慢的问题,常见原因是图片未压缩和列表未优化。Android 手机的内存和 CPU 性能参差不齐,如果列表用ScrollView放大量图片,掉帧和加载慢非常明显。我一般用FlatList替代滚动容器,并设置initialNumToRender和maxToRenderPerBatch来限制渲染数量。图片则用expo-image做裁剪和缓存,加载速度会改善很多。

5.3 第三次启动后本地通知不触发

expo-notifications这个库在 Android 上有一个特殊之处:从 Android 8 开始通知必须通过通知渠道(channel)发送,如果没有创建渠道,通知会被静默丢弃。Expo 的文档里其实提到了如何设置渠道,但很多新手会忽略,结果本地通知第一二次能弹出来,后面突然不弹了,其实就是渠道配置丢了。

解决方案是在expo-notifications的配置里预创建一个 channel,比如:

import * as Notifications from 'expo-notifications'; Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowBanner: true, shouldShowList: true, shouldPlaySound: true, shouldSetBadge: false, }), });

然后在应用启动后调用setNotificationChannelAsync,设置渠道 id、名称和重要性。这个步骤做完,通知就稳定了。这个坑我在正式环境里遇到过,后来排查了很久才发现是渠道没设置。

5.4 权限请求与拒绝后的流程

Android 权限机制比 iOS 更碎片化,不同厂商的 ROM 对权限管理策略不同。比如小米、华为等机型,即使你在 App 内请求权限,系统弹窗也可能被默认关掉,或者用户拒绝后不再弹窗。我的做法是:在权限被拒绝后,不要直接重复请求,而是弹一个自定义引导页,解释权限用途,然后跳转到系统设置页面让用户手动打开。

在 Expo 中可以用Linking.openSettings()打开应用设置页,加上权限状态判断,形成一个完整的引导流程。这在真机上适配度比较高,基本能覆盖绝大多数国产 Android 机。

6. 一些很难在文档里找到的个人心得

最后这部分不打算展开太多,纯粹分享一些在真实开发中得来的经验,希望能帮你省掉一些不必要的弯路。

第一,尽量用npx expo install来安装依赖,而不是直接用 npm。因为 Expo SDK 和它的第三方原生库版本是绑定的,expo install会帮你自动匹配当前 SDK 所支持的版本范围,避免不知不觉装了一个高版本库导致原生模块冲突。我之前有过一次直接npm install某个库的最新版,结果编译时报一堆 native 错误,最后npx expo install重装才解决。

第二,不要一上来就把所有页面都塞到 Expo Router 的根级导航里。项目变大后,会把路由目录拆分得清晰一点,比如按业务模块分目录,每个模块内有自己的栈导航,这样既方便维护,也能减少单个导航容器里的层级复杂度。

第三,Android 模拟器虽然方便,但它与真机的系统行为差异其实挺大,特别是权限弹窗、后台运行限制、通知渠道这些。建议在开发中后期,至少找一两台主流品牌的真机做回归测试。我吃过一次亏,模拟器上一切正常,上到真机后发现通知完全收不到,排查下来才发现是厂商后台省电策略限制了应用自启动。

第四,关于包体积。Expo 构建的应用默认包含 Hermes 引擎和一堆基础库,体积比纯原生大一些,这是可以接受的。但如果特别在意体积,可以在eas.json里开启压缩,或者只打包arm64-v8a架构。这样可以减小不少体积,前提是你的目标用户设备都是 64 位架构。现在新出的安卓机基本都支持,所以只保留 64 位是合理选择。

React Native + Expo 做 Android 开发,给我的整体感觉是:它已经把工程复杂度和原生构建的门槛压得非常低,哪怕你没有原生基础,也能在很短时间做出一个能上架的应用。如果你正在用它开发项目,或者正准备从原生转过来,不妨像我一样,先把环境、选型这些基础打牢,后面真正写业务时就会顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询