It's happening now, watch the livestream.

앱 축소, 난독화 및 최적화

앱을 최대한 작게 만들려면 출시 빌드에 축소를 사용하여 사용하지 않는 코드와 리소스를 삭제해야 합니다. 축소를 사용하면 앱 클래스와 멤버 이름을 줄이는 난독화 및 앱 크기를 추가로 줄이는 더 공격적인 전략을 적용하는 최적화 기능을 활용할 수도 있습니다. 이 페이지에서는 R8이 프로젝트에서 컴파일 타임 작업을 하는 방법과 작업을 맞춤설정하는 방법을 설명합니다.

Android Gradle 플러그인 3.4.0 이상을 사용하여 프로젝트를 빌드하는 경우 플러그인은 더 이상 ProGuard를 사용하여 컴파일 타임 코드 최적화 작업을 하지 않습니다. 대신 플러그인은 R8 컴파일러를 이용하여 다음의 컴파일 타임 작업을 처리합니다.

  • 코드 축소(또는 Tree Shaking): 앱에서 사용하지 않는 클래스, 필드, 메서드, 속성 및 라이브러리 종속성을 감지하여 안전하게 삭제합니다(64k 참조 제한을 해결하기 위한 유용한 도구). 예를 들어 라이브러리 종속성에서 몇 개의 API만 사용한다면 축소는 앱이 사용하지 않는 라이브러리 코드를 식별하고 앱에서 그 코드만 삭제할 수 있습니다. 자세히 알아보려면 코드 축소 방법에 관한 섹션을 참조하세요.
  • 리소스 축소: 앱의 라이브러리 종속성에서 사용하지 않는 리소스를 포함하여 패키징된 앱에서 사용하지 않는 리소스를 삭제합니다. 리소스 축소는 코드 축소와 함께 사용하여 사용하지 않는 코드를 삭제하고 마찬가지로 더 이상 참조되지 않는 리소스도 안전하게 삭제할 수 있습니다. 자세한 알아보려면 리소스 축소 방법에 관한 섹션을 참조하세요.
  • 난독화: 클래스와 멤버 이름을 줄여 DEX 파일 크기를 줄입니다. 자세히 알아보려면 코드 난독화 방법에 관한 섹션을 참조하세요.
  • 최적화: 코드를 검사하고 다시 작성하여 앱 DEX 파일의 크기를 더 줄입니다. 예를 들어, if/else 구문의 else {} 브랜치가 전혀 사용되지 않음을 R8에서 감지한 경우 R8이 else {} 브랜치 코드를 삭제합니다. 자세히 알아보려면 코드 최적화 섹션을 참조하세요.

앱의 출시 버전을 빌드할 때 기본적으로 R8은 위에서 설명한 컴파일 타임 작업을 자동으로 진행합니다. 하지만 특정 작업을 중지하거나 ProGuard 규칙 파일을 이용하여 R8의 동작을 맞춤설정할 수 있습니다. 실제로 R8은 기존의 모든 ProGuard 규칙 파일과 호환되므로 R8을 사용하도록 Android Gradle 플러그인을 업데이트하면 기존 규칙을 변경할 필요가 없습니다.

축소, 난독화 및 최적화 사용

Android 스튜디오 3.4 또는 Android Gradle 플러그인 3.4.0 이상을 사용하는 경우 R8은 프로젝트의 자바 바이트코드를 Android 플랫폼에서 실행되는 DEX 포맷으로 변환하는 기본 컴파일러입니다. 그러나 Android 스튜디오를 이용하여 새 프로젝트를 만들 때 축소, 난독화 및 코드 최적화 기능이 기본으로 사용 설정되는 것은 아닙니다. 이러한 컴파일 타임 최적화로 인해 프로젝트의 빌드 시간이 늘어나고 사용자가 유지할 코드를 적절하게 맞춤설정하지 않았을 경우 버그가 발생할 수 있기 때문입니다.

따라서 게시 전에 테스트한 앱의 최종 버전을 빌드할 때 이러한 컴파일 타임 작업을 사용 설정하는 것이 가장 좋습니다. 축소, 난독화 및 최적화를 사용하려면 프로젝트 수준의 build.gradle 파일에 다음 내용을 포함하세요.

