라이브러리 작성자는 앱 개발자가 고품질 최종 사용자 환경을 유지하면서 라이브러리를 앱에 쉽게 통합할 수 있도록 해야 합니다. 즉, 라이브러리가 개발자의 추가 설정 없이 Android 최적화(R8)와 호환되어야 합니다. 그렇지 않으면 라이브러리가 Android에서 사용하기에 적합하지 않을 수 있음을 문서화해야 합니다. Android에서 사용하기 위한 라이브러리는 중요한 앱 최적화를 방해해서는 안 되며 추가 최적화 요구사항을 준수해야 합니다.
이 문서는 게시된 라이브러리의 개발자를 대상으로 하지만, 대규모 모듈화된 앱의 내부 라이브러리 모듈 개발자에게도 유용할 수 있습니다.
앱 개발자이고 Android 앱 최적화에 대해 알아보려면 앱 최적화 사용 설정을 참고하세요. 사용하기에 적합한 라이브러리에 관해 알아보려면 라이브러리 현명하게 선택하기를 참고하세요.
보관 규칙 유형 이해하기
보관 규칙에는 두 가지 유형이 있습니다.
- 소비자 유지 규칙은 라이브러리가 반영하는 모든 항목을 유지하는 규칙을 지정해야 합니다. 라이브러리가 리플렉션이나 JNI를 사용하여 코드나 클라이언트 앱에서 정의한 코드를 호출하는 경우 이러한 규칙은 유지해야 하는 코드를 설명해야 합니다. 라이브러리는 앱 유지 규칙과 동일한 형식을 사용하는 소비자 유지 규칙을 패키징해야 합니다. 이러한 규칙은 라이브러리 아티팩트(AAR 또는 JAR)로 번들링되며 라이브러리가 사용될 때 Android 앱 최적화 중에 자동으로 사용됩니다. 이러한 규칙은
build.gradle.kts(또는build.gradle) 파일의consumerProguardFiles속성으로 지정된 파일에 유지됩니다. 자세한 내용은 소비자 유지 규칙 작성을 참고하세요. - 라이브러리 빌드 유지 규칙은 라이브러리가 빌드될 때 적용됩니다. 빌드 시간에 라이브러리를 부분적으로 최적화하기로 결정한 경우에만 필요합니다. 라이브러리의 공개 API가 삭제되지 않도록 해야 합니다. 그렇지 않으면 공개 API가 라이브러리 배포에 표시되지 않아 앱 개발자가 라이브러리를 사용할 수 없습니다. 이러한 규칙은
build.gradle.kts(또는build.gradle) 파일의proguardFiles속성으로 지정된 파일에 유지됩니다. 자세한 내용은 AAR 라이브러리 빌드 최적화를 참고하세요.
최적화 요구사항 및 가이드라인
라이브러리의 R8 구성은 사용하는 앱의 최종 바이너리 크기와 성능에 전역적으로 영향을 미칩니다. 일반적인 보관 규칙 권장사항 외에도 라이브러리 작성자는 특정 요구사항을 준수하고 추가 가이드라인을 고려해야 합니다.
최적화 요구사항 준수
라이브러리의 비효율성은 앱 블로트, 낭비되는 메모리, 느린 시작, ANR (애플리케이션 응답 없음 오류)의 주요 원인입니다. 라이브러리는 앱 품질과 사용자 환경이 크게 저하되지 않도록 다음 요구사항을 위반하지 않아야 합니다.
광범위하거나 패키지 전체 유지 규칙이 없음: 라이브러리에 라이브러리 또는 다른 라이브러리의 대부분의 코드를 유지하는 광범위한 유지 규칙이 포함되어서는 안 됩니다. 광범위한 유지 규칙은 단기적으로 비정상 종료를 해결할 수 있지만 라이브러리를 사용하는 모든 앱의 앱 크기를 부풀립니다.
라이브러리 또는 참조된 다른 라이브러리의 패키지에 패키지 전체 유지 규칙 (예:
-keep class com.mylibrary.** {*; })을 포함하지 마세요. 이러한 규칙은 라이브러리를 사용하는 모든 앱에서 이러한 패키지의 최적화를 제한합니다.부적절한 전역 규칙 없음:
-dontobfuscate또는-allowaccessmodification와 같은 전역 옵션을 사용하지 마세요.가능한 경우 리플렉션보다 코드 생성 사용: 가능한 경우 리플렉션보다 코드 생성 (codegen)을 사용합니다. 코드 생성과 리플렉션은 프로그래밍 시 상용구 코드를 피하는 일반적인 접근 방식이지만 코드 생성은 R8과 같은 앱 옵티마이저와 더 호환됩니다.
코드 생성기를 사용하면 빌드 프로세스 중에 코드가 분석되고 수정됩니다. 컴파일 시간 이후에는 주요 수정사항이 없으므로 최적화 프로그램은 궁극적으로 필요한 코드와 안전하게 삭제할 수 있는 코드를 알고 있습니다.
리플렉션을 사용하면 런타임에 코드를 분석하고 조작할 수 있습니다. 코드는 실행될 때까지 실제로 최종적으로 결정되지 않으므로 최적화 프로그램은 안전하게 삭제할 수 있는 코드를 알지 못합니다. 런타임 중에 리플렉션을 통해 동적으로 사용되는 코드가 삭제되어 사용자에게 앱 비정상 종료가 발생할 수 있습니다.
최신 라이브러리에서는 리플렉션 대신 코드 생성을 사용합니다. Room, Dagger2, 기타 여러 항목에서 사용하는 공통 진입점은 KSP를 참고하세요.
R8 전체 모드 지원: R8 전체 모드가 사용 설정된 경우 라이브러리가 비정상 종료되지 않아야 합니다. R8의 전체 모드는 R8을 사용하는 데 권장되는 모드이며 2023년에 안정화된 AGP 8.0부터 기본값입니다. R8에서 라이브러리가 비정상 종료되는 경우 전체 패키지를 유지하는 것이 아니라 특정 리플렉션 또는 JNI 진입점을 식별하고 타겟팅된 규칙을 추가하는 것이 해결책입니다.
추가 권장사항
최적화 요구사항 외에도 다음 사항을 권장합니다.
- 라이브러리의 소비자 유지 규칙 파일에서
-repackageclasses를 사용하지 마세요. 하지만 라이브러리 빌드를 최적화하려면 라이브러리의 빌드 유지 규칙 파일에서 내부 패키지 이름(예:<your.library.package>.internal)과 함께-repackageclasses를 사용하면 됩니다. 이렇게 하면 최적화되지 않은 앱에서 라이브러리의 효율성을 개선할 수 있습니다. 하지만 앱도 최적화해야 하므로 일반적으로 필요하지 않습니다. proguard-android-optimize.txt에 정의된 속성과 중복될 수 있더라도 라이브러리가 작동하는 데 필요한 속성을 라이브러리의 keep 규칙 파일에 선언합니다.- 라이브러리 배포에 다음 속성이 필요한 경우 라이브러리의 빌드 유지 규칙 파일에 유지하고 라이브러리의 소비자 유지 규칙 파일에는 유지하지 마세요.
AnnotationDefaultEnclosingMethodExceptionsInnerClassesRuntimeInvisibleAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeInvisibleTypeAnnotationsRuntimeVisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsSignature
- 라이브러리 작성자는 런타임에 주석을 사용하는 경우 소비자 유지 규칙에
RuntimeVisibleAnnotations속성을 유지해야 합니다. - 라이브러리 작성자는 소비자 유지 규칙에서 다음 전역 옵션을 사용하면 안 됩니다.
-include-basedirectory-injars-outjars-libraryjars-repackageclasses-flattenpackagehierarchy-allowaccessmodification-renamesourcefileattribute-ignorewarnings-addconfigurationdebugging-printconfiguration-printmapping-printusage-printseeds-applymapping-obfuscationdictionary-classobfuscationdictionary-packageobfuscationdictionary
반영이 허용되는 경우
리플렉션을 사용해야 하는 경우 다음 중 하나에만 리플렉션해야 합니다.
- 특정 타겟팅 유형 (특정 인터페이스 구현자 또는 하위 클래스)
- 특정 런타임 주석을 사용하는 코드
이 방식으로 리플렉션을 사용하면 런타임 비용이 제한되고 타겟팅된 소비자 유지 규칙을 작성할 수 있습니다.
이 구체적이고 타겟팅된 형태의 리플렉션은 Android 프레임워크 (예: 활동, 뷰, 드로어블을 인플레이션할 때)와 AndroidX 라이브러리 (예: WorkManager
ListenableWorkers 또는 RoomDatabases를 생성할 때) 모두에서 확인할 수 있는 패턴입니다. 반면 Gson의 개방형 리플렉션은 Android 앱에서 사용하기에 적합하지 않습니다.
파트너에 관한 오해 몇 가지
몇 가지 일반적인 오해로 인해 R8을 잘못 구성할 수 있습니다. 여기에는 다음이 포함됩니다.
R8의 최적화에 대한 잘못된 이해: 일반적인 이해와 달리 R8의 최적화는 난독화에만 국한되지 않고 메서드 인라인 처리 및 클래스 병합 기술을 사용한 코드 축소 및 논리적 최적화도 포함합니다. 자세한 내용은 R8 최적화 개요를 참고하세요.
난독화된 라이브러리의 최적화 우회: 라이브러리가 AAR (Android Archive) 또는 JAR (Java Archive)로 컴파일될 때 최적화되거나 난독화되었기 때문에 라이브러리를 최적화에서 제외하는 것이 일반적인 오류입니다. 라이브러리 빌드 시간의 최적화는 제한적이며 앱에서 보관 규칙에 라이브러리를 포함하여 라이브러리의 최적화를 사용 중지해서는 안 됩니다. 자세한 내용은 AAR 라이브러리 빌드 최적화를 참고하세요.
-keep옵션에 대한 잘못된 이해-keep규칙은 R8이 최적화 패스를 실행하지 못하도록 합니다. 자세한 내용은 적절한 보관 옵션 선택하기를 참고하세요.
규칙 패키징 구성
소비자 keep 규칙이 올바르게 적용되도록 하려면 라이브러리 형식에 따라 적절하게 패키징해야 합니다.
AAR 라이브러리
AAR 라이브러리의 소비자 규칙을 추가하려면 Android 라이브러리 모듈의 빌드 스크립트에서 consumerProguardFiles 옵션을 사용하세요. 자세한 내용은 라이브러리 모듈 생성에 관한 안내를 참고하세요.
Kotlin
android {
defaultConfig {
consumerProguardFiles("consumer-proguard-rules.pro")
}
...
}
Groovy
android {
defaultConfig {
consumerProguardFiles 'consumer-proguard-rules.pro'
}
...
}
JAR 라이브러리
JAR로 제공되는 Kotlin 또는 Java 라이브러리와 함께 규칙을 번들로 묶으려면 최종 JAR의 META-INF/proguard/ 디렉터리에 규칙 파일을 원하는 파일 이름으로 넣습니다.
예를 들어 <libraryroot>/src/main/kotlin에 코드가 있는 경우 <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro에 소비자 규칙 파일을 넣으면 규칙이 출력 JAR의 올바른 위치에 번들로 제공됩니다.
규칙이 META-INF/proguard 디렉터리에 있는지 확인하여 최종 JAR이 규칙을 올바르게 번들링하는지 확인합니다.
AAR 라이브러리 빌드 최적화 (고급)
일반적으로 라이브러리 빌드 시 가능한 최적화는 매우 제한적이므로 라이브러리 빌드를 직접 최적화할 필요는 없습니다. 라이브러리 개발자는 라이브러리를 최적화하기 전에 최적화의 여러 단계를 추론하고 라이브러리 및 앱 빌드 시간 모두에서 동작을 유지해야 합니다.
빌드 시에 라이브러리를 최적화하려면 Android Gradle 플러그인에서 지원합니다.
Kotlin
android {
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
configureEach {
consumerProguardFiles("consumer-rules.pro")
}
}
}
Groovy
android {
buildTypes {
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
configureEach {
consumerProguardFiles "consumer-rules.pro"
}
}
}
proguardFiles의 동작은 consumerProguardFiles과 매우 다릅니다.
proguardFiles은 빌드 시간에 사용되며, 라이브러리 빌드 중에 유지해야 하는 라이브러리의 부분을 정의하기 위해getDefaultProguardFile("proguard-android-optimize.txt")과 함께 사용되는 경우가 많습니다. 최소한 공개 API입니다.consumerProguardFiles는 라이브러리로 패키징되어 나중에 라이브러리를 사용하는 앱을 빌드하는 동안 발생하는 최적화에 영향을 미칩니다.
예를 들어 라이브러리에서 리플렉션을 사용하여 내부 클래스를 생성하는 경우 proguardFiles와 consumerProguardFiles 모두에서 유지 규칙을 정의해야 할 수 있습니다.
라이브러리의 빌드에서 -repackageclasses를 사용하는 경우 클래스를 라이브러리 패키지 내부의 하위 패키지로 리패키징합니다. 예를 들어 -repackageclasses 'internal' 대신 -repackageclasses
'com.example.mylibrary.internal'를 사용합니다.
다양한 R8 버전 지원 (고급)
R8의 특정 버전을 타겟팅하도록 규칙을 맞춤설정할 수 있습니다. 이렇게 하면 최신 R8 버전을 사용하는 프로젝트에서 라이브러리가 최적으로 작동할 수 있으며, 기존 규칙은 이전 R8 버전이 있는 프로젝트에서 계속 사용할 수 있습니다.
타겟팅된 R8 규칙을 지정하려면 AAR의 classes.jar 내에 있는 META-INF/com.android.tools 디렉터리 또는 JAR의 META-INF/com.android.tools 디렉터리에 규칙을 포함해야 합니다.
In an AAR library:
proguard.txt (legacy location, the file name must be "proguard.txt")
classes.jar
└── META-INF
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
In a JAR library:
META-INF
├── proguard/<ProGuard-rule-files> (legacy location)
└── com.android.tools (location of targeted R8 rules)
├── r8-from-<X>-upto-<Y>/<R8-rule-files>
└── ... (more directories with the same name format)
META-INF/com.android.tools 디렉터리에는 규칙이 작성된 R8 버전을 나타내는 r8-from-<X>-upto-<Y> 형식의 이름이 지정된 하위 디렉터리가 여러 개 있을 수 있습니다. 각 하위 디렉터리에는 R8 규칙이 포함된 파일이 하나 이상 있을 수 있으며 파일 이름과 확장자는 자유롭게 지정할 수 있습니다.
-from-<X> 및 -upto-<Y> 부분은 선택사항이며 <Y> 버전은 독점적입니다. 버전 범위는 일반적으로 연속적이지만 중복될 수도 있습니다.
예를 들어 r8, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0, r8-from-8.2.0는 타겟팅된 R8 규칙의 집합을 나타내는 디렉터리 이름입니다. r8 디렉터리의 규칙은 모든 R8 버전에서 사용할 수 있습니다. r8-from-8.0.0-upto-8.2.0 디렉터리의 규칙은 버전 8.0.0부터 버전 8.2.0(비포함)까지 R8에서 사용할 수 있습니다.
Android Gradle 플러그인은 이 정보를 사용하여 현재 R8 버전에서 사용할 수 있는 모든 규칙을 선택합니다. 라이브러리가 타겟팅된 R8 규칙을 지정하지 않으면 Android Gradle 플러그인이 기존 위치(AAR의 경우 proguard.txt, JAR의 경우 META-INF/proguard/<ProGuard-rule-files>)에서 규칙을 선택합니다.