주석에서 보관 규칙 생성

Jetpack Keep을 사용하면 별도의 유지 규칙 파일이 아닌 Kotlin 또는 자바 소스 코드에서 직접 주석을 사용하여 리플렉션 및 자바 네이티브 인터페이스 (JNI) 사용을 선언할 수 있습니다. Jetpack Keep Gradle 플러그인은 빌드 시 이러한 주석을 읽고 R8에 필요한 유지 규칙을 생성합니다.

Jetpack Keep은 코드베이스가 변경될 때 R8 최적화가 효과적으로 유지되도록 지원합니다.

  • 유지 규칙은 필요한 코드 옆에 유지합니다. 리플렉션을 사용하는 코드의 이름을 바꾸거나 삭제할 때 동일한 위치에서 주석을 업데이트합니다. 동기화할 별도의 규칙 파일이 없습니다.

  • 생성된 규칙은 조건부입니다. R8은 호출을 실행하는 코드도 유지하는 경우에만 리플렉션 호출의 타겟을 유지합니다. 호출 코드가 사용되지 않고 R8이 이를 삭제하는 경우 (선택적 라이브러리 기능에서 자주 발생) R8은 타겟도 삭제할 수 있으므로 사용되지 않는 기능의 규칙이 비대해지지 않습니다.

  • Lint가 리플렉션을 찾습니다. 라이브러리에는 주석이 추가되지 않은 리플렉션 호출에 플래그를 지정하는 린트 검사가 포함되어 있으며, 많은 주석을 자동으로 추가할 수 있습니다.

Jetpack Keep 작동 방식

이름으로 클래스를 인스턴스화하는 함수를 생각해 보세요. R8이 코드에서 클래스를 사용하는지 알 수 없는 경우 클래스의 생성자를 삭제하고 런타임에 앱이 비정상 종료됩니다. 비정상 종료를 방지하려면 다음과 같은 규칙을 keep 규칙 파일에 추가하면 됩니다.

# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
    <init>();
}

Jetpack Keep을 사용하면 대신 리플렉션을 실행하는 함수에 주석을 추가합니다.

// ResourceLocatorImpl isn't on this module's compile classpath, so both the
// annotation and the code refer to it by name.
@UsesReflectionToConstruct(
    className = "com.example.ResourceLocatorImpl",
    // Omitting parameterTypes keeps every constructor. An empty array keeps
    // only the no-argument constructor, which the code calls.
    parameterTypes = [],
)
fun loadResourceLocator(): ResourceLocator {
    val implClass = Class.forName("com.example.ResourceLocatorImpl")
    return implClass.getDeclaredConstructor().newInstance() as ResourceLocator
}

빌드 시 Gradle 플러그인은 생성자의 유지 규칙을 생성합니다. 앞서 수동으로 작성한 규칙과 달리 생성된 규칙은 조건부입니다. R8은 loadResourceLocator가 최적화된 앱에 남아 있는 경우에만 생성자를 유지합니다.

요구사항

Jetpack Keep을 사용하려면 프로젝트에 Android Gradle 플러그인 (AGP) 8.7 이상이 필요합니다.

Gradle 플러그인은 다음 모듈 유형을 지원합니다.

모듈 유형 모듈이 적용하는 플러그인 추가 설정
Android 앱 com.android.application 없음
Android 라이브러리 com.android.library 없음
Android 타겟이 있는 Kotlin 멀티플랫폼 라이브러리 com.android.kotlin.multiplatform.library 없음
JVM 라이브러리 (Java 또는 Kotlin) java-library 또는 org.jetbrains.kotlin.jvm JAR 변환 등록

Jetpack Keep 설정