android {
        buildTypes {
            release {
                // Enables code shrinking, obfuscation, and optimization for only
                // your project's release build type.
                minifyEnabled true

                // Enables resource shrinking, which is performed by the
                // Android Gradle plugin.
                shrinkResources true

                // Includes the default ProGuard rules files that are packaged with
                // the Android Gradle plugin. To learn more, go to the section about
                // R8 configuration files.
                proguardFiles getDefaultProguardFile(
                        'proguard-android-optimize.txt'),
                        'proguard-rules.pro'
            }
        }
        ...
    }
    

R8 구성 파일

R8은 ProGuard 규칙 파일을 사용하여 기본 동작을 수정하고 앱 코드의 진입점 역할을 하는 클래스와 같은 앱 구조를 더 잘 이해합니다. 규칙 파일의 일부를 수정할 수 있지만 일부 규칙은 AAPT2와 같은 컴파일 타임 도구에서 자동으로 생성되거나 앱 라이브러리 종속성에서 상속될 수 있습니다. 아래 표는 R8이 사용하는 ProGuard 규칙 파일의 소스를 설명합니다.

소스 위치 설명
Android 스튜디오 <module-dir>/proguard-rules.pro Android 스튜디오를 사용하여 새 모듈을 만들면 IDE는 모듈의 루트 디렉터리에 proguard-rules.pro 파일을 만듭니다.

기본적으로 이 파일은 규칙을 적용하지 않습니다. 따라서 맞춤설정 Keep 규칙과 같이 직접 만든 ProGuard 규칙을 여기에 포함하세요.

Android Gradle 플러그인 컴파일 타임에 Android Gradle 플러그인에서 생성됩니다. Android Gradle 플러그인은 proguard-android-optimize.txt를 생성하는데 이 파일은 대부분의 Android 프로젝트에 유용한 규칙을 포함하고 @Keep* 주석을 사용합니다.

기본적으로 Android 스튜디오를 사용하여 새 모듈을 만들 때 모듈 수준 build.gradle 파일은 출시 빌드에 이 규칙 파일을 포함합니다.

참고: Android Gradle 플러그인에는 추가로 사전에 정의한 ProGuard 규칙 파일이 포함되지만 proguard-android-optimize.txt를 사용하는 것이 좋습니다.

라이브러리 종속성 AAR 라이브러리: <library-dir>/proguard.txt

JAR 라이브러리: <library-dir>/META-INF/proguard/

자체 ProGuard 규칙 파일과 함께 AAR 라이브러리를 게시하고 AAR을 컴파일 타임 종속성으로 포함하는 경우 R8은 프로젝트를 컴파일할 때 자동으로 규칙을 적용합니다.

라이브러리가 올바르게 작동하기 위해 특정 keep 규칙이 필요한 경우, 즉 라이브러리 개발자가 문제해결 단계를 따른 경우 AAR 라이브러리와 함께 패키징된 규칙 파일을 사용하는 것이 유용합니다.

그러나 ProGuard 규칙은 부가적이므로 AAR 라이브러리 종속성이 포함하는 특정 규칙은 삭제할 수 없고 앱의 다른 부분을 컴파일하는 데 영향을 줄 수 있습니다. 예를 들어 라이브러리가 코드 최적화를 사용 중지하는 규칙을 포함하는 경우 그 규칙은 전체 프로젝트의 최적화를 사용 중지합니다.

Android Asset Package Tool 2(AAPT2) minifyEnabled true를 사용하여 프로젝트를 빌드한 후: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2는 앱의 manifest, 레이아웃 및 다른 앱 리소스의 클래스에 관한 참조를 기반으로 keep 규칙을 생성합니다. 예를 들어 AAPT2는 앱 manifest에 진입점으로 등록한 각 활동의 keep 규칙을 포함합니다.
맞춤설정 구성 파일 기본적으로 Android 스튜디오에서 새 모듈을 만들 때 IDE는 자체 규칙을 추가할 수 있도록 <module-dir>/proguard-rules.pro를 만듭니다. 추가 구성을 포함할 수 있으며 R8은 컴파일 타임에 해당 구성을 적용합니다.

