스크린샷 찍으려 릴리스 빌드를 처음 돌렸더니 앱이 죽었습니다
증상을 기록하는 앱의 첫 스토어 출시를 준비하던 날입니다. 기능은 다 붙었고 남은 일은 등재 자료를 만드는 것뿐이었습니다. 개발은 처음부터 끝까지 디버그 빌드로만 했습니다.
스크린샷에는 광고가 없는 깔끔한 화면이 필요해서, 그때 처음으로 릴리스 빌드를 실행했습니다. 앱은 실행 즉시 죽었습니다. 수백 번 돌려본 디버그 빌드에서는 한 번도 본 적 없는 종료였습니다.
설치하면 바로 죽는 앱
사용자 입장에서 이 증상은 이렇게 보입니다. 설치하고 아이콘을 누르면 첫 화면이 잠깐 떴다가 사라집니다. 다시 눌러도 마찬가지입니다.
사용자가 할 수 있는 일은 삭제하고 별 하나를 남기는 것뿐입니다. 첫 출시에 이런 리뷰가 붙으면, 그걸 희석해줄 설치 수가 아직 없습니다. 리뷰 점수는 초반 며칠에 거의 결정됩니다.
로그를 열어보니 원인은 제가 쓴 코드가 아니었습니다. 백그라운드 작업을 예약해주는 라이브러리가, 내부에서 쓰는 데이터베이스 클래스를 찾지 못해 인스턴스를 만들 수 없다는 예외였습니다.
그 클래스는 제가 한 줄도 건드린 적이 없는 라이브러리 내부 구현입니다. 앱 코드에는 이름조차 등장하지 않습니다. 그런데 릴리스 빌드에서만 그 클래스가 사라졌습니다.

릴리스 빌드는 다른 프로그램이다
릴리스 빌드에서는 코드 축소기가 켜집니다. 축소기는 앱 전체를 정적으로 훑어서 아무도 참조하지 않는 클래스와 메서드를 지웁니다. 앱 용량을 줄이고 실행을 빠르게 하려는 정상 동작입니다.
문제는 참조를 판단하는 방식입니다. 축소기는 코드에 적힌 참조만 봅니다. 그런데 어떤 라이브러리는 클래스 이름을 문자열로 들고 있다가 실행 시점에 리플렉션으로 찾아서 씁니다.
그 참조는 코드 어디에도 적혀 있지 않습니다. 축소기 눈에는 아무도 안 쓰는 클래스로 보입니다. 그래서 지워지고, 실행 시점에 그 이름으로 찾는 순간 클래스가 없다는 예외가 납니다.
창고를 정리하면서 송장에 안 적힌 상자를 버리는 일과 같습니다. 송장에는 정문으로 들어온 물건만 기록되고, 뒷문으로 들어온 상자는 목록에 없습니다.
정리하는 사람은 목록만 보고 판단하니 그 상자를 폐기 대상으로 분류합니다. 실제로 그 상자를 매일 쓰고 있어도, 정리하는 사람에게 보이는 건 목록에 없다는 사실뿐입니다. 리플렉션이 정확히 그 뒷문입니다.
이 부류의 버그가 고약한 이유는 재현 조건이 사용자 동작이 아니라 빌드 종류에 달려 있다는 점입니다. 디버그 빌드에서는 축소기가 아예 돌지 않습니다. 개발 중 몇백 번을 실행해도 이 크래시는 나오지 않습니다.
항목 | 디버그 빌드 | 릴리스 빌드 |
|---|---|---|
코드 축소·난독화 | 꺼짐 | 켜짐 |
참조 없어 보이는 클래스 | 그대로 남음 | 제거 대상 |
리플렉션으로 찾는 클래스 | 문제 없음 | 사라질 수 있음 |
이 부류 크래시 | 재현 불가 | 실행 즉시 |
정리하면 디버그 빌드에서 잘 돈다는 사실은 릴리스 빌드에 대해 거의 아무것도 알려주지 않습니다. 두 빌드는 같은 소스에서 나온 다른 프로그램입니다.

