라이브러리 작성자를 위한 최적화

라이브러리 작성자는 앱 개발자가 고품질 최종 사용자 환경을 유지하면서 라이브러리를 앱에 쉽게 통합할 수 있도록 해야 합니다. 라이브러리가 추가 설정 없이 Android 최적화와 호환되는지 확인하거나 라이브러리가 Android에서 사용하기에 적합하지 않을 수 있음을 문서화해야 합니다.

이 문서는 게시된 라이브러리의 개발자를 대상으로 하지만, 대규모 모듈화된 앱의 내부 라이브러리 모듈 개발자에게도 유용할 수 있습니다.

앱 개발자로서 Android 앱 최적화에 대해 알아보려면 앱 최적화 사용 설정을 참고하세요. 사용하기에 적합한 라이브러리에 관해 알아보려면 라이브러리 현명하게 선택하기를 참고하세요.

리플렉션 대신 코드 생성 사용

가능하면 리플렉션 대신 코드 생성 (codegen)을 사용하세요. 코드 생성과 리플렉션은 프로그래밍 시 상용구 코드를 피하는 일반적인 접근 방식이지만 코드 생성은 R8과 같은 앱 최적화 프로그램과 더 호환됩니다.

  • 코드 생성기를 사용하면 빌드 프로세스 중에 코드가 분석되고 수정됩니다. 컴파일 시간 이후에는 주요 수정사항이 없으므로 최적화 프로그램은 궁극적으로 필요한 코드와 안전하게 삭제할 수 있는 코드를 알고 있습니다.
  • 리플렉션을 사용하면 런타임에 코드가 분석되고 조작됩니다. 코드는 실행될 때까지 실제로 최종 확정되지 않으므로 최적화 프로그램은 안전하게 삭제할 수 있는 코드를 알지 못합니다. 런타임 중에 리플렉션을 통해 동적으로 사용되는 코드가 삭제되어 사용자에게 앱 비정상 종료가 발생할 수 있습니다.

최신 라이브러리에서는 리플렉션 대신 코드 생성을 사용합니다. Room, Dagger2 등에서 사용하는 공통 진입점은 KSP를 참고하세요.

복습이 허용되는 경우

리플렉션을 사용해야 하는 경우 다음 중 하나로만 리플렉션해야 합니다.

  • 특정 타겟팅 유형 (특정 인터페이스 구현자 또는 하위 클래스)
  • 특정 런타임 주석을 사용하는 코드

이 방식으로 리플렉션을 사용하면 런타임 비용이 제한되고 타겟팅된 소비자 유지 규칙을 작성할 수 있습니다.

이 구체적이고 타겟팅된 형태의 리플렉션은 Android 프레임워크 (예: 활동, 뷰, 드로어블을 인플레이션할 때)와 AndroidX 라이브러리 (예: WorkManager ListenableWorkers 또는 RoomDatabases를 생성할 때) 모두에서 볼 수 있는 패턴입니다. 반면 Gson의 개방형 리플렉션은 Android 앱에서 사용하기에 적합하지 않습니다.

보관 라이브러리의 보관 규칙 유형

