ProGuard 완벽 가이드 - Android 코드 난독화와 최적화
ProGuard 완벽 가이드
ProGuard는 Android 앱의 코드를 난독화하고 최적화하는 도구입니다. 이 글에서는 ProGuard의 개념, 설정 방법, 그리고 자주 발생하는 문제들의 해결 방법을 다룹니다.
ProGuard 기본 용어
ProGuard는 다음 네 가지 주요 기능을 제공합니다:
| 기능 | 설명 |
|---|---|
| Shrinking | 사용하지 않는 클래스, 메서드, 필드 제거 |
| Optimizing | 바이트코드 최적화, 메서드 인라이닝 |
| Obfuscating | 클래스/메서드/필드 이름을 의미 없는 짧은 문자열로 변경 |
| Preverifying | 클래스에 preverification 정보 추가 |
@Keep 어노테이션
@Keep 어노테이션은 해당 클래스나 메서드가 난독화 또는 제거되지 않도록 보호합니다. 주로 리플렉션을 통해 접근하는 코드에 사용합니다.
@Keep
class MyClass {
@Keep
fun myMethod() {
// 이 메서드는 난독화되지 않음
}
}
Keep 규칙 상세 가이드
keep vs keepclassmembers vs keepclasseswithmembers
세 가지 keep 규칙의 차이점을 이해하는 것이 중요합니다.
1. -keep
클래스와 지정된 멤버를 모두 유지합니다.
# 클래스만 keep (멤버는 난독화 가능)
-keep class com.example.MyClass
# 클래스와 모든 멤버 keep
-keep class com.example.MyClass { *; }
# 클래스와 특정 메서드만 keep
-keep class com.example.MyClass {
public ** component1();
}
# 내부 클래스 중 이름이 serializer인 경우 유지
-keep,includedescriptorclasses class **$$serializer { *; }
2. -keepclassmembers
멤버만 유지하고, 클래스 자체가 사용되지 않으면 클래스는 제거될 수 있습니다.
# Parcelable의 CREATOR 필드만 유지
-keepclassmembers class * implements android.os.Parcelable {
static ** CREATOR;
}
3. -keepclassmembernames
멤버의 이름만 유지합니다. 해당 멤버가 사용되지 않으면 제거됩니다.
# 네트워크 Value Object의 필드 이름 유지
-keepclassmembernames public class * extends com.example.BaseValueObject {
private <fields>;
}
4. -keepclasseswithmembers
조건을 충족하는 멤버가 있는 클래스와 해당 멤버를 유지합니다.
# component1 메서드가 있는 클래스 전체 유지
-keepclasseswithmembers class com.example.** {
public ** component1();
<fields>;
}
# Retrofit 어노테이션이 있는 메서드를 가진 클래스 유지
-keepclasseswithmembers class * {
@retrofit.http.* <methods>;
}
어노테이션 기반 Keep
# @Keep 어노테이션이 있는 클래스 유지
-keep @android.support.annotation.Keep class *
# 특정 어노테이션이 있는 메서드를 가진 클래스 유지
-keepclasseswithmembers class * {
@retrofit.http.* <methods>;
}
필드 및 와일드카드
# 모든 private 필드 유지
-keepclassmembernames public class * extends com.example.BaseModel {
private <fields>;
}
# 모든 필드 유지
-keepclassmembernames class * implements com.example.Log {
<fields>;
}
# *** 는 any type을 의미
public static *** parse(***);
3rd Party 라이브러리 처리
외부 라이브러리를 사용할 때는 해당 라이브러리의 API 호출에 사용되는 Request/Response 클래스들을 keep해야 합니다.
기본 방법
-keepclassmembernames class com.thirdparty.sdk.** { *; }
DEX 에러가 나는 경우
-keep을 사용하면 DEX 에러가 발생할 수 있습니다. 이 경우 필요한 클래스만 개별적으로 keep합니다:
-keep class com.thirdparty.sdk.Models.AllowedCredentials
-keepclassmembers class com.thirdparty.sdk.Models.AllowedCredentials { *; }
일반적인 에러와 해결 방법
1. can’t find referenced class
원인:
- APK 빌드 시 참조하는 클래스를 찾을 수 없음
- 라이브러리가 참조하는 클래스가 프로젝트에 포함되지 않음
분석:
Warning: com.example.library.SomeClass: can't find referenced class com.missing.Class
해결 방법:
- ProGuard를 적용하지 않았을 때 문제가 없다면, 런타임에 해당 클래스가 사용되지 않는 것
- 이 경우
dontwarn처리:
-dontwarn com.missing.Class
주의: 특정 케이스에서만 해당 클래스가 사용될 수 있으므로, 충분한 테스트 필요
2. Enum 클래스 관련 에러
enum 클래스의 경우 클래스 이름도 keep해야 합니다.
java.lang.ClassNotFoundException: Didn't find class "com.example.e.HasType"
해결:
-keep enum com.example.HasType { *; }
3. 상속 관련 에러
부모 클래스가 난독화된 경우, 자식 클래스에서 문제가 발생할 수 있습니다.
java.lang.IllegalArgumentException: Unable to create converter for class UserPropertiesResponse
원인: 난독화되는 클래스를 상속하는 클래스를 keep하려 할 때, 부모 클래스가 이미 난독화되어 찾을 수 없음
해결:
# 부모 클래스와 자식 클래스 모두 keep
-keep class com.example.base.BaseModel
-keepclassmembers class * extends com.example.base.BaseModel { *; }
4. Kotlin Reflection 에러
Retrofit에서 Jackson + KotlinModule을 사용할 때 발생:
Caused by: java.lang.IllegalStateException: No BuiltInsLoader implementation was found.
Please ensure that the META-INF/services/ is not stripped from your application
해결:
-keep class kotlin.reflect.jvm.internal.** { *; }
권장 ProGuard 설정 템플릿
# 기본 설정
-optimizationpasses 5
-dontusemixedcaseclassnames
-verbose
# Kotlin 관련
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
# Kotlin Reflection
-keep class kotlin.reflect.jvm.internal.** { *; }
# Serialization
-keepattributes *Annotation*
-keepattributes Signature
-keepattributes InnerClasses
-keepattributes EnclosingMethod
# Parcelable
-keepclassmembers class * implements android.os.Parcelable {
static ** CREATOR;
}
# Enum
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Native methods
-keepclasseswithmembernames class * {
native <methods>;
}
# Retrofit
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Gson/Jackson
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
디버깅 팁
1. Mapping 파일 생성
-printmapping mapping.txt
2. 난독화된 스택트레이스 복원
retrace.sh -verbose mapping.txt stacktrace.txt
3. 제거된 코드 확인
-printusage unused.txt
Comments