minifyEnabled 속성을 true로 설정하면 R8은 위에 나열된 모든 사용 가능한 소스의 규칙을 결합합니다. 라이브러리 종속성과 같은 다른 컴파일 타임 종속성은 사용자가 모르는 R8 동작을 변경할 수 있으므로 R8을 사용해서 문제를 해결할 때 이 점을 기억해야 합니다.

프로젝트를 빌드할 때 R8이 적용하는 모든 규칙의 전체 보고서를 출력하려면 모듈의 proguard-rules.pro 파일에 다음을 포함하세요.

// You can specify any path and filename.
    -printconfiguration ~/tmp/full-r8-config.txt
    

추가 구성 포함

Android 스튜디오를 사용하여 새 프로젝트나 모듈을 만들 때 IDE에서는 자체 규칙을 포함할 수 있도록 <module-dir>/proguard-rules.pro 파일을 만듭니다. 또한 모듈의 build.gradle 파일에서 proguardFiles 속성에 다른 파일을 추가하여 다른 파일의 추가 규칙을 포함할 수도 있습니다.

예를 들어, 빌드 변형에 해당하는 productFlavor 블록에 다른 proguardFiles 속성을 추가하여 각 빌드 변형의 전용 규칙을 추가할 수 있습니다. 아래 Gradle 파일은 flavor2 제품 버전에 flavor2-rules.pro를 추가합니다. 이제 release 블록의 규칙도 함께 적용되므로 flavor2는 세 개의 ProGuard 규칙을 모두 사용합니다.

android {
        ...
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile(
                  'proguard-android-optimize.txt'),
                  // List additional ProGuard rules for the given build type here. By default,
                  // Android Studio creates and includes an empty rules file for you (located
                  // at the root directory of each module).
                  'proguard-rules.pro'
            }
        }
        flavorDimensions "version"
        productFlavors {
            flavor1 {
              ...
            }
            flavor2 {
                proguardFile 'flavor2-rules.pro'
            }
        }
    }
    

코드 축소

minifyEnabled 속성을 true로 설정하면 기본적으로 R8에서 코드 축소가 사용 설정됩니다.

코드 축소(Tree Shaking이라고도 함)는 런타임에 필요하지 않다고 R8이 판단한 코드를 삭제하는 프로세스입니다. 예를 들어 앱이 많은 라이브러리 종속성을 포함하지만 기능의 일부만 사용하는 경우 이 프로세스를 통해 앱 크기를 크게 줄일 수 있습니다.

앱 코드를 축소하기 위해 R8은 먼저 결합된 구성 파일 조합을 기반으로 앱 코드의 모든 진입점을 결정합니다. 이 진입점은 Android 플랫폼이 앱의 활동 또는 서비스를 여는 데 사용할 수 있는 모든 클래스를 포함합니다. R8은 각 진입점에서 시작하여 앱의 코드를 검사해 앱이 런타임에 액세스할 수 있는 모든 메서드, 멤버 변수 및 기타 클래스의 그래프를 작성합니다. 그래프에 연결되지 않은 코드는 도달할 수 없는 것으로 간주되며 앱에서 삭제될 수 있습니다.

그림 1은 런타임 라이브러리 종속성을 가진 앱을 보여줍니다. 앱 코드를 검사하는 동안 R8은 MainActivity.class 진입점에서 foo(), faz()bar() 메서드에 도달할 수 있는지 판단합니다. 그러나 런타임에 OkayApi.class 클래스 또는 baz() 메서드가 앱에서 전혀 사용되지 않으며 R8은 앱을 축소할 때 이 코드를 삭제합니다.

그림 1. 컴파일 타임에 R8은 프로젝트의 결합된 keep 규칙을 기반으로 그래프를 작성하여 도달하지 않는 코드를 결정합니다.

R8은 프로젝트의 R8 구성 파일-keep 규칙을 이용하여 진입점을 결정합니다. 즉, keep 규칙은 앱을 축소할 때 R8이 삭제하면 안 되는 클래스를 지정하고 R8은 이 클래스를 앱의 진입점으로 사용할 수 있는지 고려합니다. Android Gradle 플러그인과 AAPT2는 앱의 활동, 보기 및 서비스와 같이 대부분의 앱 프로젝트에서 필요한 keep 규칙을 자동으로 생성합니다. 그러나 추가적인 keep 규칙을 사용하여 이 기본 동작을 맞춤설정해야 하는 경우 유지할 코드를 맞춤설정하는 방법에 관한 섹션을 참조하세요.

