홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

Expo dev-client로 안드로이드 실기기에 앱 올리고 Metro 붙이기

정기창·2026년 7월 27일

3부작 「맥에서 Xcode 없이 안드로이드 앱 실기기 테스트하기」 중 2부입니다. 1부 「맥에서 Xcode 없이 안드로이드 개발환경 세팅하기 (Homebrew만으로)」에서 이어집니다.

1부에서 Homebrew만으로 안드로이드 개발환경을 세팅했으니, 이제 Expo 앱을 실기기에 올리고 Metro를 붙이는 일만 남았습니다. 간단할 줄 알았는데 몇 군데서 막혔고, 막힐 때마다 원인이 제 예상과 달랐습니다. 실기기 빌드부터 pnpm 모노레포의 Metro 설정까지, 막혔던 지점을 순서대로 적어 둡니다.

ios/android 폴더는 소스가 아니라 생성물입니다

처음 저지른 실수는 네이티브 파일을 직접 고친 것이었습니다. 권한 하나를 추가하려고 AndroidManifest.xml을 손으로 수정했는데, 나중에 prebuild --clean을 한 번 돌리자 그 수정이 통째로 사라졌습니다.

Expo에는 CNG(Continuous Native Generation)라는 원리가 있습니다. ios/와 android/ 디렉토리는 app.json과 config plugin으로부터 매번 새로 생성되는 산출물이라는 뜻입니다. 그래서 이 두 폴더는 .gitignore에 넣고 정본은 app.json 하나로 둡니다. 권한이나 값도 네이티브 파일이 아니라 app.json의 permissions·infoPlist·plugins로 표현해야 prebuild --clean 재생성에도 살아남습니다.

첫 빌드는 7~8분, 그다음부터는 빠릅니다

실기기에 올리는 명령 자체는 한 줄입니다.

npx expo run:android   # = npm run android

처음 이 명령을 돌렸을 때 7~8분이 걸렸습니다. 멈춘 줄 알고 중간에 끊을 뻔했는데, 그동안 gradle 배포판을 내려받고 의존성을 받고 NDK의 CMake로 네이티브 코드를 컴파일하는 중이었습니다. 한 번 캐시가 쌓이면 증분 빌드는 1분 20초 정도로 짧아집니다. 빌드가 끝나면 APK가 실기기에 설치되고, dev-client가 실행되면서 Metro에 붙습니다.

모노레포에서 Metro가 모듈을 못 찾을 때

여기서 가장 오래 막혔습니다. 빌드는 성공했는데 앱을 켜자 Unable to resolve module로 번들이 실패했습니다. 원인은 pnpm의 구조에 있었습니다.

pnpm은 react·react-native·expo 같은 공유 의존성을 저장소 루트의 node_modules에 hoisting하고 .pnpm/ 심볼릭링크로 배치합니다. 그런데 Expo가 기본으로 깔아 주는 Metro 설정은 앱 자기 자신의 node_modules만 바라봅니다. 그래서 루트로 올라간 의존성을 찾지 못하는 것이었습니다. metro.config.js에 저장소 루트를 알려 주는 두 줄이 없으면 계속 실패합니다.

const workspaceRoot = path.resolve(projectRoot, '../../..'); // 모노레포 루트
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
];

참고로 Metro 0.82(React Native 0.81에 동봉) 이상은 심볼릭링크를 기본으로 해소합니다. 그래서 .pnpm/ 링크는 자동으로 resolve되고, 우리가 직접 챙길 것은 위의 watchFolders와 nodeModulesPaths 두 가지입니다.

실기기는 LAN IP로 붙습니다

Metro는 개발용 서버라 실기기가 네트워크로 접속해야 합니다. 폰과 맥이 같은 Wi-Fi 서브넷에 있어야 하고, Metro를 LAN에 노출시켜 띄웁니다.

EXPO_PUBLIC_API_URL=http://192.168.0.10:4000 npx expo start --dev-client --port 8081

