제품 소식

R8 유지 규칙 구성 및 문제 해결

전문 길이: 7분

최신 Android 개발에서 작고 빠르며 안전한 애플리케이션을 제공하는 것은 기본적인 사용자 기대치입니다. 이를 달성하기 위한 Android 빌드 시스템의 기본 도구는 R8  최적화 프로그램입니다. 이 컴파일러는 축소, 코드 이름 바꾸기 또는 최소화, 앱 최적화를 위해 불필요한 코드와 리소스 삭제를 처리합니다.

R8을 사용 설정하는 것은 앱 출시를 준비하는 데 중요한 단계이지만 개발자가 '유지 규칙' 형식으로 안내를 제공해야 합니다.

이 도움말을 읽은 후 YouTube에서 R8 최적화 프로그램을 사용 설정, 디버깅, 문제 해결하는 방법을 다룬 Performance Spotlight Week 동영상을 확인하세요.

 

 

보관 규칙이 필요한 이유

Keep 규칙을 작성해야 하는 이유는 핵심적인 충돌 때문입니다. R8은 정적 분석 도구이지만 Android 앱은 JNI (Java Native Interface)를 사용하여 네이티브 코드 안팎에서 리플렉션이나 호출과 같은 동적 실행 패턴에 의존하는 경우가 많습니다.

R8은 직접 호출을 분석하여 사용된 코드의 그래프를 빌드합니다. 코드가 동적으로 액세스되는 경우 R8의 정적 분석에서 이를 예측할 수 없으므로 해당 코드를 사용되지 않음으로 식별하여 삭제하면 런타임 비정상 종료가 발생합니다.

keep 규칙은 R8 컴파일러에 대한 명시적 명령어로, '이 특정 클래스, 메서드 또는 필드는 런타임에 동적으로 액세스되는 진입점입니다. 직접적인 참조를 찾을 수 없더라도 유지해야 합니다.'

보관 규칙에 관한 자세한 내용은 공식 가이드를 참고하세요.

보관 규칙을 작성할 위치

애플리케이션의 맞춤 유지 규칙은 텍스트 파일에 작성됩니다. 일반적으로 이 파일의 이름은 proguard-rules.pro이며 앱 또는 라이브러리 모듈의 루트에 있습니다. 그런 다음 이 파일은 모듈의 build.gradle.kts 파일의 release 빌드 유형에 지정됩니다.

  release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

올바른 기본 파일 사용

getDefaultProguardFile 메서드는 Android SDK에서 제공하는 기본 규칙 집합을 가져옵니다. 잘못된 파일을 사용하면 앱이 최적화되지 않을 수 있습니다. proguard-android-optimize.txt을 사용해야 합니다. 이 파일은 표준 Android 구성요소의 기본 Keep 규칙을 제공하고 R8의 코드 최적화를 사용 설정합니다. 오래된 proguard-android.txt는 유지 규칙만 제공하며 R8의 최적화를 사용 설정하지 않습니다.

progaurd.png

이는 심각한 성능 문제이므로 Android 스튜디오 Narwhal 3 기능 출시부터 개발자에게 잘못된 파일 사용에 관한 경고가 표시됩니다. 또한 Android Gradle 플러그인 버전 9.0부터는 오래된 proguard-android.txt 파일을 더 이상 지원하지 않습니다. 따라서 최적화된 버전으로 업그레이드해야 합니다.

보관 규칙 작성 방법

보관 규칙은 세 가지 주요 부분으로 구성됩니다.

  1. -keep 또는 -keepclassmembers과 같은 옵션 
  2. 선택적 수정자(예: allowshrinking)
  3. 일치시킬 코드를 정의하는 클래스 사양

전체 문법과 예시는 보관 규칙 추가 안내를 참고하세요.

Keep Rule 안티패턴

권장사항뿐만 아니라 안티 패턴에 대해서도 알아두는 것이 중요합니다. 이러한 안티패턴은 오해나 문제 해결 바로가기로 인해 발생하는 경우가 많으며 프로덕션 빌드의 성능에 심각한 영향을 미칠 수 있습니다.