대신 앱 리소스의 크기를 줄이는 데만 관심이 있다면 리소스 축소 방법에 관한 섹션을 참조하세요.

유지할 코드 맞춤설정

대부분의 상황에서는 기본 ProGuard 규칙 파일(proguard-android- optimize.txt)만 있으면 R8을 이용하여 사용하지 않는 코드를 삭제할 수 있습니다. 그러나 R8에서 정확하게 분석하기 어려운 상황도 있으며 실제로 앱에서 사용하는 코드를 삭제하는 경우도 발생할 수 있습니다. 다음은 코드를 잘못 삭제할 수 있는 몇 가지 예입니다.

  • 앱이 JNI(Java Native Interface)에서 메서드를 호출하는 경우
  • 앱이 런타임에 리플랙션 등을 사용하여 코드를 찾는 경우

앱을 테스트하면 잘못된 코드 삭제로 인한 오류가 나타나지만 삭제된 코드 보고서를 생성하여 삭제된 코드를 검사할 수도 있습니다.

오류를 수정하고 R8이 특정 코드를 유지하도록 하려면 ProGuard 규칙 파일에 -keep 줄을 추가합니다. 예:

-keep public class MyClass
    

또는 유지하려는 코드에 @Keep 주석을 추가할 수도 있습니다. 클래스에 @Keep을 추가하면 전체 클래스가 그대로 유지됩니다. 이 주석을 메서드나 필드에 추가하면 메서드/필드 및 메서드/필드의 이름뿐만 아니라 클래스 이름도 그대로 유지됩니다. 참고로 이 주석은 축소 사용 방법에 관한 섹션에 설명한 대로 AndroidX 주석 라이브러리를 사용하고 Android Gradle 플러그인과 함께 패키징된 ProGuard 규칙 파일을 포함할 때만 사용할 수 있습니다.

-keep 옵션을 사용하려면 고려해야 하는 사항이 많습니다. 규칙 파일을 맞춤설정하는 방법에 관한 자세한 정보는 ProGuard 설명서를 참조하세요. 문제해결 섹션에서는 코드를 제거할 때 발생할 수 있는 다른 일반적인 문제를 간략히 설명합니다.

리소스 축소

리소스 축소는 코드 축소와 함께 사용할 때만 작동합니다. 코드 축소기가 사용하지 않는 코드를 모두 삭제하면 리소스 축소기에서 아직 앱에 사용되는 리소스를 식별할 수 있습니다. 이는 리소스를 포함하는 코드 라이브러리를 추가하는 경우에 특히 그렇습니다. 사용하지 않는 코드를 삭제해야 라이브러리 리소스가 참조되지 않으며 리소스 축소기가 삭제할 수 있습니다.

리소스 축소를 사용하려면 build.gradle 파일에서 shrinkResources 속성을 true로 설정합니다(코드 축소의 경우 minifyEnabled와 함께 사용). 예:

android {
        ...
        buildTypes {
            release {
                shrinkResources true
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'),
                        'proguard-rules.pro'
            }
        }
    }
    

소스를 삭제하기 전에 동적으로 생성되거나 호출되는 클래스 또는 메서드를 유지하려면 proguard-rules.pro 파일을 편집해야 할 수도 있으므로 코드 축소를 위해 minifyEnabled를 사용하여 아직 앱을 빌드하지 않은 경우 shrinkResources를 사용하기 전에 먼저 빌드합니다.

유지할 리소스 맞춤설정

특정 리소스를 유지하거나 삭제하려는 경우 <resources> 태그를 이용하여 프로젝트에 XML 파일을 생성하고, tools:keep 속성에서 유지할 각 리소스와 tools:discard 속성에서 삭제할 각 리소스를 지정합니다. 두 속성은 모두 쉼표로 구분된 리소스 이름 목록을 허용합니다. 별표 문자를 와일드카드로 사용할 수 있습니다.

예:

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
        tools:discard="@layout/unused2" />
    

