디버그에선 되는데 릴리스 빌드에선 켜자마자 꺼진다면: R8 크래시 원인과 해결
핵심 요약
- R8은 보통 릴리스 빌드에서만 돌아서, R8이 코드를 지워 생기는 크래시는 디버그 빌드로 아무리 테스트해도 보이지 않아요.
Failed to create an instance of class androidx.work.impl.WorkDatabase오류는 R8이WorkDatabase_Impl생성자를 지워서 나요. WorkManager와 함께 들어온 Room 2.5.0의 keep 규칙은 R8 full mode에서 생성자를 지켜 주지 못해요.- 생성자까지 지키는 keep 규칙을 넣거나
work-runtime2.11.2를 의존성에 직접 추가하면 고쳐지고, 릴리스 APK를dexdump로 열어 생성자가 남았는지 확인할 수 있어요. - 출시 전에는 서명한 릴리스 빌드를 실제 기기에 설치해서 앱을 완전히 종료한 상태에서 켜 보세요.
디버그 빌드에서 잘 켜지던 Android 앱이 릴리스 빌드에서만 켜자마자 꺼진다면, R8이 리플렉션으로만 쓰이는 코드를 지운 건 아닌지 먼저 의심해 보세요. R8은 보통 릴리스 빌드에서만 돌기 때문에, 디버그 빌드로 몇 번을 테스트해도 이 크래시는 나오지 않아요.
R8은 무엇이고 왜 릴리스 빌드에서만 문제가 되나요
R8은 Android 앱을 빌드할 때 코드를 줄여 주는 Google의 최적화 도구예요. 앱과 라이브러리에서 쓰이지 않는 코드를 지우고 클래스와 메서드 이름을 a.b.a처럼 짧게 바꾸는 난독화도 해요. 앱이 작고 빨라져서 Google은 릴리스 빌드에서는 늘 켜 두라고 권해요. Android Gradle 플러그인(AGP) 9.3부터는 optimization { enable = true }로, 그 전에는 isMinifyEnabled = true로 켜요. 디버그 빌드에서는 보통 켜지 않아요.
R8은 코드에 적힌 호출을 따라가며 쓰이는 코드를 골라내요. 그런데 라이브러리가 클래스 이름으로 객체를 만드는 리플렉션을 쓰면, R8은 그렇게 만들어지는 클래스를 아무도 안 쓴다고 보고 지워요. 지우면 안 되는 코드는 keep 규칙으로 R8에 알려 줘야 해요. keep 규칙으로 지킨 클래스는 이름도 그대로 남아요.
쓰지도 않은 WorkManager가 왜 앱을 끄나요
WorkManager는 백그라운드 작업을 예약하는 Android 라이브러리예요. 앱이 직접 쓰지 않아도 다른 라이브러리를 따라 들어와요. 예를 들어 광고 SDK와 Play Asset Delivery를 함께 쓰면, 광고 SDK는 WorkManager 2.7.0을, Play Asset Delivery는 2.9.1을 가져오고 Gradle은 높은 쪽인 2.9.1을 골라요. 2.9.1은 데이터베이스 라이브러리인 Room 2.5.0을 함께 가져와요.
어느 라이브러리가 어떤 버전을 가져오는지는 이 명령으로 볼 수 있어요.
./gradlew :app:dependencyInsight --configuration releaseRuntimeClasspath --dependency androidx.work:work-runtime
WorkManager는 앱이 시작될 때 자동으로 초기화되면서 Room 데이터베이스를 열고, Room은 그 데이터베이스 클래스 WorkDatabase_Impl의 객체를 리플렉션으로 만들어요. 그래서 이 클래스의 생성자가 지워지면 첫 화면이 뜨기도 전에 앱이 꺼져요. Play Console 크래시 보고에는 이런 줄이 남아요. 끝의 .canonicalName까지 실제 메시지 그대로예요.
Caused by java.lang.RuntimeException: Failed to create an instance of class androidx.work.impl.WorkDatabase.canonicalName
at androidx.room.Room.getGeneratedImplementation (Room.java:67)
keep 규칙이 있는데도 생성자가 지워지는 이유
Room 라이브러리에는 R8용 keep 규칙이 들어 있어요. Room 2.5.0과 2.7.0의 규칙을 AAR 파일에서 꺼내 비교하면 차이가 보여요.
# room-runtime 2.5.0
-keep class * extends androidx.room.RoomDatabase
# room-runtime 2.7.0 (Android용 파일 이름은 room-runtime-android)
-keep class * extends androidx.room.RoomDatabase { void <init>(); }
2.5.0 규칙은 클래스만 적고 생성자는 적지 않았어요. AGP 8.0 전까지 기본이던 R8 호환 모드는 이런 규칙이면 인자 없는 생성자도 함께 남겼어요. 하지만 AGP 8.0부터 기본인 R8 full mode는 그러지 않아서, 클래스는 남기고 생성자는 지울 수 있어요. 2.7.0 규칙은 void <init>();(인자 없는 생성자)까지 적어서 생성자도 지켜요.
R8 크래시 고치는 법
두 가지 방법이 있어요. 앱 규칙은 라이브러리 버전과 상관없이 생성자를 지키고, 버전을 올리면 라이브러리 규칙 자체가 고쳐져요. 둘 다 해 두는 게 안전해요.
먼저 앱의 proguard-rules.pro에 생성자까지 지키는 규칙을 넣어요.
-keep class * extends androidx.room.RoomDatabase { void <init>(); }
그리고 앱의 의존성에 work-runtime을 직접 추가해서 버전을 올려요. 2.11.2는 Room 2.7.0을 가져오고, 2.7.0에는 위 규칙이 들어 있어요.
dependencies {
implementation("androidx.work:work-runtime:2.11.2")
}
고친 뒤에는 위 명령의 마지막 옵션을 --dependency androidx.room:room-runtime으로 바꿔서 Room이 2.7.0 이상인지 확인해요.
릴리스 빌드에서 생성자가 남았는지 확인하는 법
기기가 없어도 릴리스 APK 안을 직접 볼 수 있어요. Android SDK의 build-tools에 있는 dexdump는 APK 안의 클래스와 메서드를 보여 주는 도구예요. keep 규칙 덕분에 WorkDatabase_Impl은 이름이 그대로라서, 이름으로 찾아 생성자가 있는지 보면 돼요.
dexdump app-release.apk | awk '/Class descriptor.*WorkDatabase_Impl;/{f=1;next} /Class descriptor/{f=0} f && /<init>/'
: '<init>'로 끝나는 줄이 나오면 생성자가 남아 있는 거예요. 클래스는 있는데 아무것도 나오지 않으면 생성자가 지워진 거라서, 그 APK로 설치한 앱은 켜자마자 꺼져요.
출시 전에 릴리스 빌드로 꼭 해 볼 것
Google Play 심사가 앱을 켜 본다고 기대하면 안 돼요. Play Console의 사전 출시 보고서는 앱을 테스트 기기에 설치해서 실행하지만, 기기 실험실에 여유가 있을 때만 자동으로 돌아요. 출시 전에 이 세 가지를 직접 해 보세요.
- 서명한 릴리스 빌드를 실제 기기에 설치해서 앱을 완전히 종료한 상태에서 켜 보세요. Play Console 내부 테스트 트랙으로 설치하면 사용자가 Play에서 받는 것과 같은 파일로 확인할 수 있어요.
- 업로드 전에 리플렉션으로 만들어지는 클래스의 생성자가 남았는지
dexdump로 확인하세요. - APK로 올린다면 난독화 매핑 파일(
mapping.txt)도 직접 올리세요. 그래야 Play Console 크래시 보고에 R8이 줄인 이름 대신 원래 클래스 이름이 나와요. AGP 4.1 이상으로 만든 앱 번들에는 이 파일이 자동으로 들어 있어요.
자주 묻는 질문
릴리스 빌드에서 R8을 끄면 해결되나요? R8을 끄면 코드가 지워지지 않으니 이 크래시는 사라져요. 하지만 앱이 커지고 코드도 그대로 드러나요. Google은 릴리스 빌드에서는 앱 최적화를 늘 켜 두라고 권하니, R8은 켜 둔 채 keep 규칙으로 고치는 게 맞아요.
WorkManager가 아닌 다른 라이브러리에서도 이런 크래시가 나나요?
네. 리플렉션으로만 만들어지는 클래스라면 어느 라이브러리든 같은 일이 생길 수 있어요. R8 호환성 문서도 리플렉션으로만 만드는 클래스는 keep 규칙으로 직접 지키라고 안내해요. 릴리스 빌드에서만 ClassNotFoundException이나 "Failed to create an instance" 같은 오류가 나면 먼저 R8을 의심해 보세요.