npx expo run:android
명령어로 안드로이드 앱을 빌드하고 런을 했다.
그런데 다음과 같은 오류가 발생했다.
// 터미널에서
Failed to stop dev server (bundler: metro)
CommandError: Timeout waiting for 'metro' dev server to close
// 에뮬레이터에서
This development build encountered the following error.
The development server returned response error code: 404
우선 터미널에서 보았을 때
npx expo run:android
명령어 이후에 빌드는 성공했다.
BUILD SUCCESSFUL in 22s
그리고
Waiting on http://localhost:8081
› Opening exp+onestep://expo-development-client/(생략) on Galaxy_S24_API_33
이후에 위와 같은 오류가 난 것이다.
Failed to stop dev server (bundler: metro)
CommandError: Timeout waiting for 'metro' dev server to close
그러면 일단 지금 보기에 앱 자체는 잘 실행되었고, development build인 expo앱을 특정 URI를 통해서 에뮬레이터에서 실행하는 것으로 보인다.
그런데 이 이후에 dev server를 멈추는데에 실패했고 번들러가 metro라고 한다.
metro dev server가 닫히기 기다리다가 타임아웃이 됐다고 한다.
그런데 찾아본 바로는 react native가 개발 모드에서 동작하는 방식은 메트로 개발 서버가 로컬에 떠있고 에뮬레이터 내 앱에서 이 로컬 개발 서버에 JS 파일을 요청하면 메트로가 응답하는 식이다. 🔗 [React Native] Metro? 메트로가 뭐야?
🔗 GPT 대화 🔗 Metro docs
그런데 앱을 실행하는데 필요한 metro를 오히려 종료하는데 실패했다니 왜 그럴까
일단 난 npx expo run:android가 어떤 과정을 수행하는지 몰라서 조금 찾아보았다. expo-cli/src/run
// packages/@expo/cli/src/run/index.ts
// ...은 생략한 부분
...
export const expoRun: Command = async (argv) => {
...
switch (platform) {
case 'android': {
const { expoRunAndroid } = await import('./android/index.js');
return expoRunAndroid(argsWithoutPlatform);
}
...
npx expo run:android를 했을 때 expoRunAndroid가 실행된다.
./android/index.js에서는 결국 runAndroidAsync.ts에서 runAndroidAsync를 임포트하여 실행한다.
// packages/@expo/cli/src/run/android/runAndroidAsync.ts
// ...은 생략한 부분
...
export async function runAndroidAsync(projectRoot: string, { install, ...options }: Options) {
...
const manager = await startBundlerAsync(projectRoot, {
port: props.port,
// If a scheme is specified then use that instead of the package name.
scheme: (await getSchemesForAndroidAsync(projectRoot))?.[0],
headless: !props.shouldStartBundler,
});
...
await manager.getDefaultDevServer().openCustomRuntimeAsync(
'emulator',
{
applicationId: props.packageName,
},
{ device: props.device.device }
);
if (props.shouldStartBundler) {
logProjectLogsLocation();
} else {
await manager.stopAsync();
}
}
...
runAndroidAsync에서 metro 번들러 관련된 부분만 모아보면 위와 같다.
여기서 metro를 종료시킬 때는 props.shouldStartBundler가 false 일 때이다.
그리고 GPT에게 물어보니 props.shouldStartBundler가 false가 될 경우는 다음과 같다고 한다.
GPT가 말해준 네 가지 경우 중 1번은 apk가 아니므로 탈락, 2번도 릴리즈 모드가 아니니 탈락, 3번과 같은 옵션은 주지 않았으니 탈락...해서 4번만 남았다. 8081 포트가 이미 사용중이라는건가? 하고
lsof로 8081포트를 사용중인 프로세스를 보았더니 다른 프로세스가 8081을 쓰고 있더라
그런데 내 기억에 이럴 경우 다른 포트를 쓰겠냐고 물어봤던 것 같기도 해서 npx expo start로 개발 서버만 켜보았더니 이럴수가 8081은 이미 쓰고 있다며 8082 포트로 열어도 되냐고 물어본다.
일단 8081 포트를 쓰는 프로세스를 죽이고 다시 명령어를 실행하니 잘 된다.
expo run 명령어로는 8081 포트가 사용 중일 때 다른 포트로 열지 물어보지 않는가