React Native Metro 选项不全?端口冲突与缓存清理指南
2025-04-08 07:44:11
Metro Server 为何只显示部分 React Native 应用选项?
开发 React Native 应用时,你可能碰到过一个怪事:明明用差不多的方式创建了两个项目,启动 Metro Server 后,一个项目的控制台能看到完整的操作选项(比如 i
- iOS, a
- Android),另一个却只显示部分选项(比如 r
- reload, d
- Dev Menu)。这到底是怎么回事?
问题现象
假设你先后创建了两个 React Native CLI 项目(非 Expo):
npx @react-native-community/cli@latest init AwesomeProject # 假设1月21日创建
npx @react-native-community/cli@latest init PasswordGen # 假设1月22日创建
接着,你尝试分别为这两个项目启动 Metro Server 和运行 Android 应用:
对于 AwesomeProject
:
cd AwesomeProject
npx react-native start # 启动 Metro
# 在另一个终端
npx react-native run-android # 编译安装并运行 App
这时,AwesomeProject
的 Metro Server 控制台显示了预期的完整选项:
i- open on iOS
a - open on Android
r - reload app(s)
d - open Dev Menu
j - open DevTools
但当你对 PasswordGen
项目做同样的操作:
cd ../PasswordGen
npx react-native start # 启动 Metro
# 在另一个终端
npx react-native run-android # 编译安装并运行 App
PasswordGen
的 Metro Server 控制台却可能只显示了:
r - reload app(s)
d - open Dev Menu
j - open DevTools
少了 i
和 a
选项,感觉 Metro 好像没完全“认识”这个新 App。就算换台电脑,问题也可能依旧存在。
分析问题根源
这种情况通常不是 React Native 本身的 Bug,而是开发环境中的一些配置或状态冲突导致的。主要原因可能包括:
- 端口冲突或残留进程: 最常见的原因。Metro 默认使用 8081 端口。如果前一个项目的 Metro Server 没有完全关闭,或者有其他程序占用了 8081 端口,新启动的 Metro Server 可能无法正常绑定或与其他服务(如
run-android
启动的调试桥)建立完整连接。即使 Metro 尝试在不同端口启动,应用本身可能仍然配置为连接 8081,导致连接失败。 - 缓存问题: Metro、Gradle(Android 构建系统)、Watchman(文件监视器)甚至 npm/Yarn 都有自己的缓存。这些缓存如果包含了过时或冲突的信息,可能会干扰新项目的启动和连接过程。
- ADB (Android Debug Bridge) 连接混乱: ADB 负责模拟器/设备与开发机之间的通信。如果同时操作多个项目或模拟器/设备,ADB 的状态可能变得混乱,无法正确地将 Metro Server 与目标应用关联起来。
- Watchman 监视问题: Watchman 用于高效地检测文件变化。在多项目环境下,如果 Watchman 监视的文件过多或遇到权限问题,也可能间接影响 Metro 的正常运行。
解决方案
针对上述可能的原因,可以尝试以下几种方法来解决。建议按顺序尝试,通常前一两种方法就能解决大部分问题。
方案一:检查并处理端口冲突
这是首要排查的方向。确保 Metro 要使用的端口(默认 8081)是干净的。
1. 查找占用端口的进程
-
macOS / Linux:
打开终端,运行以下命令查看哪个进程占用了 8081 端口:sudo lsof -i :8081
你会看到类似
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
的输出。记下PID
列的数字。 -
Windows:
打开命令提示符 (cmd) 或 PowerShell,运行:netstat -ano | findstr "8081"
在输出结果中找到状态为
LISTENING
或ESTABLISHED
的行,记下最后一列的 PID (进程标识符)。
2. 结束占用端口的进程
-
macOS / Linux:
使用kill
命令强制结束进程(将<PID>
替换为上一步找到的数字):kill -9 <PID>
-
Windows:
打开任务管理器 (Ctrl+Shift+Esc
),切换到“详细信息”或“进程”选项卡。找到对应 PID 的进程(可能需要先在菜单“查看”->“选择列”中勾选“PID”),然后选中它,点击“结束任务”。
或者,在管理员权限的命令提示符或 PowerShell 中运行:taskkill /PID <PID> /F
3. (可选)在不同端口启动 Metro
如果 8081 端口确实被其他必要服务占用,或者你想同时运行两个项目的 Metro,可以为其中一个指定不同的端口。
-
启动 Metro 时指定端口:
cd PasswordGen npx react-native start --port 8088
这里使用了 8088 端口,你可以选择其他未被占用的端口。
-
让设备/模拟器连接新端口:
仅仅让 Metro 监听新端口还不够,你需要告诉设备上的应用去连接这个新端口。- 方法一(推荐): 如果你的 React Native 版本较新(0.60+),
run-android
或run-ios
命令通常会自动检测正在运行的 Metro Server 实例,包括非默认端口的实例,并进行配置。因此,先启动 Metro 指定端口,再运行run-android
或run-ios
通常就能工作。 - 方法二(手动 ADB reverse): 如果自动检测不生效,或者你想更明确地控制,需要手动设置端口转发。确保你的设备或模拟器已连接 (
adb devices
能看到)。
这个命令告诉设备,将发往设备本地 8088 端口的 TCP 请求转发到开发机的 8088 端口。对于 iOS,端口转发通常由adb reverse tcp:8088 tcp:8088
run-ios
处理,手动配置较少见。
- 方法一(推荐): 如果你的 React Native 版本较新(0.60+),
安全建议
- 结束进程时要小心,确认你结束的是不再需要的 Metro 或 Node 进程,而不是系统关键进程。
- 如果使用非默认端口,确保该端口未被防火墙阻止。
进阶使用技巧
adb reverse --list
可以查看当前设置的所有端口转发规则。adb reverse --remove <DEVICE_PORT>
或adb reverse --remove-all
可以移除转发规则。- 当你确定只需要一个 Metro 实例时,养成彻底关闭前一个实例(在 Metro 控制台按
Ctrl+C
并确认退出)的好习惯,能有效避免端口冲突。
方案二:清理各种缓存
缓存是性能优化的好东西,但有时也会变成问题的源头。清理缓存可以解决由过时或损坏数据引起的问题。
1. 清理 Metro 缓存
-
使用
--reset-cache
启动:
这是最直接的方式,在启动 Metro 时告诉它忽略现有缓存。npx react-native start --reset-cache
或者,结合指定端口:
npx react-native start --port 8088 --reset-cache
-
手动删除缓存文件:
Metro 的缓存通常位于系统的临时目录下。- macOS:
rm -rf $TMPDIR/react-*
- Linux:
rm -rf /tmp/react-*
- Windows: 通常在
%TEMP%\react-native-packager-cache-*
或类似路径,可以在文件管理器中搜索react-native-packager-cache
并删除相关文件夹。
- macOS:
2. 清理 Android 构建缓存 (Gradle)
Android 项目构建时会产生大量缓存。
cd android
./gradlew clean # 在 macOS/Linux 上
# 或者
gradlew.bat clean # 在 Windows 上
cd ..
这会清理掉 android/app/build
目录下的编译产物。更彻底的清理可以删除 ~/.gradle/caches
目录,但下次构建时间会显著增加。谨慎操作。
3. 清理 npm/Yarn 缓存和 node_modules
依赖问题也可能导致奇怪的行为。
rm -rf node_modules # 或者用 rimraf node_modules (跨平台)
npm cache clean --force # 如果用 npm
# 或者
yarn cache clean # 如果用 yarn
npm install # 重新安装依赖
# 或者
yarn install
4. 重置 Watchman 状态
Watchman 用于监控文件变更。
watchman watch-del-all # 删除所有监视项
watchman shutdown-server # 关闭 Watchman 服务(它会在需要时自动重启)
进阶使用技巧
- 理解不同缓存的作用:Metro 缓存 JS 包和转换结果;Gradle 缓存编译后的 Java/Kotlin 代码和依赖;npm/Yarn 缓存下载的包。清理要有针对性。
./gradlew cleanBuildCache
可以尝试清理 Gradle 的构建缓存,比clean
更深入一些,但可能需要较新版本的 Gradle。- 对于大型项目,频繁清理
node_modules
和gradle clean
会拖慢开发流程。优先尝试 Metro 的--reset-cache
。
方案三:确保没有旧进程残留
有时,即使你在终端按了 Ctrl+C
,Metro Server 或相关的 Node.js 进程也可能没有完全退出,变成了“僵尸进程”。
1. 查找 Node 进程
-
macOS / Linux:
ps aux | grep node
查找与 React Native 或 Metro Server 相关的进程。留意那些看起来像是在运行 Metro 或你的项目脚本的行。
-
Windows:
打开任务管理器,在“详细信息”或“进程”里查找node.exe
。注意区分,你可能有其他 Node.js 应用在运行。
2. 结束相关进程
-
macOS / Linux:
使用kill <PID>
或kill -9 <PID>
(强制)结束找到的可疑 Node 进程 PID。 -
Windows:
在任务管理器中选中目标node.exe
进程,点击“结束任务”。
安全建议
pkill node
或killall node
这类命令会杀死所有 Node.js 进程,可能中断你正在进行的其他工作,务必谨慎使用。最好是根据ps
或任务管理器的信息,精确地杀死目标进程。
方案四:重置 ADB 连接
如果怀疑是设备连接或 ADB 本身出了问题。
adb kill-server # 关闭 ADB 服务
adb start-server # 重新启动 ADB 服务
adb devices # 检查设备是否重新连接成功
之后再尝试 npx react-native run-android
。
方案五:检查和重置 Watchman
虽然不如端口和缓存常见,但 Watchman 问题也值得检查。
# 先尝试重置
watchman watch-del-all
watchman shutdown-server
# 如果问题依旧,并且怀疑是 Watchman 安装问题
# 可以尝试卸载重装 (以 brew 为例)
brew uninstall watchman
brew install watchman
预防措施和调试思路
- 尽量一次只运行一个项目: 如果不需要同时调试两个 RN 项目,养成在切换项目前彻底关闭前一个项目的 Metro Server (
Ctrl+C
) 和相关模拟器/应用的习惯。 - 为不同项目使用不同端口: 如果确实需要同时运行,从一开始就规划好,使用
--port
参数为每个项目分配不同的端口,并确保设备连接到了正确的端口(通常通过先启动 Metro 再运行run-android/ios
实现)。 - 善用清理命令: 遇到奇怪的构建或连接问题时,
--reset-cache
和gradle clean
是你的好朋友。 - 观察 Metro 输出: Metro Server 启动时的日志信息其实包含了它正在使用的端口、是否找到了设备等关键信息。仔细阅读这些输出,有助于定位问题。
- 隔离变量: 如果上述方法都无效,尝试创建一个全新的 React Native 项目,看它是否能正常工作。如果新项目正常,说明问题可能出在旧项目的特定配置或代码里。如果新项目也有问题,那环境问题的可能性更大(比如 Node 版本、全局安装的 CLI 工具、系统配置等)。
通过系统地排查端口、缓存、残留进程和 ADB 连接,大部分 Metro Server 无法完全识别应用(缺少 a
/i
选项)的问题都可以得到解决。保持开发环境的清洁和良好的操作习惯是避免这类问题的关键。