전역 옵션

이러한 플래그는 출시 빌드에서 절대 사용해서는 안 되는 전역 전환 버튼입니다. 문제 격리를 위한 임시 디버깅에만 사용됩니다.

-dontotptimize를 사용하면 R8의 성능 최적화가 효과적으로 사용 중지되어 앱이 느려집니다.

-dontobfuscate를 사용하면 모든 이름 변경이 사용 중지되고 -dontshrink를 사용하면 데드 코드 삭제가 사용 중지됩니다. 이 두 가지 전역 규칙은 모두 앱 크기를 늘립니다.

성능이 더 우수한 앱 사용자 환경을 위해 가능한 경우 프로덕션 환경에서 이러한 전역 플래그를 사용하지 마세요.

과도하게 광범위한 보관 규칙

R8의 이점을 무효화하는 가장 쉬운 방법은 지나치게 광범위한 유지 규칙을 작성하는 것입니다. 아래와 같은 keep 규칙은 R8 최적화 도구에 이 패키지의 모든 클래스 또는 모든 하위 패키지를 축소하거나 난독화하거나 최적화하지 않도록 지시합니다. 이렇게 하면 해당 전체 패키지에 대한 R8의 이점이 완전히 삭제됩니다. 대신 좁고 구체적인 보관 규칙을 작성해 보세요.
 

  -keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

반전 연산자 (!)

반전 연산자 (!)는 규칙에서 패키지를 제외하는 강력한 방법인 것 같습니다. 하지만 그렇게 간단하지 않습니다. 다음 예를 살펴보겠습니다.

  -keep class !com.example.my_package.** { *; } // USE WITH CAUTION

이 규칙이 'com.example.package의 클래스를 유지하지 마세요'를 의미한다고 생각할 수 있지만 실제로는 'com.example.package에 있지 않은 전체 애플리케이션의 모든 클래스, 메서드, 속성을 유지하세요 '를 의미합니다. 이 사실이 놀라웠다면 R8 구성에서 부정 표현이 있는지 확인하는 것이 좋습니다.

Android 구성요소의 중복 규칙

또 다른 일반적인 실수는 앱의 ActivitiesServices 또는 BroadcastReceivers에 대한 유지 규칙을 수동으로 추가하는 것입니다. 이 작업은 필요하지 않습니다. 기본 proguard-android-optimize.txt 파일에는 이러한 표준 Android 구성요소가 즉시 작동하도록 하는 관련 규칙이 이미 포함되어 있습니다.

또한 많은 라이브러리가 자체 Keep 규칙을 가져옵니다. 따라서 이러한 항목에 대한 규칙을 직접 작성하지 않아도 됩니다. 사용 중인 라이브러리의 Keep 규칙에 문제가 있는 경우 라이브러리 작성자에게 문의하여 문제를 확인하는 것이 가장 좋습니다.

규칙 유지 권장사항

이제 하지 말아야 할 일을 알았으니 권장사항을 살펴보겠습니다.

세부적인 보관 규칙 작성

좋은 보관 규칙은 최대한 좁고 구체적이어야 합니다. 필요한 것만 유지하여 R8이 다른 모든 것을 최적화하도록 해야 합니다.
 

규칙품질

 

-keep class com.example.** { ; }

 

낮음: 전체 패키지와 하위 패키지를 유지합니다.

 

-keep class com.example.MyClass { ; }

 

낮음: 여전히 너무 넓을 수 있는 전체 클래스를 유지합니다.
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
높음: 특정 클래스의 관련 메서드와 속성만 유지됩니다.

공통 상위 요소 사용

여러 다른 데이터 모델에 대해 별도의 Keep 규칙을 작성하는 대신 공통 기본 클래스나 인터페이스를 타겟팅하는 규칙 하나를 작성하세요. 아래 규칙은 이 인터페이스를 구현하는 클래스의 모든 멤버를 보관하도록 R8에 지시하며 확장성이 높습니다.

  # Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