Jetpack Keep을 사용하려면 리플렉션 또는 JNI를 사용하는 각 모듈에 라이브러리와 Gradle 플러그인을 모두 추가합니다. 라이브러리는 주석과 린트 검사를 제공하고 플러그인은 주석을 유지 규칙으로 변환합니다.

  1. libs.versions.toml 파일에 라이브러리와 플러그인을 추가합니다. 라이브러리와 플러그인은 동일한 버전을 공유합니다.

    [versions]
    # The library and the plugin ship together. Use one version for both so
    # that they stay in sync when you upgrade.
    annotationKeep = "1.0.0-alpha01"
    
    [libraries]
    androidx-annotation-keep = { group = "androidx.annotation.keep", name = "keep", version.ref = "annotationKeep" }
    
    [plugins]
    androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" }
    

    Gradle은 Google의 Maven 저장소에서 플러그인을 다운로드하므로 settings.gradle.kts 파일의 pluginManagement 저장소에 google()가 포함되어 있는지 확인합니다.

  2. 루트 build.gradle.kts 파일에서 AGP와 함께 플러그인을 적용하지 않고 선언합니다.

    plugins {
        alias(libs.plugins.android.application) apply false
        // Other plugins.
    
        // The plugin relies on AGP classes that it doesn't bundle, so declare
        // it next to AGP. Declaring it here also gives every module the same
        // plugin version.
        alias(libs.plugins.androidx.annotation.keep) apply false
    }
    
  3. 리플렉션 또는 JNI를 사용하는 각 모듈의 build.gradle.kts 파일에서 플러그인을 적용하고 라이브러리 종속 항목을 추가합니다.

    plugins {
        alias(libs.plugins.android.application)
        // The plugin reads annotations only from this module's own code, so
        // apply it in every module that uses Jetpack Keep.
        alias(libs.plugins.androidx.annotation.keep)
    }
    
    dependencies {
        implementation(libs.androidx.annotation.keep)
    }
    
  4. 프로젝트를 Gradle 파일과 동기화합니다.

Android 앱 모듈의 각 빌드 변형에 대해 플러그인은 <variant>ExtractKeepRules 작업(예: releaseExtractKeepRules)을 등록하고 생성된 규칙을 해당 변형의 R8에 전달합니다. 라이브러리 모듈의 경우 플러그인은 생성된 규칙을 라이브러리 출력의 소비자 유지 규칙으로 패키징합니다. JVM 라이브러리 모듈에는 추가 설정이 필요합니다.

린트로 리플렉션 찾기

Jetpack Keep에는 다음과 같은 린트 검사가 포함되어 있습니다.

ReflectionWithoutKeepAnnotations
일치하는 유지 주석이 없는 Class.forName, getDeclaredConstructor, getDeclaredMethod, getDeclaredField와 같은 리플렉션 호출에 플래그를 지정합니다.
MissingKeepAnnotationPlugin
androidx.annotation.keep:keep에 종속되지만 androidx.annotation.keep Gradle 플러그인을 적용하지 않는 모듈에 플래그를 지정합니다. 플러그인이 없으면 빌드가 주석에서 유지 규칙을 생성하지 않습니다.