프로젝트 리소스에 이 파일을 저장합니다(예: res/raw/keep.xml). 빌드는 APK에 이 파일을 패키징하지 않습니다.

리소스를 삭제할 수 있는데도 불구하고 삭제할 리소스를 지정하는 것이 이상해 보일 수 있지만 빌드 변형을 사용할 때는 이 방법이 유용할 수 있습니다. 예를 들어, 어떤 리소스가 코드에서 사용되는 것처럼 보이지만(이에 따라 축소기에서 삭제하지 않음) 실제로는 주어진 빌드 변형에서 사용하지 않는다는 것을 알고 있다면 모든 리소스를 공용 프로젝트 디렉터리에 넣고 각 빌드 변형을 위해 다른 keep.xml 파일을 생성할 수 있습니다. 또한 컴파일러가 리소스 ID를 인라인으로 추가한 후 리소스 분석기가 실제 참조된 리소스와 같은 값을 가진 코드의 정숫값을 구별하지 못한 경우 빌드 도구가 필요에 따라 리소스를 잘못 식별할 수도 있습니다.

엄격한 참조 확인 사용

일반적으로 리소스 축소기는 리소스의 사용 여부를 정확하게 판별할 수 있습니다. 그러나 코드 또는 AppCompat 같은 라이브러리가 Resources.getIdentifier()를 호출하는 경우 이 코드는 동적으로 생성된 문자열을 기반으로 리소스 이름을 찾습니다. 이렇게 하면 리소스 축소기는 기본적으로 방어적인 동작을 하며 매칭 이름 형식을 가진 모든 리소스를 잠재적으로 사용 중이며 삭제할 수 없는 리소스로 표시합니다.

예를 들어 다음 코드는 img_ 접두사가 있는 모든 리소스를 사용하는 리소스로 표시합니다.

Kotlin

    val name = String.format("img_%1d", angle + 1)
    val res = resources.getIdentifier(name, "drawable", packageName)
    

자바

    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    

리소스 축소기는 또한 코드에 있는 모든 문자열 상수 및 다양한 res/raw/ 리소스를 살펴보고 file:///android_res/drawable//ic_plus_anim_016.png와 유사한 형식의 리소스 URL을 찾습니다. 이와 같은 문자열 또는 URL을 구성하는 데 사용될 수 있는 문자열을 찾으면 삭제하지 않습니다.

다음은 기본적으로 사용되는 안전 축소 모드의 예입니다. 그러나 '나중에 후회하는 것보다는 더 안전한' 처리를 사용 중지하고 리소스 축소기가 확실히 사용되는 리소스만 유지하도록 지정할 수 있습니다. 이를 위해 다음과 같이 keep.xml 파일에서 shrinkModestrict로 설정합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:shrinkMode="strict" />
    

위에서 보는 바와 같이 엄격한 축소 모드를 사용하고 코드가 동적으로 생성된 문자열이 있는 리소스를 참조한다면 tools:keep 속성을 사용하여 이 리소스를 반드시 수동으로 유지해야 합니다.

사용하지 않는 대체 리소스 삭제

Gradle 리소스 축소기는 앱 코드에서 참조하지 않는 리소스만 삭제하며 다른 기기 설정을 위한 대체 리소스는 삭제하지 않습니다. 필요한 경우 Android Gradle 플러그인의 resConfigs 속성을 사용하여 앱에서 불필요한 대체 리소스 파일을 삭제할 수 있습니다.

예를 들어 언어 리소스가 포함된 라이브러리(예: AppCompat 또는 Google Play 서비스)를 사용 중인 경우, APK는 앱의 나머지 부분이 동일 언어로 번역되었는지와 상관없이 라이브러리의 메시지를 위해 번역된 모든 언어 문자열을 포함합니다. 앱에서 공식적으로 지원하는 언어만 유지하려면 resConfig 속성을 사용하여 언어를 지정할 수 있습니다. 지정되지 않은 언어의 리소스는 모두 삭제됩니다.

다음 스니펫은 언어 리소스를 영어와 프랑스어로 제한하는 방법을 보여줍니다.

android {
        defaultConfig {
            ...
            resConfigs "en", "fr"
        }
    }
    

마찬가지로 각기 다른 기기 구성을 대상으로 하는 다중 APK 빌드를 통해 APK에 포함할 화면 밀도 또는 ABI 리소스를 맞춤설정할 수 있습니다.

