Android ProGuard 설정과 문제 해결 방법을 알아봅니다.

ProGuard 용어

  • Shrinking: 안 쓰는 메서드, 클래스, 필드 등 제거
  • Optimizing: 메서드 바이트코드 최적화, inline
  • Obfuscating: 이름을 의미없는 짧은 문자열로 변경 (난독화)
  • Preverifying: 클래스에 pre verification info 추가

Keep 규칙

@Keep 어노테이션

빌드 시 minify에서 제외되어야 하는 클래스나 메서드에 사용합니다. 리플렉션으로 접근하는 경우에 필요합니다.

Keep 규칙 종류

-keep

클래스와 멤버를 유지합니다.

# 클래스만 keep (멤버는 keep 안 됨)
-keep class com.example.app.data.Task

# 클래스와 모든 멤버 keep
-keep class com.google.ads.mediation.AdUrlAdapter {
    *;
}

# 클래스와 특정 메서드만 keep
-keep class com.example.app.data.Tasks {
    public ** component1();
}

# 내부 serializer 클래스 유지
-keep,includedescriptorclasses class **$$serializer { *; }

-keepclassmembers

멤버만 유지합니다 (클래스를 안 쓰면 삭제됨).

-keepclassmembers class * implements android.os.Parcelable {
    static ** CREATOR;
}

-keepclassmembernames

클래스 내의 멤버들의 이름만 유지합니다. 해당 멤버를 사용하지 않으면 삭제됩니다.

# Network Value Object 공통 처리
-keepclassmembernames public class * extends com.example.base.net.BaseValueObject {
    private <fields>;
}

-keepclasseswithmembers

조건을 충족하는 멤버가 있는 경우에 클래스와 멤버를 유지합니다.

-keepclasseswithmembers class <com.your.package>.** {
    public ** component1();
    <fields>;
}

어노테이션으로 Keep

-keep @android.support.annotation.Keep class *

-keepclasseswithmembers class * {
    @retrofit.http.* <methods>;
}

필드 Keep

-keepclassmembernames public class * extends com.example.base.net.BaseValueObject {
    private <fields>;
}

-keepclassmembernames class * implements com.example.log.Log {
    <fields>;
}

Any Type

***는 any type인 경우 사용합니다.

public static *** parse(***);

3rd Party 라이브러리

3rd 파티 라이브러리를 사용할 때, 해당 라이브러리에서 API 호출하는 경우 Request/Response 클래스를 반드시 keep 해야 합니다.

-keepclassmembernames class com.example.sdk.** { *; }

-keep을 쓰면 dex 에러가 발생할 수 있습니다. 이 경우 개별 클래스를 수동으로 keep해야 합니다.

-keep class com.example.sdk.Models.AllowedCredentials
-keepclassmembers class com.example.sdk.Models.AllowedCredentials { *; }

에러 해결

can’t find referenced class

APK 빌드 시 referenced class가 없다는 오류입니다.

원인:

  • 라이브러리가 참조하는 클래스가 라이브러리에 포함되지 않음
  • implement로 의존성을 추가했지만 해당 클래스가 없음 (api로 하면 포함됨)

해결:

  • ProGuard 적용 안 했을 때 문제없다면 런타임에 해당 클래스가 사용되지 않는 것
  • -dontwarn 사용
-dontwarn com.example.unused.class.**

ClassNotFoundException (Enum)

Enum 클래스의 경우 클래스 이름도 keep 해야 합니다.

java.lang.ClassNotFoundException: Didn't find class "com.example.base.e.HasType"

상속 클래스 Keep

난독화되는 클래스를 상속하는 클래스를 난독화 방지할 경우, 부모 클래스도 keep해야 합니다.

-keep class com.example.base.mvp.BaseModel
-keepclassmembers class * extends com.example.base.mvp.BaseModel { *; }

Retrofit Jackson + KotlinModule 크래시

Caused by: java.lang.IllegalStateException: No BuiltInsLoader implementation was found.

해결:

-keep class kotlin.reflect.jvm.internal.** { *; }

Unable to create converter

java.lang.IllegalArgumentException: Unable to create converter for class

부모 클래스의 keep이 누락된 경우 발생합니다.

implement vs api

  • implement: 해당 라이브러리를 내부에 보관하지 않음
  • api: 해당 라이브러리를 보관함

라이브러리가 참조하는 클래스가 필요한 경우 api를 사용해야 합니다.

참고 자료