린트를 사용하여 Android 모듈에 주석을 달려면 다음 단계를 따르세요.

  1. 모듈에서 린트를 실행하여 주석이 필요한 리플렉션 호출을 찾습니다.

    ./gradlew :app:lint
    
  2. lint가 자동으로 수행할 수 있는 수정사항을 적용합니다.

    ./gradlew :app:lintFix
    

    Lint는 코드가 반영하는 클래스와 멤버를 확인할 수 있는 경우 주석을 자동으로 추가할 수 있습니다.

  3. 추가된 각 주석을 검토하여 코드의 기능과 일치하는지 확인합니다. 예를 들어 매개변수 유형이 코드에서 호출하는 생성자 또는 메서드와 일치하는지 확인합니다.

  4. Android 스튜디오에서 나머지 발견 사항을 해결합니다. 린트가 타겟 클래스를 확인할 수 없는 경우 빠른 수정은 완료할 수 있도록 TODO 자리표시자가 있는 주석을 삽입합니다. 주석을 수동으로 추가할 수도 있습니다.

    예를 들어 다음 함수는 클래스 이름을 매개변수로 수신하므로 린트가 어떤 클래스를 구성하는지 확인할 수 없습니다. 빠른 수정은 자리표시자가 있는 주석을 추가합니다.

    @UsesReflectionToConstruct(
        className = TODO(),
        parameterTypes = []
    )
    fun createInstance(className: String): Any =
        Class.forName(className).getDeclaredConstructor().newInstance()
    

    자리표시자를 코드에서 구성하는 클래스로 바꿉니다. 컴파일 시간에 클래스를 사용할 수 있는 경우 className 대신 classConstant를 사용합니다. Kotlin에서는 TODO()를 대체할 때까지 코드가 컴파일되지 않습니다.

  5. 기존 규칙을 삭제하기 전에 플러그인이 주석에서 생성하는 유지 규칙을 확인합니다. 앱 모듈의 경우 출시 변형의 추출 작업을 실행합니다.

    ./gradlew :app:releaseExtractKeepRules
    

    플러그인은 규칙을 app/build/generated/release/androidx.annotation.keep.rules.pro에 작성합니다. 라이브러리 모듈의 경우 생성된 규칙 확인을 참고하세요.

  6. 새 주석이 대체하는 유지 규칙을 유지 규칙 파일에서 삭제합니다. 규칙은 다른 모듈이나 라이브러리의 리플렉션과 같이 린트에서 플래그를 지정하지 않은 리플렉션도 포함할 수 있으므로 삭제하기 전에 각 규칙을 확인하세요.

    • -keep class com.example.** { *; }와 같은 패키지 전체 규칙의 경우 주석이나 다른 규칙이 패키지 클래스의 모든 리플렉션을 포함하는지 확인합니다.
    • 특정 클래스나 멤버를 유지하는 규칙의 경우 생성된 규칙 중 하나와 밀접하게 일치하거나 다른 리플렉션이 이에 종속되지 않는지 확인합니다.
  7. 출시 빌드를 테스트합니다.

코드에 주석 달기

리플렉션을 실행하는 코드(리플렉션 호출이 포함된 함수, 생성자, 필드 또는 클래스)에 Jetpack Keep 주석을 추가합니다. 주석은 리플렉션의 타겟을 설명하며, 생성된 규칙은 R8이 주석이 달린 코드를 유지하는 경우에만 해당 타겟을 유지합니다.

코드의 기능에 따라 주석을 선택합니다.

주석 코드가 다음과 같은 경우에 사용합니다.
@UsesReflectionToConstruct 리플렉션을 통해 생성자를 호출합니다(예: getDeclaredConstructor().newInstance() 사용).
@UsesReflectionToAccessMethod 리플렉션을 통해 메서드를 호출합니다(예: getMethod 또는 getDeclaredMethod 사용).
@UsesReflectionToAccessField 리플렉션을 통해 필드를 읽거나 씁니다(예: getField 또는 getDeclaredField 사용).
@UnconditionallyKeep 앱의 Kotlin 또는 Java 코드 외부에서 호출되는 경우(예: JNI를 통해 네이티브 코드에서 호출) 조건부 규칙을 생성하는 @UsesReflection* 주석을 사용할 수 없는 경우에만 이 주석을 사용하세요.

모든 @UsesReflection* 주석은 다음 규칙을 따릅니다.

  • classConstant 또는 className를 사용하여 타겟 클래스를 식별합니다. 컴파일 시간에 클래스를 사용할 수 있는 경우 classConstant을 사용합니다. 컴파일러는 참조를 확인하고 이름 바꾸기와 같은 IDE 리팩터링은 참조를 업데이트합니다. 선택적 종속 항목의 클래스와 같이 컴파일 시간에 클래스를 사용할 수 없는 경우에만 className를 사용합니다. 이 두 매개변수는 상호 배타적입니다.
  • 가장 구체적인 클래스를 타겟팅합니다. 생성된 규칙은 지정한 클래스와 서브클래스 또는 인터페이스 구현에서 일치하는 멤버를 유지합니다. 클래스가 일반적일수록 R8이 더 많은 코드를 유지합니다.
  • 매개변수 유형을 지정하여 단일 오버로드를 유지합니다. parameterTypes 또는 parameterTypeNames를 설정하지 않으면 규칙은 메서드의 모든 생성자 또는 모든 오버로드를 유지합니다. 인수가 없는 생성자 또는 메서드만 유지하려면 빈 배열을 전달합니다.
  • 여러 타겟에 대해 주석을 반복합니다. 하나의 함수가 둘 이상의 클래스 또는 멤버를 반영하는 경우 각 타겟에 주석을 하나씩 추가합니다.