주석을 사용하여 여러 클래스 타겟팅

맞춤 주석 (예: @Serialize)을 만들고 이를 사용하여 필드를 보존해야 하는 클래스에 '태그'를 지정합니다. 이는 또 다른 깔끔하고 선언적이며 확장성이 뛰어난 패턴입니다. 사용 중인 프레임워크의 기존 주석에 대한 Keep 규칙을 만들 수도 있습니다.

  # Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

적절한 Keep 옵션 선택하기

보관 옵션은 규칙에서 가장 중요한 부분입니다. 잘못된 옵션을 선택하면 최적화가 불필요하게 사용 중지될 수 있습니다.

Keep Option(유지 옵션)기능
-keep클래스 및 선언에 언급된 멤버 가 삭제되거나 이름이 변경되지 않도록 합니다.
-keepclassmembers지정된 구성원이 삭제되거나 이름이 바뀌는 것을 방지하지만 클래스 자체는 삭제할 수 있습니다. 단, 다른 방법으로 삭제되지 않는 클래스에만 해당합니다.
-keepclasseswithmembers조합: 지정된 모든 구성원이 있는 경우에만 클래스 해당 구성원을 유지합니다.

keep 옵션에 대한 자세한 내용은 Keep 옵션 문서를 참고하세요.

수정자를 사용한 최적화 허용

allowshrinking 및 allowobfuscation과 같은 수정자는 광범위한 -keep 규칙을 완화하여 최적화 권한을 R8에 다시 부여합니다. 예를 들어 기존 라이브러리에서 전체 클래스에 -keep를 사용하도록 강제하는 경우 축소 및 난독화를 허용하여 일부 최적화를 되찾을 수 있습니다.

  # Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

추가 최적화를 위한 전역 옵션 추가

Keep 규칙 외에도 R8 구성 파일에 전역 플래그를 추가하여 최적화를 더욱 장려할 수 있습니다.

-repackageclasses는 난독화된 모든 클래스를 단일 패키지로 이동하도록 R8에 지시하는 강력한 옵션입니다. 이렇게 하면 중복된 패키지 이름 문자열이 삭제되어 DEX 파일의 공간이 크게 절약됩니다.

-allowaccessmodification를 사용하면 R8이 더 적극적인 인라인 처리를 지원하기 위해 액세스 권한을 확대 (예: private에서 public로)할 수 있습니다. 이제 proguard-android-optimize.txt을 사용할 때 기본적으로 사용 설정됩니다.

경고: 라이브러리 작성자는 이러한 전역 최적화 플래그가 전체 앱에 강제로 적용되므로 소비자 규칙에 추가해서는 안 됩니다.

더 명확하게 하기 위해 Android Gradle 플러그인 버전 9.0부터 라이브러리의 전역 최적화 플래그를 완전히 무시할 예정입니다. 

라이브러리 권장사항

모든 Android 앱은 어떤 식으로든 라이브러리를 사용합니다. 라이브러리 권장사항에 대해 알아보겠습니다.

라이브러리 개발자

라이브러리에서 리플렉션이나 JNI를 사용하는 경우 소비자에게 필요한 Keep 규칙을 제공해야 합니다. 이러한 규칙은 consumer-rules.pro 파일에 배치되며 이 파일은 라이브러리의 AAR 파일 내에 자동으로 번들로 묶입니다.

  android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

라이브러리 소비자

문제가 있는 Keep 규칙 필터링하기

문제가 있는 Keep 규칙이 포함된 라이브러리를 사용해야 하는 경우 AGP 9.0부터 build.gradle.kts 파일에서 필터링할 수 있습니다. 이렇게 하면 R8이 특정 종속 항목에서 오는 규칙을 무시합니다.

  release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

최고의 보관 규칙은 보관 규칙이 없는 것입니다.