여기서 192.168.0.10은 맥의 LAN IP, 4000은 백엔드 포트입니다. IP가 바뀌면 이 환경변수만 갱신하면 되고, 다시 빌드할 필요는 없습니다. 재빌드 없이 env만 바꾸면 된다는 점이 실기기 테스트에서 은근히 요긴했습니다.

런처가 안 붙을 때는 딥링크로

dev 런처에는 최근 접속 목록("Recently Opened")이 뜨는데, 이게 비어 있거나 자동 탐색이 안 되는 경우가 있었습니다. 그럴 때는 adb로 딥링크를 직접 던져 Metro에 붙일 수 있습니다.

adb shell am start -a android.intent.action.VIEW \
  -d "exp+myapp://expo-development-client/?url=http%3A%2F%2F192.168.0.10%3A8081"

-d 뒤의 값은 퍼센트 인코딩된 http://192.168.0.10:8081입니다. 런처 UI를 거치지 않고 바로 특정 Metro 주소로 붙으니, 목록이 비어 있어도 문제가 되지 않았습니다.

화면이 멈췄을 때는 콜드 스타트

앱이 흰 화면에서 멈추거나 상태가 꼬였을 때, 앱을 완전히 죽였다가 다시 띄우면 대부분 풀렸습니다.

adb shell am force-stop com.example.myapp
adb shell am start -n com.example.myapp/.MainActivity

com.example.myapp은 앱의 패키지 이름입니다. force-stop으로 프로세스를 완전히 내리고 MainActivity로 다시 시작하면, 어정쩡하게 살아 있던 상태가 초기화됩니다.

다음 편으로

마지막으로 사소하지만 몇 번 당한 것이 있습니다. Metro나 백엔드를 배경으로 돌리려고 nohup pnpm dev &를 썼는데, pnpm이 자식 프로세스를 detach하면서 정작 서버가 죽는 경우가 있었습니다. 서브셸로 감싸거나, 쓰는 도구가 제공하는 background 실행 옵션을 쓰는 편이 안전했습니다.

돌이켜 보면 막힘의 대부분은 '실기기는 네트워크 너머의 다른 컴퓨터'이고 'ios/android는 생성물'이라는 두 원리를 몸으로 이해하지 못해서였다는 생각이 들었습니다. 3부에서는 이렇게 붙인 실기기를 adb로 원격 조작하면서 자동 QA하는 법을 다루겠습니다.

3부작 중 2부였습니다. 이어지는 3부는 「adb로 안드로이드 실기기를 원격 조작하며 QA하기」입니다. (1부: 「맥에서 Xcode 없이 안드로이드 개발환경 세팅하기 (Homebrew만으로)」)

AndroidExpoReact NativeMetropnpm모노레포

관련 글

맥에서 Xcode 없이 안드로이드 개발환경 세팅하기 (Homebrew만으로)

iOS는 Xcode가 필수지만 안드로이드는 다릅니다. Android Studio 없이 Homebrew와 command-line tools만으로 React Native/Expo 개발환경을 세팅한 기록입니다. JDK 17 keg-only 함정부터 SDK 패키지, 에뮬레이터까지 막힌 순서 그대로 정리했습니다.

관련도 97%

adb로 안드로이드 실기기를 원격 조작하며 QA하기

3부작 완결편. 미러링은 보는 용도, adb는 조작하는 용도로 나누고 screencap·input tap으로 실기기를 원격 조작합니다. 좌표계 어긋남, Android 9 미디어 스캐너가 .MOV·대용량 영상을 색인하지 않는 함정, 잠금은 사람이 푸는 자동화 경계까지 실전 중심으로 정리했습니다.

관련도 96%

Xcode 설치부터 아이폰 실기기 설치까지, 무료 계정으로 뚫은 관문 여섯 개

Xcode를 설치했는데도 아이폰에 앱이 올라가지 않았습니다. 커맨드라인 도구와 Xcode의 차이부터 무료 개발자 계정의 제약, 개발자 모드가 켜지지 않는 이유까지, 실제로 막혔던 여섯 지점과 원문 에러를 정리했습니다.

관련도 93%