EEltaCrew
홈 › 블로그

R8 을 켜고 빌드는 통과했는데 앱이 깨졌다: 15개 앱에서 실제로 걸린 런타임 함정 5가지

게시 2026-09-16실측 기준 2026-09-12읽는 시간 약 5분elta · EltaCrew

Play 의 2027년 코드 최적화 요건에 맞춰 앱 15개에 R8 을 켰습니다. 빌드는 전부 통과했지만 7개 중 2개가 실행 중에 깨졌습니다. Gson 역직렬화, getIdentifier 리소스 조회, Room _Impl, 디버그 빌드의 무효화 등 크래시 로그에 안 잡히는 유형을 정리합니다.

2026년 8월 26일 구글이 Play 신규 품질 요건을 발표했습니다. 2027년 2월부터 DEX 가 앱 10MB(게임 50MB)를 넘는 앱은 코드 최적화(R8)가 필수이고, 지원 문서에는 "선택 사항이 아니며 노출과 게시 기능에 영향을 줄 수 있다"고 적혀 있습니다. 대상 앱 15개에 R8 을 켜고 keep 규칙을 앱마다 맞춰 릴리즈 빌드를 통과시켰습니다. DEX 는 71.6%에서 87.7%까지 줄었습니다.

문제는 그다음이었습니다. 빌드가 통과한 앱 7개를 릴리즈 빌드로 설치해 화면을 눌러 보니 2개가 깨졌습니다. R8 은 "빌드 성공 = 정상"이 성립하지 않습니다.

1. Gson: 역직렬화 대상 패키지를 전부 keep

반려동물 앱이 실행 즉시 죽었습니다.

ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to d82

dto 패키지만 keep 했는데 실제 모델 클래스는 data.repository 에 있었습니다. 제네릭 타입 정보가 지워지자 Gson 이 목표 타입을 몰라 LinkedTreeMap 을 만들었고, 캐스팅에서 터진 것입니다. fromJson 과 TypeToken 호출을 전부 찾아 대상 클래스가 있는 패키지를 모두 keep 해서 해결했습니다. 백업 파일을 읽는 클래스의 내부 클래스(BackupManager$*)까지 포함해야 했습니다.

2. getIdentifier: 리소스 키가 사용자 화면에 그대로 보인다

스크린타임 앱 화면에 tpl_nightowl_2_wit, title_dawn_scroller 같은 문자열이 찍혔습니다. 크래시는 없었습니다. getResources().getIdentifier() 로 이름을 조회하는 코드를 R8 이 보지 못해 리소스 축소가 그 문자열 리소스를 지웠고, id == 0 이면 이름을 그대로 돌려주는 폴백 때문에 조용히 깨진 것입니다.

이 유형은 크래시 감시로는 절대 못 잡습니다. 사용자 눈에만 보입니다. 해결은 res/raw/keep.xml 에 해당 리소스 이름 패턴을 적는 것이고, 전수 검색해 보니 로또 앱(ball_*)과 에러코드 앱(logo_*)도 같은 패턴을 쓰고 있어 예방 조치했습니다. 근본적으로는 getIdentifier 대신 평범한 분기로 매핑하는 편이 낫습니다.

3. Room: _Impl 은 이름으로 찾는다

리모컨 앱 릴리즈를 실기기에 설치하니 앱이 뜨기도 전에 죽었습니다.

Unable to get provider androidx.startup.InitializationProvider
Failed to create an instance of androidx.work.impl.WorkDatabase

Room 은 데이터베이스 클래스 이름 뒤에 _Impl 을 붙여 Class.forName 으로 찾습니다. R8 이 이 참조를 못 보고 삭제했고, WorkManager 가 androidx.startup 프로바이더에서 WorkDatabase 를 만들기 때문에 첫 화면이 그려지기 전에 프로세스가 죽습니다. -keep class **_Impl { *; } 한 줄이 필요합니다. Room 과 WorkManager 를 같이 쓰면 이게 제일 먼저 터집니다.

4. 그 밖에 빌드 단계에서 걸린 것

5. 확인용 빌드는 반드시 non-debuggable