리플렉션으로 객체 구성

코드에서 리플렉션을 통해 클래스를 인스턴스화할 때 @UsesReflectionToConstruct를 사용합니다. 다음 예시에서 태스크 실행기는 이름으로 StartupTask 인터페이스의 구현을 인스턴스화합니다. 주석은 인터페이스를 지정하므로 생성된 규칙은 R8이 execute를 유지하는 한 모든 StartupTask 구현의 인수 없는 생성자를 유지합니다.

interface StartupTask {
    fun run()
}

object TaskRunner {
    // The task class is known only at runtime, so target the interface that
    // every task implements.
    @UsesReflectionToConstruct(classConstant = StartupTask::class, parameterTypes = [])
    fun execute(className: String) {
        val task = Class.forName(className)
            .getDeclaredConstructor()
            .newInstance() as StartupTask
        task.run()
    }
}

리플렉션으로 메서드 호출

코드에서 리플렉션을 통해 메서드를 호출하는 경우 @UsesReflectionToAccessMethod를 사용합니다. methodName을 메서드 이름으로 설정합니다. 다음 예시에서 코드는 선택적 종속 항목의 정적 메서드를 호출합니다. 이 클래스는 컴파일 시간에 사용할 수 없으므로 주석은 classConstant 대신 className를 사용합니다.

object CrashReporting {
    @UsesReflectionToAccessMethod(
        className = "com.example.crashlib.CrashReporter",
        methodName = "log",
        // Omitting parameterTypes keeps every log overload. Keep only the one
        // that the code calls.
        parameterTypes = [String::class],
    )
    fun log(message: String) {
        val reporterClass = try {
            Class.forName("com.example.crashlib.CrashReporter")
        } catch (e: ClassNotFoundException) {
            // Crash reporting is optional, so skip it instead of failing.
            return
        }
        reporterClass.getMethod("log", String::class.java).invoke(null, message)
    }
}

리플렉션으로 필드 액세스

코드가 리플렉션을 통해 필드를 읽거나 쓸 때 @UsesReflectionToAccessField를 사용합니다. fieldName를 필드 이름으로 설정합니다.

class UserProfileFetcher {
    // User doesn't expose internalId, so reading it requires reflection.
    @UsesReflectionToAccessField(classConstant = User::class, fieldName = "internalId")
    fun readInternalId(user: User): Any? {
        val field = User::class.java.getDeclaredField("internalId")
        field.isAccessible = true
        return field.get(user)
    }
}

앱 외부에서 호출된 코드 유지

네이티브 코드가 JNI를 통해 호출하는 메서드와 같이 앱의 Kotlin 또는 자바 코드 외부에서 호출되는 코드에는 @UnconditionallyKeep를 사용합니다. 호출하는 코드가 아닌 호출되는 요소에 주석을 추가합니다.

class NativeBridge {
    // Only native code calls this method, through a JNI lookup by name that
    // R8 can't see. Keep the method and its name unconditionally.
    @UnconditionallyKeep
    fun onNativeEvent(message: String) {
        Log.d("NativeBridge", "Received from native code: $message")
    }

    // The default proguard-android-optimize.txt file already keeps native
    // method names, so this method doesn't need an annotation.
    external fun startNativeProcess()
}

생성된 규칙은 주석이 달린 메서드와 이 메서드가 포함된 클래스를 원래 이름으로 유지합니다. 메서드의 서명이 참조하는 다른 클래스는 유지하지 않습니다. 네이티브 코드가 이러한 클래스에 종속되는 경우 이러한 클래스에도 주석을 달거나 유지 규칙을 추가합니다. JNI 및 R8에 관한 자세한 내용은 JNI를 통한 네이티브 코드와의 상호작용을 참고하세요.

