Skip to content

Most visited

Recently visited

navigation

코드 및 리소스 축소

APK 파일을 최대한 작게 만들려면, 축소를 활성화하여 릴리스 빌드에서 미사용 코드와 리소스를 제거해야 합니다. 이 페이지에서는 이를 위한 방법과 빌드 중에 유지하거나 폐기할 코드와 리소스를 지정하는 방법에 대해 설명합니다.

코드 축소는 ProGuard에서 사용 가능하며, 패키징된 앱에서 미사용 클래스, 필드, 메서드 및 특성을 감지하여 제거하고 여기에는 포함된 코드 라이브러리의 미사용 클래스, 필드, 메서드 및 특성도 포함됩니다. 코드 축소는 64k 참조 제한을 해결하기 위한 유용한 도구입니다. ProGuard는 또한 바이트코드를 최적화하고, 미사용 코드 명령을 제거하며, 남아있는 클래스, 필드 및 메서드를 짧은 이름으로 난독 처리합니다. 난독 처리된 코드는 APK의 리버스 엔지니어링을 어렵게 만들며, 보안에 민감한 기능(예: 라이선스 검사)이 앱에 사용되는 경우 특히 유용합니다.

리소스 축소는 Gradle용 Android 플러그인에서 사용 가능하며, 패키징된 앱에서 미사용 리소스를 제거하고 여기에는 코드 라이브러리의 미사용 리소스도 포함됩니다. 리소스 축소는 코드 축소와 함께 사용되며, 미사용 코드가 제거되면 더 이상 참조되지 않는 모든 리소스가 안전하게 제거될 수 있습니다.

이 문서의 기능은 다음에 종속됩니다.

코드 축소

ProGuard로 코드 축소를 활성화하려면 minifyEnabled truebuild.gradle 파일의 적절한 빌드 유형에 추가합니다.

유의할 점은, 코드 축소가 빌드 시간을 느리게 하므로 가능하면 디버그 빌드에서는 사용하지 말아야 합니다. 그러나 테스트에 사용되는 최종 APK에서는 코드 축소를 활성화하는 것이 중요합니다. 왜냐하면 유지할 코드를 충분히 사용자설정하지 않을 경우 버그가 발생할 수도 있기 때문입니다.

예를 들어, build.gradle 파일의 다음 스니펫은 릴리스 빌드에 대해 코드 축소를 활성화합니다.

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

참고: Android Studio는 Instant Run을 사용할 때 ProGuard를 비활성화합니다. 증분 빌드에 코드 축소가 필요할 경우 실험용 Gradle 축소기를 사용해보세요.

minifyEnabled 속성 외에도 proguardFiles 속성이 ProGuard 규칙을 정의합니다.