보관 규칙에는 두 가지 유형이 있습니다.

  • 소비자 유지 규칙은 라이브러리가 반영하는 모든 항목을 유지하는 규칙을 지정해야 합니다. 라이브러리가 리플렉션이나 JNI를 사용하여 코드나 클라이언트 앱에서 정의한 코드를 호출하는 경우 이러한 규칙은 유지해야 하는 코드를 설명해야 합니다. 라이브러리는 앱 유지 규칙과 동일한 형식을 사용하는 소비자 유지 규칙을 패키징해야 합니다. 이러한 규칙은 라이브러리 아티팩트(AAR 또는 JAR)로 번들링되며 라이브러리가 사용될 때 Android 앱 최적화 중에 자동으로 사용됩니다. 이러한 규칙은 build.gradle.kts (또는 build.gradle) 파일의 consumerProguardFiles 속성으로 지정된 파일에 유지됩니다. 자세한 내용은 소비자 유지 규칙 작성을 참고하세요.
  • 라이브러리 빌드 유지 규칙은 라이브러리가 빌드될 때 적용됩니다. 빌드 시점에 라이브러리를 부분적으로 최적화하기로 결정한 경우에만 필요합니다. 라이브러리의 공개 API가 삭제되지 않도록 해야 합니다. 그렇지 않으면 공개 API가 라이브러리 배포에 표시되지 않아 앱 개발자가 라이브러리를 사용할 수 없습니다. 이러한 규칙은 build.gradle.kts (또는 build.gradle) 파일의 proguardFiles 속성으로 지정된 파일에 유지됩니다. 자세한 내용은 AAR 라이브러리 빌드 최적화를 참고하세요.

소비자 유지 규칙 작성

일반적인 유지 규칙 안내 외에도 라이브러리 작성자를 위한 권장사항이 있습니다.

  • 부적절한 전역 규칙을 사용하지 마세요. 라이브러리를 사용하는 모든 앱에 영향을 미치므로 -dontobfuscate 또는 -allowaccessmodification과 같은 전역 설정을 라이브러리의 소비자 유지 규칙 파일에 넣지 마세요.
  • 라이브러리의 소비자 유지 규칙 파일에서 -repackageclasses를 사용하지 마세요. 하지만 라이브러리 빌드를 최적화하려면 라이브러리의 빌드 유지 규칙 파일에서 <your.library.package>.internal과 같은 내부 패키지 이름과 함께 -repackageclasses를 사용하면 됩니다. 이렇게 하면 라이브러리를 사용하는 앱이 최적화되지 않은 경우에도 라이브러리가 더 효율적으로 작동할 수 있지만, 앱도 최적화해야 하므로 일반적으로 필요하지 않습니다. 라이브러리 최적화에 관한 자세한 내용은 라이브러리 작성자를 위한 최적화를 참고하세요.
  • proguard-android-optimize.txt에 정의된 속성과 중복될 수 있더라도 라이브러리가 작동하는 데 필요한 속성을 라이브러리의 keep 규칙 파일에 선언합니다.
  • 라이브러리 배포에 다음 속성이 필요한 경우 라이브러리의 보관 규칙 파일에 유지하고 라이브러리의 소비자 보관 규칙 파일에는 유지하지 마세요.
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • 라이브러리 작성자는 런타임에 주석을 사용하는 경우 소비자 유지 규칙에 RuntimeVisibleAnnotations 속성을 유지해야 합니다.
  • 라이브러리 작성자는 소비자 유지 규칙에서 다음 전역 옵션을 사용하면 안 됩니다.
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

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 라이브러리 빌드 최적화 (고급)

일반적으로 라이브러리 빌드 시 가능한 최적화는 매우 제한적이므로 라이브러리 빌드를 직접 최적화할 필요는 없습니다. 라이브러리가 애플리케이션의 일부로 포함되는 애플리케이션 빌드 중에만 R8이 라이브러리의 모든 메서드가 사용되는 방식과 전달되는 매개변수를 알 수 있습니다. 라이브러리 개발자는 최적화하기 전에 라이브러리 및 앱 빌드 시간 모두에서 동작을 유지하면서 최적화의 여러 단계를 추론해야 합니다.

빌드 시에 라이브러리를 최적화하려면 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는 라이브러리로 패키징되어 나중에 라이브러리를 사용하는 앱을 빌드하는 동안 발생하는 최적화에 영향을 미칩니다.

예를 들어 라이브러리에서 리플렉션을 사용하여 내부 클래스를 생성하는 경우 proguardFilesconsumerProguardFiles 모두에서 유지 규칙을 정의해야 할 수 있습니다.

라이브러리의 빌드에서 -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>)에서 규칙을 선택합니다.