기본적으로 @UnconditionallyKeep는 요소의 이름을 유지합니다. 외부 호출자가 이름에 의존하지 않는 경우 R8이 요소의 이름을 바꿀 수 있도록 shouldPreserveName = false를 설정합니다.

생성된 규칙 확인

이 섹션에서는 각 유형의 모듈에 대해 생성된 규칙을 확인하는 방법을 설명합니다. 앱 모듈의 경우 플러그인은 규칙을 R8에 직접 전달합니다. 라이브러리 모듈의 경우 플러그인은 규칙을 라이브러리 출력의 소비자 유지 규칙으로 패키징합니다. 라이브러리에 종속된 앱은 Jetpack Keep을 사용하지 않더라도 R8을 실행할 때 이러한 규칙을 자동으로 적용합니다. 소비자 유지 규칙에 관한 자세한 내용은 라이브러리 작성자를 위한 최적화를 참고하세요.

Android 앱

플러그인이 앱 모듈에 생성하는 유지 규칙을 확인하려면 빌드 변형의 추출 작업을 실행합니다.

./gradlew :app:releaseExtractKeepRules

플러그인은 각 대안의 규칙을 app/build/generated/<variant>/androidx.annotation.keep.rules.pro에 작성합니다. 각 규칙 앞의 # context: 주석은 규칙을 생성한 주석이 지정된 코드를 식별합니다.

생성된 규칙이 다른 유지 규칙과 함께 최적화에 어떤 영향을 미치는지 확인하려면 R8 구성 분석기를 사용하세요.

Android 라이브러리

Android 라이브러리 모듈에는 추가 설정이 필요하지 않습니다. AAR을 빌드하면 플러그인은 AAR에 이미 포함된 소비자 유지 규칙 뒤에 생성된 규칙을 AAR의 proguard.txt 파일에 추가합니다. 플러그인은 이를 위해 각 빌드 변형에 <variant>KeepRulesTransformAar 작업을 등록합니다.

규칙을 확인하려면 ./gradlew :mylibrary:assembleRelease 등을 사용하여 AAR을 빌드하고 생성된 AAR에서 proguard.txt를 엽니다. 생성된 규칙은 다음 헤더가 있는 섹션에 표시됩니다.

#################################################################
#   Rules generated from androidx.annotation.keep annotations   #
#################################################################

macOS 및 Linux에서는 명령줄에서 규칙을 출력할 수도 있습니다.

unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt

Kotlin 멀티플랫폼 라이브러리

Android 타겟이 있는 Kotlin 멀티플랫폼 라이브러리 모듈에도 추가 설정이 필요하지 않습니다. Android 타겟의 AAR을 빌드하면 플러그인은 Android 라이브러리와 동일한 방식으로 생성된 규칙을 AAR의 proguard.txt 파일에 추가합니다. 플러그인은 이를 위해 androidMainKeepRulesTransformAar 작업을 등록합니다.

규칙을 확인하려면 이 작업을 실행하고 생성된 AAR에서 proguard.txt를 엽니다. macOS 및 Linux에서는 명령줄에서 규칙을 출력할 수 있습니다.

./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt

JVM 라이브러리

JVM 라이브러리 모듈의 규칙을 확인하려면 먼저 플러그인이 생성된 규칙을 JAR 사본에 추가하도록 모듈을 구성해야 합니다. 그런 다음 등록한 변환 작업을 실행하고 생성되는 JAR의 규칙 파일을 확인합니다. 예를 들어 작업 이름을 keepRulesJar로 지정한 경우 다음 명령어를 사용하여 macOS 및 Linux에서 규칙을 출력할 수 있습니다.

./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
    "META-INF/com.android.tools/*" "META-INF/proguard/*"

JVM 라이브러리 구성