중복 리소스 병합

기본적으로 Gradle은 이름이 동일한 리소스를 병합합니다(예: 동일한 이름의 드로어블이 다른 리소스 폴더에 있는 경우). 코드가 찾고 있는 이름이 여러 리소스와 일치할 때 발생하는 오류를 피해야 하므로 이 동작은 shrinkResources 속성에 의해 제어되지 않으며 사용 중지할 수 없습니다.

리소스 병합은 둘 이상의 파일이 동일한 리소스 이름, 유형 및 한정자를 공유하는 경우에만 발생합니다. Gradle은 중복된 파일 중에서 가장 적합하다고 여겨지는 파일을 아래 설명한 우선순위에 따라 선택한 후 APK 파일에 배포하기 위해 하나의 리소스만 AAPT에 전달합니다.

Gradle은 다음 위치에서 중복 리소스를 찾습니다.

  • 기본 소스 집합과 연관된 기본 리소스, 일반적으로 src/main/res/에 위치함
  • 빌드 유형 및 빌드 버전에서 파생한 변형 오버레이
  • 라이브러리 프로젝트 종속성

Gradle은 다음과 같은 우선순위 단계에 따라 중복 리소스를 병합합니다.

종속성 → 기본 → 빌드 버전 → 빌드 유형

예를 들어, 중복 리소스가 기본 리소스와 빌드 버전 양쪽에 나타나는 경우 Gradle은 빌드 버전에 있는 리소스를 선택합니다.

동일한 소스 집합 안에 동일한 리소스가 있으면 Gradle이 리소스를 병합할 수 없으며 리소스 병합 오류가 발생합니다. 이러한 오류는 build.gradle 파일의 sourceSet 속성에 여러 개의 소스 집합을 정의한 경우 발생할 수 있습니다(예: src/main/res/src/main/res2/ 모두 동일한 리소스를 포함하는 경우).

코드 난독화

난독화의 목적은 앱 클래스, 메서드 및 필드의 이름을 단축하여 앱 크기를 줄이는 것입니다. 다음은 R8을 사용한 난독화의 예입니다.

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
    androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
        android.content.Context mContext -> a
        int mListItemLayout -> O
        int mViewSpacingRight -> l
        android.widget.Button mButtonNeutral -> w
        int mMultiChoiceItemLayout -> M
        boolean mShowTitle -> P
        int mViewSpacingLeft -> j
        int mButtonPanelSideLayout -> K
    

난독화는 앱에서 코드를 삭제하지는 않지만 다수의 클래스, 메서드 및 필드의 색인을 생성하는 DEX 파일을 사용하는 앱은 크기를 크게 절약할 수 있습니다. 그러나 난독화는 코드의 다른 부분의 이름을 바꾸기 때문에 스택 추적 검사와 같은 특정 작업에는 추가적인 도구가 필요합니다. 난독화 후 스택 추적을 알아보려면 난독화된 스택 추적을 디코딩하는 방법에 관한 다음 섹션을 참조하세요.

또한, 코드가 앱 메서드와 클래스의 예측 가능한 이름 지정을 사용하고 있다면(예: 리플랙션 사용) 유지할 코드를 맞춤설정하는 방법에 관한 섹션에 설명한 대로 서명을 진입점으로 취급하고 keep 규칙을 지정해야 합니다. 이러한 keep 규칙은 R8이 코드를 앱의 최종 DEX에 유지할 뿐만 아니라 원래 이름 지정을 유지하도록 합니다.

난독화된 스택 추적 디코딩

R8이 코드를 난독화하면 클래스와 메서드 이름이 변경될 수 있기 때문에 스택 추적을 이해하는 것은 불가능은 아니지만 어렵습니다. 이름을 바꾸는 것 외에도 R8은 또한 DEX 파일을 작성할 때 스택 추적에 표시된 행 번호를 변경하여 추가로 크기를 줄일 수 있습니다. 다행히 R8은 실행할 때마다 매번 mapping.txt 파일을 만들며 이 파일은 원본 이름에 매핑하는 난독화된 클래스, 메서드 및 필드 이름을 포함합니다. 이 매핑 파일은 또한 행 번호를 원본 소스 파일의 행 번호로 다시 매핑하는 정보도 포함합니다. R8은 <module- name>/build/outputs/mapping/<build-type>/ 디렉터리에 파일을 저장합니다.