고친 방법과 유지 규칙의 함정
해결 자체는 단순했습니다. 축소기 설정에 해당 라이브러리 패키지를 유지 대상으로 명시했습니다. 리플렉션으로 접근하는 클래스는 정적 분석에 잡히지 않으니, 지우지 말라고 사람이 직접 적어주는 방식입니다.
다만 이 처방에는 대가가 있습니다. 패키지를 통째로 지키면 축소 효과가 줄고 앱 용량이 커집니다. 정석은 실제로 필요한 클래스만 좁게 지키는 것이고, 제가 넣은 규칙은 출시를 막지 않으려는 임시방편에 가깝습니다.
규칙을 직접 쓰기 전에 라이브러리 문서를 먼저 확인하는 편이 빠릅니다. 많은 라이브러리가 자체 유지 규칙을 함께 배포합니다. 저는 급해서 손으로 먼저 쓰고, 나중에 문서에서 권장 규칙을 발견했습니다.
스크린샷 전용 빌드 플래그를 만들었다
원래 하려던 일로 돌아가서, 스크린샷용 빌드에는 두 가지가 필요했습니다. 광고가 붙지 않는 화면, 그리고 비어 있지 않은 데이터입니다. 빈 목록만 찍힌 스크린샷은 아무것도 설명하지 못합니다.
그래서 빌드할 때 플래그를 주면 광고 없이 3주치 예시 데이터가 채워진 릴리스 빌드가 나오도록 했습니다. 스토어용 이미지 다섯 장을 이 빌드로 촬영했습니다.
이 플래그가 값을 하는 자리는 재촬영입니다. 손으로 데이터를 넣고 광고를 임시로 끄는 방식은 한 번은 되지만, 두 번째부터는 감당이 안 됩니다. 화면이 바뀌거나 스토어 요구 규격이 달라지면 같은 상태를 다시 만들어야 합니다.
플래그로 만들어두면 언제든 같은 화면이 나옵니다. 문구 하나를 고쳐도 다섯 장을 5분 만에 다시 뽑을 수 있습니다.
덤으로 잡힌 버그도 있습니다. 예시 데이터 생성기가 현재 시각 이후의 기록까지 만들고 있었습니다. 아직 오지 않은 시각에 증상이 기록된 화면이 스토어 이미지에 그대로 들어갈 뻔했습니다.
스크린샷을 찍는 과정이 아니었으면 이 버그는 눈에 띌 일이 없었습니다. 시연·촬영용 데이터를 만드는 코드도 결국 코드입니다. 여기 있는 버그는 스토어 이미지에 박힌 채로 나갑니다.

릴리스 빌드가 처음 필요해지는 지점을 앞으로 당긴다
이번에 사고를 막은 것은 실력이 아니라 순서였습니다. 스크린샷 촬영은 출시 절차에서 거의 마지막에 하는 일인데, 동시에 릴리스 빌드가 처음 필요해지는 순간이기도 했습니다.
그 두 가지가 겹친 덕분에 제출 전에 크래시를 봤습니다. 스크린샷을 디버그 빌드로 찍었거나 이미 찍어둔 이미지를 재사용했다면, 설치 즉시 죽는 앱이 그대로 첫 출시로 나갔을 겁니다.
첫 출시 직전은 미검증 경로가 몰려 있는 구간입니다. 서명, 코드 축소, 스토어 정책 검사, 배포 국가 설정이 전부 그때 처음 실행됩니다. 이 구간에 일정을 촘촘하게 잡으면, 문제를 발견해도 고칠 시간이 없습니다.
유지 규칙을 추가하고 릴리스 빌드가 실제로 뜨는 것을 눈으로 확인한 뒤에야 이 문제가 닫혔습니다. 설정 파일에 규칙을 적어둔 것만으로는 아무것도 검증되지 않습니다. 안전장치가 있다는 것만 믿고 넘어갔다가 한 번 당한 적이 있습니다.
출시 전에 점검할 항목
릴리스 빌드를 실제로 설치해서 첫 화면까지 진입해봤는지 — 빌드 성공은 실행 성공이 아니다
리플렉션을 쓰는 라이브러리가 무엇인지 파악하고, 각 라이브러리 문서의 권장 유지 규칙을 반영했는지
유지 규칙이 패키지 통째 유지로 넓게 걸려 있지 않은지 — 용량 손해를 지금 계산해둔다
스토어 자료용 빌드가 플래그 한 개로 재현되는지 — 재촬영 비용이 여기서 결정된다
예시·시연 데이터 생성기가 만드는 값이 실제로 말이 되는지 — 미래 시각, 음수, 빈 라벨을 눈으로 확인한다
저는 이번 일 이후로 릴리스 빌드 실행을 출시 체크리스트가 아니라 주간 작업에 넣었습니다. 빌드 시간이 몇 분 더 들지만, 설치 즉시 죽는 앱을 스토어에 올리는 비용보다는 훨씬 쌉니다.
디버그 빌드로만 개발한 앱은 출시 순간까지 릴리스 빌드가 미검증 상태로 남습니다. 검증되지 않은 경로가 처음 실행되는 자리를 출시일에 둔 것이 이번 실수였습니다.