JAR을 생성하는 JVM 라이브러리 모듈의 경우 플러그인이 JAR을 자동으로 수정하지 않습니다. 대신 annotationKeep.registerJavaArchiveTransform 함수를 사용하여 생성된 규칙으로 JAR 사본을 만드는 작업을 등록한 다음 JAR을 게시합니다. 라이브러리 모듈의 build.gradle.kts 파일에 다음을 추가합니다.

plugins {
    id("java-library")
    alias(libs.plugins.jetbrains.kotlin.jvm)
    alias(libs.plugins.androidx.annotation.keep)
}

val jarTask = tasks.named<Jar>("jar")

// Apps run R8 on this library's code, so the generated rules must be inside
// the JAR. The plugin handles this automatically only for Android modules.
val keepRulesJarTask = annotationKeep.registerJavaArchiveTransform(
    taskName = "keepRulesJar",
    input = jarTask.flatMap { it.archiveFile },
    fileName = jarTask.flatMap { it.archiveFileName }.map { it.replace(".jar", "-keep-rules.jar") },
)

// Consumers of this library resolve apiElements and runtimeElements, so add
// the JAR that contains the generated rules to them.
configurations.configureEach {
    if (name == "apiElements" || name == "runtimeElements") {
        outgoing.artifact(keepRulesJarTask.flatMap { it.outputJar })
    }
}

dependencies {
    implementation(libs.androidx.annotation.keep)
}

변환은 생성된 규칙을 JAR의 타겟팅된 R8 규칙 위치인 META-INF/com.android.tools/r8/proguard.txt에 추가합니다. JAR에 이미 META-INF/com.android.tools/ 또는 META-INF/proguard/의 규칙이 포함되어 있으면 변환은 생성된 규칙을 기존 규칙 파일에 추가합니다. 이러한 위치에 관한 자세한 내용은 다양한 R8 버전 지원을 참고하세요.

변환을 등록한 후 JAR에서 규칙을 확인합니다.

예: 선택적 부가기능 라이브러리 로드

라이브러리는 리플렉션을 사용하여 선택적 부가기능을 로드하는 경우가 많습니다. 예를 들어 애널리틱스 SDK는 핵심 라이브러리와 동영상 부가기능을 제공할 수 있습니다. 부가기능은 핵심 라이브러리에 종속되므로 핵심 라이브러리는 부가기능의 클래스를 직접 참조할 수 없습니다. 대신 핵심 라이브러리는 런타임에 이름으로 부가기능의 클래스를 조회하고 클래스를 찾은 경우에만 동영상 분석을 사용 설정합니다.

핵심 라이브러리는 리플렉션을 통해서만 부가기능의 클래스를 사용하므로 R8은 앱의 출시 빌드에서 클래스 또는 생성자를 삭제할 수 있으며 이로 인해 조회가 실패합니다. 이 오류는 부가기능이 포함된 앱의 출시 빌드에서만 발생하므로 테스트에서 포착하기 어렵습니다.

Jetpack Keep이 없으면 부가기능은 선택적 종속 항목을 지원하는 리플렉션에 표시된 대로 클래스의 소비자 유지 규칙을 제공합니다.

# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
    <init>();
}

이 규칙은 이를 필요로 하는 조회와 다른 라이브러리에 있으므로 두 라이브러리가 동기화되지 않을 수 있습니다. Jetpack Keep을 사용하면 핵심 라이브러리에서 조회를 주석 처리합니다.

// In the core library.
interface AnalyticsPlugin {
    fun start()
}

object AnalyticsManager {
    // The annotation and the lookup share this constant so that they can't
    // refer to different classes.
    private const val VIDEO_TRACKER_CLASS =
        "com.example.analytics.video.VideoEventTracker"

    @UsesReflectionToConstruct(
        className = VIDEO_TRACKER_CLASS,
        parameterTypes = [],
    )
    fun initialize() {
        val tracker = try {
            Class.forName(VIDEO_TRACKER_CLASS)
                .getDeclaredConstructor()
                .newInstance() as AnalyticsPlugin
        } catch (e: ClassNotFoundException) {
            // Apps that don't play video leave out the add-on, so a missing
            // class isn't an error.
            return
        }
        // This is an ordinary interface call, not a reflective one, so R8
        // keeps start() without an annotation.
        tracker.start()
    }
}