각 빌드 변형 전용의 ProGuard 규칙을 더 추가하려면, 또 다른 proguardFiles 속성을 해당하는 productFlavor 블록에 추가합니다. 예를 들어, 다음 Gradle 파일에서는 flavor2-rules.proflavor2 제품 버전에 추가합니다. 이제 flavor2는 세 개의 ProGuard 규칙을 모두 사용하며, release 블록의 규칙들도 함께 적용됩니다.

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                   'proguard-rules.pro'
        }
    }
    productFlavors {
        flavor1 {
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}

각 빌드에서 ProGuard는 다음과 같은 파일을 출력합니다.

dump.txt
APK에 있는 모든 클래스 파일의 내부 구조를 설명합니다.
mapping.txt
원래 클래스, 메서드 및 필드 이름과 난독 처리된 클래스, 메서드 및 필드 이름 간에 변환을 제공합니다.
seeds.txt
난독 처리되지 않은 클래스와 멤버를 나열합니다.
usage.txt
APK에서 제거되었던 코드를 나열합니다.

이들 파일은 <module-name>/build/outputs/mapping/release/에 저장됩니다.

유지할 코드 사용자설정

일부 경우에는 기본 ProGuard 구성 파일(proguard-android.txt)로 충분하며 ProGuard가 미사용 코드를 모두 제거합니다. 그러나 많은 경우에는 ProGuard로 올바른 분석을 수행하기가 어려우며, 실제로 앱에 필요한 코드가 제거될 수도 있습니다. 다음 예시는 코드가 잘못 제거될 수 있는 경우를 나타냅니다.

앱 테스트를 수행하면 잘못 제거된 코드로 인해 발생하는 오류를 표시하지만, 또한 usage.txt에 저장된 <module-name>/build/outputs/mapping/release/ 출력 파일을 검토하여 어떤 코드가 제거되었는지를 검사할 수 있습니다.

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

-keep public class MyClass

또는 유지하려는 코드에 @Keep 주석을 추가할 수 있습니다. @Keep을 클래스에 추가하면 전체 클래스가 그대로 유지됩니다. 이것을 메서드나 필드에 추가하면 메서드/필드 및 그 이름 뿐만 아니라 클래스 이름도 그대로 유지됩니다. 참고로, 이 주석은 Annotations 지원 라이브러리를 사용할 때만 사용이 가능합니다.

-keep 옵션을 사용할 때는 여러 가지 사항을 고려해야 합니다. 구성 파일 사용자 지정에 대한 자세한 내용은 ProGuard 매뉴얼을 참조하세요. 문제해결 섹션에서는 코드가 제거될 때 발생할 수 있는 다른 일반적인 문제에 대해 간략히 설명합니다.

난독 처리된 스택 추적 디코딩

ProGuard가 코드를 축소한 후에는 메서드 이름이 난독 처리되기 때문에 스택 추적을 읽기가 어려워집니다(불가능하지 않은 경우). 다행히도 ProGuard는 실행될 때마다 mapping.txt 파일을 만들며, 이 파일은 난독 처리된 이름에 매핑된 원래 클래스, 메서드 및 필드 이름을 표시합니다. ProGuard은 파일을 앱의 <module-name>/build/outputs/mapping/release/ 디렉토리에 저장합니다.

유의할 점은, ProGuard로 릴리스 빌드를 만들 때마다 mapping.txt 파일을 덮어쓰므로, 새 릴리스를 게시할 때마다 복사본을 신중하게 저장해야 합니다. 각 릴리스 빌드에 대해 mapping.txt 파일 복사본을 유지하게 되면, 사용자가 구버전의 앱에서 난독 처리된 스택 추적을 제출하는 경우 문제를 디버그할 수 있습니다.

앱을 Google Play에 게시하는 경우, 각 버전의 APK에 대해 mapping.txt 파일을 업로드할 수 있습니다. 그러면 Google Play는 사용자가 보고한 문제들로부터 들어오는 스택 추적의 난독 처리를 해제하므로, Google Play Developer Console에서 이 스택 추적을 검토할 수 있습니다. 자세한 내용은 크래시 스택 추적의 난독 처리 해제에 대한 도움말 센터 문서를 참조하세요.

난독 처리된 스택 추적을 읽기 가능 스택 추적으로 변환하려면, retrace 스크립트를 사용합니다(Windows는 retrace.bat, Mac/Linux는 retrace.sh). 이 스크립트는 <sdk-root>/tools/proguard/ 디렉토리에 있습니다. 이 스크립트는 mapping.txt 파일과 여러분의 스택 추적을 가져와서 새로운 읽기 가능 스택 추적을 생성합니다. retrace 도구를 사용하는 구문은 다음과 같습니다.

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

예:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

스택 추적 파일을 지정하지 않으면 retrace 도구는 표준 입력에서 값을 읽습니다.

Instant Run으로 코드 축소 활성화

코드 축소가 앱의 증분 빌드에 중요하다면 Gradle용 Android 플러그인에 내장된 실험용 코드 축소기를 사용해보세요. 이 축소기는 ProGuard와 달리 Instant Run을 지원합니다.

ProGuard와 같은 구성 파일을 사용하여 Android 플러그인 축소기를 구성할 수 있습니다. 그러나 Android 플러그인 축소기는 코드를 난독 처리하거나 최적화하지 않고 미사용 코드만 제거합니다. 따라서 Android 플러그인 축소기는 디버그 빌드에만 사용해야 합니다. 릴리스 빌드에서 릴리스 APK 코드를 난독처리하고 최적화하려면 ProGuard를 활성화합니다.

Android 플러그인 축소기를 활성화하려면 '디버그' 빌드 유형에서 useProguardfalse로 설정합니다(minifyEnabled 설정은 true로 유지).

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

참고: Android 플러그인 축소기가 처음에 메서드를 제거하지만 메서드에 연결할 수 있도록 코드를 변경할 경우, Instant Run은 이를 구조적 코드 변경 취급하고 다음과 같이 콜드 스왑을 수행합니다.

리소스 축소

리소스 축소는 코드 축소와 함께만 작동합니다. 코드 축소기가 모든 미사용 코드를 제거한 후 리소스 축소기는 아직 앱에 사용되는 리소스를 식별할 수 있습니다. 이는 특히 리소스가 포함된 코드 라이브러리를 추가하는 경우에 사실입니다. 미사용 라이브러리 코드는 제거해야 하므로, 이 경우 라이브러리 리소스가 참조되지 않게 되며 리소스 축소기에 의해 제거될 수 있습니다.

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

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

코드 축소의 경우 minifyEnabled를 사용하여 앱을 아직 빌드하지 않았다면 shrinkResources를 활성화하기 전에 먼저 빌드합니다. 리소스 제거를 시작하기 전에 동적으로 생성되거나 호출되는 클래스나 메서드를 유지하려면 proguard-rules.pro 파일을 편집할 필요가 있습니다.

참고: 현재는 리소스 축소기가 values/ 폴더에 정의된 리소스를 제거하지 않습니다(예: 문자열, 치수, 스타일 및 색상). 그 이유는 AAPT(Android Asset Packaging Tool)에서는 Gradle 플러그인은 사전 정의된 버전을 리소스에 지정할 수 없기 때문입니다. 자세한 내용은 Issue 70869를 참조하세요.

유지할 리소스 사용자설정

특정 리소스를 유지하거나 폐기하려는 경우, <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 인라인을 추가해서 리소스 분석기가 실제 참조된 리소스와 같은 값을 가진 코드의 정수값을 구별하지 못한 것이 원인일 수 있습니다.

엄격한 참조 확인 활성화

일반적으로, 리소스 축소기는 리소스의 사용 여부를 정확하게 판별할 수 있습니다. 그러나 코드가 Resources.getIdentifier()를 호출하거나 임의 라이브러리가 호출을 수행하는 경우(예: AppCompat 라이브러리가 호출을 수행), 이 코드는 동적으로 생성된 문자열을 기반으로 리소스 이름을 찾습니다. 이렇게 하면 리소스 축소기는 기본적으로 방어적인 동작을 수행하며, 일치하는 이름 형식을 가진 모든 리소스를 잠재적으로 사용되고 제거가 불가능한 리소스로 표시합니다.

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

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에 포함시킬 화면 밀도나 ABI 리소스를 사용자 지정하기 위해 APK 분할을 사용할 수 있으며 이를 통해 다른 기기에 다른 APK를 빌드할 수 있습니다.

중복 리소스 병합

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

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

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

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

종속성 → 메인 → 빌드 버전 → 빌드 유형

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

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

리소스 축소 문제해결

리소스를 축소하는 경우 Gradle 콘솔은 앱 패키지로부터 제거되는 리소스의 요약을 표시합니다. 예:

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

Gradle은 또한 resources.txt라는 이름의 진단 파일을 <module-name>/build/outputs/mapping/release/(ProGuard의 출력 파일과 동일한 폴더)에 생성합니다. 이 파일에는 어떤 리소스가 다른 리소스를 참조하는지 그리고 어떤 리소스가 사용되거나 제거되는지에 대한 상세 정보가 포함됩니다.

예를 들어, @drawable/ic_plus_anim_016가 아직도 APK에 남아있는 이유를 알아보려면 resources.txt 파일을 열고 해당 파일 이름을 검색합니다. 아래와 같이 다른 리소스로부터 참조되는 것을 확인할 수도 있습니다.

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     @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 특성을 사용하여 이 문자열을 제거하라고 빌드 시스템에 알릴 수 있습니다. 자세한 내용은 유지할 리소스를 사용자설정하는 방법을 참조하세요.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)