Google Play에 앱을 게시할 때 APK 각 버전의 mapping.txt 파일을 업로드할 수 있습니다. 그러면 Google Play는 사용자가 신고한 문제에서 수신한 스택 추적을 난독 해제하므로 Google Play Console에서 문제를 검토할 수 있습니다. 자세한 내용은 비정상 종료된 스택 추적을 난독 해제하는 방법에 관한 고객센터 도움말을 참조하세요.

난독화된 스택 추적을 읽을 수 있는 스택 추적으로 직접 변환하려면 ProGuard와 함께 패키징된 ReTrace 스크립트를 사용하세요.

코드 최적화

앱을 더 많이 축소하기 위해 R8은 코드를 더 상세히 검사하여 사용하지 않는 코드를 추가로 삭제하거나 가능하다면 코드를 간결하게 다시 작성합니다. 다음은 이러한 최적화의 몇 가지 예입니다.

  • if/else 구문에서 코드가 else {} 브랜치에 전혀 접근하지 않는 경우 R8은 else {} 브랜치 코드를 삭제할 수 있습니다.
  • 코드가 한 곳에서만 메서드를 호출하는 경우 R8은 이 메서드를 삭제하고 단일 호출 사이트에서 인라인으로 처리할 수 있습니다.
  • 하나의 구체적인 구현 클래스만 사용하는 추상적인 기본 클래스와 같이 클래스에 고유한 서브클래스가 하나만 있고 클래스 자체가 인스턴스화 되지 않는다고 판단되면 R8은 두 개의 클래스를 결합하여 앱에서 클래스를 삭제할 수 있습니다.
  • 자세히 알아보려면 제이크 와튼이 작성한 R8 최적화 블로그 게시물을 참조하세요.

R8은 임의의 최적화를 사용 중지하거나 사용 또는 최적화 동작을 수정하는 것을 허용하지 않습니다. 사실상 R8은 -optimizations- optimizationpasses와 같이 기본 최적화를 수정하려고 시도하는 모든 ProGuard 규칙을 무시합니다. R8이 지속적으로 개선됨에 따라 최적화의 표준 동작을 유지하는 것은 Android 스튜디오팀이 사용자가 겪을 수 있는 모든 문제를 쉽게 해결하는 데 도움이 되기 때문에 이 제한은 중요합니다.

더 적극적인 최적화 사용

R8은 기본적으로 사용하지 않는 추가적인 최적화 조합을 포함합니다. 프로젝트의 gradle.properties 파일에 다음 내용을 포함하여 추가적인 최적화를 사용할 수 있습니다.

android.enableR8.fullMode=true
    

추가적인 최적화를 사용하면 R8이 ProGuard와 다르게 작동하기 때문에 런타임 문제를 피하고자 ProGuard 규칙을 추가로 포함해야 할 수도 있습니다. 예를 들어, 코드가 Java Reflection API를 통해 클래스를 참조한다고 가정해 보겠습니다. 기본적으로 실제로 그렇게 코드를 작성하지 않았더라도 R8은 런타임에 클래스의 객체를 검사하고 조작하는 코드가 있다고 가정하고 자동으로 클래스와 클래스의 정적 초기화 프로그램을 유지합니다.

그러나 'full mode'를 사용하면 R8은 이러한 가정을 하지 않으며 런타임에 코드가 클래스를 사용하지 않는다고 판단되면 앱의 최종 DEX에서 클래스를 삭제합니다. 즉, 클래스와 클래스의 정적 초기화 프로그램을 유지하려면 규칙 파일에 keep 규칙을 포함해야 합니다.

R8의 'full mode'를 사용하는 동안 문제가 발생하면 R8 FAQ 페이지를 참조하여 가능한 해결책을 찾아보세요. 문제를 해결할 수 없다면 버그로 신고하세요.

R8 문제해결

이 섹션은 R8에서 축소, 난독화 및 최적화를 사용할 때 발생하는 문제를 해결하기 위한 몇 가지 전략을 설명합니다. 아래에서 문제의 해결책을 찾지 못했다면 R8 FAQ 페이지ProGuard의 문제해결 가이드를 참조하세요.