궁극적인 R8 구성 전략은 보관 규칙을 작성할 필요를 완전히 없애는 것입니다. 많은 앱의 경우 리플렉션보다 코드 생성을 선호하는 최신 라이브러리를 선택하면 됩니다. 코드 생성을 사용하면 최적화 프로그램이 런타임에 실제로 사용되는 코드와 삭제할 수 있는 코드를 더 쉽게 확인할 수 있습니다. 또한 동적 리플렉션을 사용하지 않으면 '숨겨진' 진입점이 없으므로 Keep 규칙이 필요하지 않습니다. 새 라이브러리를 선택할 때는 항상 리플렉션보다 코드 생성을 사용하는 솔루션을 선호하세요.

라이브러리를 선택하는 방법에 관한 자세한 내용은 라이브러리 현명하게 선택하기를 참고하세요.

R8 구성 디버깅 및 문제 해결

R8이 유지해야 하는 코드를 삭제하거나 APK가 예상보다 큰 경우 다음 도구를 사용하여 문제를 진단하세요.

중복 및 전역 Keep 규칙 찾기

R8은 수십 개의 소스에서 규칙을 병합하므로 '최종' 규칙 집합을 알기 어려울 수 있습니다. proguard-rules.pro 파일에 이 플래그를 추가하면 전체 보고서가 생성됩니다.

  # Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

이 파일을 검색하여 중복된 규칙을 찾거나 문제가 있는 규칙 (예: -dontoptimize)을 포함한 특정 라이브러리로 추적할 수 있습니다.

R8에 질문하기: 왜 이 기능을 유지하나요?

삭제될 것으로 예상한 클래스가 앱에 아직 있는 경우 R8에서 그 이유를 알려줄 수 있습니다. 이 규칙을 추가하세요.

  # Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

빌드 중에 R8은 해당 클래스를 유지하도록 한 정확한 참조 체인을 출력하므로 참조를 추적하고 규칙을 조정할 수 있습니다.

전체 가이드는 R8 문제 해결 섹션을 참고하세요.

다음 단계

R8은 Android 앱 성능을 향상하는 강력한 도구입니다. 효과를 내려면 정적 분석 엔진으로서의 작동 방식을 올바르게 이해해야 합니다.

구체적인 구성원 수준 규칙을 작성하고, 상위 요소와 주석을 활용하고, 적절한 유지 옵션을 신중하게 선택하면 필요한 항목만 정확하게 유지할 수 있습니다. 가장 고급 방법은 리플렉션 기반 이전 버전 대신 최신 코드 생성 기반 라이브러리를 선택하여 규칙의 필요성을 완전히 없애는 것입니다.

Performance Spotlight Week를 진행하면서 YouTube에서 오늘의 Spotlight Week 동영상을 확인하고 R8 챌린지를 계속 진행하세요. R8 사용 설정 또는 문제 해결에 관한 질문이 있으면 #optimizationEnabled를 사용하세요. 최선을 다해 도와드리겠습니다.

이제 직접 혜택을 확인해 보세요.

오늘 앱에 R8 전체 모드를 사용 설정해 보세요.

  1. 개발자 가이드(앱 최적화 사용 설정)를 따라 시작하세요.
  2. 여전히 proguard-android.txt를 사용하는지 확인하고 proguard-android-optimize.txt으로 바꿉니다.
  3. 그런 다음 영향을 측정합니다. 차이를 느끼는 것뿐만 아니라 확인하세요.  GitHub의 Macrobenchmark 샘플 앱의 코드를 적용하여 시작 전후의 시작 시간을 측정하여 성능 향상을 측정합니다.

앱의 성능이 크게 개선될 것으로 기대합니다.

질문이 있다면 소셜 태그 #AskAndroid를 사용하여 질문해 주세요. Google 전문가가 한 주 내내 질문을 모니터링하고 답변합니다.

내일은 기준 및 시작 프로필을 사용한 프로필 기반 최적화에 대해 알아보고, 지난 출시에서 Compose 렌더링 성능이 어떻게 개선되었는지 공유하고, 백그라운드 작업의 성능 고려사항을 공유합니다.

작성자:

계속 읽기