주석에서 보관 규칙 생성

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

Jetpack Keep은 코드베이스가 변경될 때 R8 최적화가 계속 효과를 발휘하도록 지원합니다.

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

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

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

Jetpack Keep 작동 방식

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

# 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. 린트가 자동으로 적용할 수 있는 수정사항을 적용합니다.

    ./gradlew :app:lintFix
    

    린트는 코드가 리플렉션하는 클래스와 멤버를 결정할 수 있는 경우 주석을 자동으로 추가할 수 있습니다.

  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`classConstant 또는 `className`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)
    }
}

앱 외부에서 호출되는 코드 유지

앱의 Kotlin 또는 Java 코드 외부에서 호출되는 코드(예: 네이티브 코드가 JNI를 통해 호출하는 메서드)에는 @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에 관한 자세한 내용은 네이티브 코드와의 상호작용 을 참고하세요.

기본적으로 @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 태스크를 등록하여 이 작업을 실행합니다.

규칙을 확인하려면 AAR을 빌드합니다(예: ./gradlew :mylibrary:assembleRelease). 그런 다음 생성된 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을 빌드할 때 플러그인은 생성된 규칙을 AAR의 proguard.txt 파일에 동일한 방식으로 Android 라이브러리에 추가합니다. 플러그인은 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 libraries에 설명된 대로 생성된 규칙이 AAR's proguard.txt 파일에 표시되는지 확인합니다. 그런 다음 애드온을 포함하는 앱의 출시 빌드를 테스트합니다. 누락된 규칙이 동영상 애널리틱스를 중단시키는 위치이기 때문입니다.

추가 리소스

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