삭제된(혹은 유지된) 코드의 보고서 생성

R8이 앱에서 삭제한 모든 코드의 보고서를 확인하면 특정 R8 문제를 해결하는 데 도움이 될 수 있습니다. 이 보고서를 생성하려는 모듈마다 -printusage <output-dir>/usage.txt에 맞춤설정 규칙 파일을 추가하세요. R8을 사용하여 앱을 빌드하면 R8은 지정된 경로 및 파일 이름이 포함된 보고서를 출력합니다. 삭제된 코드의 보고서는 다음과 유사합니다.

androidx.drawerlayout.R$attr
    androidx.vectordrawable.R
    androidx.appcompat.app.AppCompatDelegateImpl
        public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
        public boolean hasWindowFeature(int)
        public void setHandleNativeActionModesEnabled(boolean)
        android.view.ViewGroup getSubDecor()
        public void setLocalNightMode(int)
        final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
        public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
        private static final boolean DEBUG
        private static final java.lang.String KEY_LOCAL_NIGHT_MODE
        static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
    ...
    

대신 R8이 프로젝트의 keep 규칙에서 결정한 진입점과 관련된 보고서를 보려면 맞춤설정 규칙 파일에 -printseeds <output-dir>/seeds.txt를 포함하세요. R8을 사용하여 앱을 빌드하면 R8은 지정된 경로 및 파일 이름이 포함된 보고서를 출력합니다. 유지된 진입점의 보고서는 다음과 유사합니다.

com.example.myapplication.MainActivity
    androidx.appcompat.R$layout: int abc_action_menu_item_layout
    androidx.appcompat.R$attr: int activityChooserViewStyle
    androidx.appcompat.R$styleable: int MenuItem_android_id
    androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
    androidx.lifecycle.FullLifecycleObserverAdapter
    ...
    

리소스 축소 문제해결

리소스를 축소할 때 Build 창은 APK에서 삭제된 리소스의 요약을 보여줍니다. (Gradle에서 받은 상세한 텍스트 출력을 표시하려면 먼저 창의 왼쪽에서 Toggle view 를 클릭해야 합니다.) 예:

:android:shrinkDebugResources
    Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
    :android:validateDebugSigning
    

Gradle은 또한 ProGuard의 출력 파일과 동일한 폴더인 <module-name>/build/outputs/mapping/release/resources.txt라는 진단 파일을 만듭니다. 이 파일은 다른 리소스를 참조하는 리소스 및 사용되거나 삭제되는 리소스에 관한 세부정보를 포함합니다.

예를 들어 @drawable/ic_plus_anim_016이 계속 APK에 남아있는 이유를 알아보려면 resources.txt 파일을 열고 파일 이름을 검색합니다. 아래와 같이 다른 리소스에서 참조하는 것을 확인할 수 있습니다.

16:25:48.005 [QUIET] [system.out] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
    16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016
    

이제 @drawable/add_schedule_fab_icon_anim이 검색되는 이유를 알아야 합니다. 위쪽으로 검색해 보면 'The root reachable resources are:' 아래에 나열된 리소스를 찾을 수 있습니다. 이는 add_schedule_fab_icon_anim에 코드 참조가 있음을 의미합니다(즉, 연결 가능 코드에서 R.drawable ID가 발견됨).

엄격한 검사를 사용하지 않는 경우 동적으로 로드된 리소스의 이름을 구성하기 위해 사용될 수 있는 문자열 상수가 있다면 리소스 ID가 연결 가능하다고 표시될 수 있습니다. 이 경우 리소스 이름의 빌드 출력을 검색하면 다음과 같은 메시지가 표시될 수 있습니다.

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
        used because it format-string matches string pool constant ic_plus_anim_%1$d.
    

이 문자열 중 하나가 표시되고 주어진 리소스를 동적으로 로드하는 데 사용되지 않음이 확실하면 유지할 리소스 맞춤설정 방법에 관한 섹션에서 설명한 것처럼 tools:discard 속성을 사용하여 이 문자열을 삭제하라고 빌드 시스템에 알릴 수 있습니다.