이건 제가 만든 인계 문서가 틀렸던 사례입니다. "디버그 빌드에 R8 만 켠 별도 빌드로 확인하라"고 적어 뒀는데, debuggable 빌드 타입에 isMinifyEnabled = true 를 켜면 AGP 는 경고 한 줄만 내고 난독화와 최적화를 꺼 버립니다.

All code optimizations and obfuscation are disabled for debuggable builds

그 상태로 테스트하면 위의 함정이 하나도 재현되지 않아 "통과"로 오판합니다. 올바른 형태는 release 를 상속하되 isDebuggable = false 로 두고 서명만 디버그 키로 바꾼 별도 빌드 타입입니다. 광고 ID 는 테스트용으로 바꿉니다. 이러면 실기기에 설치할 수 있으면서 R8 은 실제 릴리즈와 같게 돕니다.

같은 이유로, Android Studio 기본 Variant 가 debug 라서 무심코 debug 로 설치해 확인하면 R8 이 아예 안 걸린 앱을 보는 것입니다.

확인 순서

새 앱에 R8 을 켜거나 keep 규칙을 좁힐 때 이 순서로 봅니다.

  1. fromJson / TypeToken 을 검색해 대상 클래스의 패키지를 전부 keep.
  2. getIdentifier 를 검색해 걸리면 res/raw/keep.xml.
  3. Room 을 쓰면 **_Impl keep. WorkManager 를 같이 쓰면 필수.
  4. 문자열로 클래스를 로드하는 라이브러리(암호, 직렬화, DI)의 provider 패키지 keep.
  5. non-debuggable 빌드를 실기기에 설치하고 크래시가 없어도 화면을 눈으로 봅니다. 2번 유형은 눈으로만 잡힙니다.

남은 불확실한 점

구글 문서의 "25% coverage" 가 크기 감소인지 클래스 비율인지 정의가 없습니다. 단정하지 않습니다. R8 기본 설정이면 어느 해석이든 넘습니다. 그리고 R8 을 켰다는 것과 Play 가 요건 충족으로 판정하는 것은 다른 문제라, 최종 확인은 Play Console 의 앱 품질 개요에서 봐야 합니다.

AndroidR8Google Play코드 최적화

함께 읽기

로또 용지 QR 인식 성공이 0건이었던 이유: 기록하지 않은 경로는 실패처럼 보인다로또 앱 주간 보고에서 QR 화면을 연 사람은 19명인데 인식 성공은 0건이었습니다. 스캐너가 고장 난 게 아니라, 진짜 용지 QR 이 지나가는 경로에 기록이 하나도 없었습니다. 측정 구멍을 찾은 과정, 같은 기능 앱 4개와 비교한 결과, 스캔 엔진을 번들로 바꾼 이유, 고친 뒤 1주일 숫자를 정리합니다.지도 왼쪽 아래 로고를 누르면 앱이 꺼졌다: 네이버 지도 SDK 와 AppCompat 테마로또 판매점 지도 앱의 Crashlytics 에 「You need to use a Theme.AppCompat theme」 크래시가 4건 올라왔습니다. 사용자가 아니라 Play 사전 출시 보고서의 테스트 로봇이 지도 왼쪽 아래 NAVER 로고를 누른 것이었습니다. 원인, 앱 전체 테마를 바꾸지 않고 지도만 감싼 수정, 다크 모드에서 생긴 두 번째 문제를 정리합니다.광고 SDK 초기화가 끝나기 전에 광고를 요청하면 꺼진다: 막는 장치를 만들고도 세 번 다시 나온 이유운영 중인 앱에서 「MobileAds.initialize must be called」 크래시를 처음 고친 뒤에도 같은 크래시가 다른 모양으로 세 번 더 나왔습니다. 초기화 완료를 기다리는 장치는 있었지만, 시작 시점에 켜지는 값, 동의만 보고 켜지는 값, 그 장치를 거치지 않는 새 광고 자리가 빈틈이었습니다. 각 경우의 원인과 23개 앱 전수 점검 결과를 정리합니다.