주석은 다음 가이드라인을 따릅니다.

  • 리플렉션을 실행하는 코드에 주석을 추가합니다. 주석은 Class.forName를 호출하는 initialize에 있으며 부가기능의 클래스에는 없습니다.
  • 코드가 하는 일에 따라 주석을 선택합니다. 조회는 클래스의 인스턴스를 만들므로 코드는 @UsesReflectionToConstruct를 사용합니다.
  • 참조할 수 없는 클래스에는 className를 사용합니다. 부가기능의 클래스가 핵심 라이브러리의 컴파일 클래스 경로에 없으므로 주석은 classConstant를 사용할 수 없습니다.
  • 코드에서 호출하는 생성자만 유지합니다. 조회는 인수 없는 생성자를 호출하므로 parameterTypes는 빈 배열입니다.

핵심 라이브러리의 AAR을 빌드하면 플러그인은 R8이 initialize를 유지할 때마다 VideoEventTracker 및 인수가 없는 생성자를 유지하는 규칙을 추가합니다. SDK를 사용하는 앱은 Jetpack Keep을 사용하지 않더라도 이 규칙을 자동으로 적용하므로 부가기능에는 자체 규칙이 필요하지 않습니다. 부가기능의 규칙과 비교할 때 이 접근 방식에는 다음과 같은 이점이 있습니다.

  • 규칙은 조회 옆에 유지됩니다. 핵심 라이브러리는 조회와 규칙을 모두 소유하므로 두 라이브러리 간의 변경사항을 조정하는 대신 함께 업데이트합니다.
  • R8은 사용되지 않는 코드를 삭제할 수 있습니다. 앱에 부가기능이 포함되어 있지만 initialize를 호출하지 않는 경우 생성된 규칙이 적용되지 않으며 R8은 VideoEventTracker를 삭제할 수 있습니다. 부가기능의 규칙은 부가기능이 포함된 모든 앱에 부가기능을 유지합니다.
  • 린트가 주석이 없는 조회를 포착합니다. 핵심 라이브러리가 나중에 주석 없이 다른 리플렉션 조회를 추가하면 린트가 이를 신고하므로 라이브러리를 출시하기 전에 수정할 수 있습니다.

결과를 확인하려면 Android 라이브러리에 설명된 대로 생성된 규칙이 AAR의 proguard.txt 파일에 표시되는지 확인합니다. 그런 다음 부가기능이 포함된 앱의 출시 빌드를 테스트합니다. 누락된 규칙으로 인해 동영상 분석이 중단될 수 있기 때문입니다.

제한사항

Jetpack Keep은 아직 주석으로 클래스와 멤버를 찾는 리플렉션을 감지하거나 이에 대한 보관 규칙을 생성하지 않습니다. 이 기능은 출시 예정입니다.

그때까지는 이러한 종류의 리플렉션에 유지 규칙을 사용하세요. 예를 들어 다음 규칙은 R8이 유지하는 클래스에서 @SomeAnnotation로 주석이 달린 모든 멤버를 유지합니다.

-keepclassmembers class * {
    @com.example.annotation.SomeAnnotation <methods>;
    @com.example.annotation.SomeAnnotation <fields>;
}

Jetpack Keep이 사용 사례에 적합하지 않거나 다른 의견이 있는 경우 문제를 신고하세요.

추가 리소스

  • Jetpack Keep 샘플: 앱, Android 라이브러리, Kotlin 멀티플랫폼 라이브러리, JVM 라이브러리에서 Jetpack Keep을 사용하는 Android 스튜디오 프로젝트입니다.
  • Keep 규칙 사용 사례 및 예시: 일반적인 리플렉션 및 JNI 시나리오의 Keep 규칙입니다.
  • 권장사항 따르기: 범위가 좁은 유지 규칙을 작성하는 방법에 관한 안내입니다.