상위 수준에서 유지 규칙은 클래스(또는 서브클래스나 구현)와 해당 클래스 내에서 유지할 멤버(메서드, 생성자 또는 필드)를 지정합니다.
유지 규칙의 일반적인 문법은 다음과 같습니다. 하지만 특정 유지 옵션은 선택사항인 keep_option_modfier
를 허용하지 않습니다.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
다음은 keepclassmembers
을 유지 옵션으로, allowoptimization
을 수정자로 사용하고 com.example.MyClass
에서 someSpecificMethod()
을 유지하는 유지 규칙의 예입니다.
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
유지 옵션
유지 옵션은 유지 규칙의 첫 번째 부분입니다. 클래스의 어떤 측면을 보존할지 지정합니다. keep
, keepclassmembers
, keepclasseswithmembers
, keepnames
, keepclassmembernames
, keepclasseswithmembernames
의 6가지 유지 옵션이 있습니다.
다음 표에서는 이러한 보관 옵션을 설명합니다.
Keep 옵션 | 설명 |
---|---|
keepclassmembers |
최적화 후 클래스가 있는 경우에만 지정된 멤버를 유지합니다. |
keep |
지정된 클래스와 지정된 멤버 (필드 및 메서드)를 보존하여 최적화되지 않도록 합니다. 참고: keep 는 일반적으로 keep 옵션 수정자와 함께만 사용해야 합니다. keep 자체는 일치하는 클래스에서 어떤 종류의 최적화도 발생하지 않도록 하기 때문입니다. |
keepclasseswithmembers |
클래스 사양의 모든 멤버가 클래스에 있는 경우에만 클래스와 지정된 멤버를 유지합니다. |
keepclassmembernames |
지정된 클래스 멤버의 이름 변경을 방지하지만 클래스 또는 해당 멤버의 삭제는 방지하지 않습니다. 참고: 이 옵션의 의미는 종종 오해를 받습니다. 대신 동등한 -keepclassmembers,allowshrinking 을 사용하는 것이 좋습니다. |
keepnames |
클래스와 해당 구성원의 이름이 바뀌는 것을 방지하지만 사용되지 않는 것으로 간주되는 경우 완전히 삭제되는 것을 방지하지는 않습니다. 참고: 이 옵션의 의미는 종종 오해를 받습니다. 대신 동등한 -keep,allowshrinking 을 사용하는 것이 좋습니다. |
keepclasseswithmembernames |
클래스와 지정된 구성원의 이름이 바뀌는 것을 방지합니다. 단, 구성원이 최종 코드에 있는 경우에만 해당합니다. 코드 삭제를 방지하지는 않습니다. 참고: 이 옵션의 의미는 종종 오해를 받습니다. 대신 -keepclasseswithmembers,allowshrinking 을 사용하는 것이 좋습니다. |
올바른 보관 옵션 선택하기
올바른 유지 옵션을 선택하는 것은 앱에 적합한 최적화를 결정하는 데 매우 중요합니다. 특정 유지 옵션은 참조되지 않은 코드가 삭제되는 코드 축소 프로세스를 통해 코드를 축소하는 반면, 다른 옵션은 코드를 난독화하거나 이름을 바꿉니다. 다음 표에는 다양한 보관 옵션의 작업이 나와 있습니다.
Keep 옵션 | 클래스 축소 | 클래스를 난독화합니다. | 구성원 축소 | 구성원을 난독화합니다. |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
옵션 수정자 유지
보관 옵션 수정자는 보관 규칙의 범위와 동작을 제어하는 데 사용됩니다. 보관 규칙에 0개 이상의 보관 옵션 수정자를 추가할 수 있습니다.
keep 옵션 수정자의 가능한 값은 다음 표에 설명되어 있습니다.
값 | 설명 |
---|---|
allowoptimization |
지정된 요소를 최적화할 수 있습니다. 하지만 지정된 요소는 이름이 변경되거나 삭제되지 않습니다. |
allowobfucastion |
지정된 요소의 이름 바꾸기를 허용합니다. 하지만 요소가 삭제되거나 다른 방식으로 최적화되지는 않습니다. |
allowshrinking |
R8에서 지정된 요소를 참조하는 항목을 찾지 못하는 경우 해당 요소를 삭제할 수 있습니다. 하지만 요소의 이름이 변경되거나 다른 방식으로 최적화되지는 않습니다. |
includedescriptorclasses |
보관되는 메서드 (매개변수 유형 및 반환 유형) 및 필드 (필드 유형)의 설명자에 표시되는 모든 클래스를 보관하도록 R8에 지시합니다. |
allowaccessmodification |
R8이 최적화 프로세스 중에 클래스, 메서드, 필드의 액세스 수정자 (public , private , protected )를 변경 (일반적으로 확대)할 수 있도록 허용합니다. |
allowrepackage |
R8이 기본 (루트) 패키지를 비롯한 다른 패키지로 클래스를 이동하도록 허용합니다. |
클래스 사양
보관 규칙의 일부로 클래스, 상위 클래스 또는 구현된 인터페이스를 지정해야 합니다. java.lang.String
과 같은 java.lang
네임스페이스의 클래스를 비롯한 모든 클래스는 정규화된 Java 이름을 사용하여 지정해야 합니다. 사용해야 하는 이름을 이해하려면 생성된 Java 이름 가져오기에 설명된 도구를 사용하여 바이트 코드를 검사하세요.
다음 예는 MaterialButton
클래스를 지정하는 방법을 보여줍니다.
- 올바름:
com.google.android.material.button.MaterialButton
- 잘못됨:
MaterialButton
클래스 사양은 유지해야 하는 클래스 내의 구성원도 지정합니다. 다음 규칙은 MaterialButton
클래스와 모든 구성원을 유지합니다.
-keep class com.google.android.material.button.MaterialButton { *; }
서브클래스 및 구현
인터페이스를 구현하는 서브클래스 또는 클래스를 타겟팅하려면 각각 extend
및 implements
를 사용하세요.
예를 들어 다음과 같이 서브클래스 Foo
이 있는 클래스 Bar
이 있는 경우
class Foo : Bar()
다음 유지 규칙은 Bar
의 모든 하위 클래스를 보존합니다. 유지 규칙에는 슈퍼클래스 Bar
자체가 포함되지 않습니다.
-keep class * extends Bar
Bar
를 구현하는 Foo
클래스가 있는 경우:
class Foo : Bar
다음 유지 규칙은 Bar
를 구현하는 모든 클래스를 보존합니다. 보관 규칙에는 인터페이스 Bar
자체가 포함되지 않습니다.
-keep class * implements Bar
액세스 수정자
public
, private
, static
, final
과 같은 액세스 수정자를 지정하여 보관 규칙을 더 정확하게 만들 수 있습니다.
예를 들어 다음 규칙은 api
패키지 및 하위 패키지 내의 모든 public
클래스와 이러한 클래스의 모든 공개 및 보호된 멤버를 유지합니다.
-keep public class com.example.api.** { public protected *; }
클래스 내의 멤버에 한정자를 사용할 수도 있습니다. 예를 들어 다음 규칙은 Utils
클래스의 public static
메서드만 유지합니다.
-keep class com.example.Utils {
public static void *(...);
}
Kotlin 관련 수정자
R8은 internal
, suspend
과 같은 Kotlin 전용 수정자를 지원하지 않습니다.
이러한 필드를 유지하려면 다음 가이드라인을 따르세요.
internal
클래스, 메서드 또는 필드를 유지하려면 공개로 취급하세요. 예를 들어 다음 Kotlin 소스를 고려해 보세요.package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
internal
클래스, 메서드, 필드는 Kotlin 컴파일러에서 생성된.class
파일에서public
이므로 다음 예와 같이public
키워드를 사용해야 합니다.-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
suspend
멤버가 컴파일되면 keep 규칙에서 컴파일된 서명을 일치시킵니다.예를 들어 다음 스니펫에 표시된 대로 정의된
fetchUser
함수가 있는 경우suspend fun fetchUser(id: String): User
컴파일되면 바이트 코드의 서명은 다음과 같이 표시됩니다.
public final Object fetchUser(String id, Continuation<? super User> continuation);
이 함수의 유지 규칙을 작성하려면 컴파일된 이 시그니처와 일치하거나
...
를 사용해야 합니다.컴파일된 서명을 사용하는 예는 다음과 같습니다.
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }
...
을 사용하는 예는 다음과 같습니다.-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
회원 사양
클래스 사양에는 보존할 클래스 멤버가 선택적으로 포함됩니다. 클래스에 하나 이상의 구성원을 지정하면 해당 구성원에게만 규칙이 적용됩니다.
예를 들어 특정 클래스와 모든 멤버를 유지하려면 다음을 사용합니다.
-keep class com.myapp.MyClass { *; }
클래스만 유지하고 멤버는 유지하지 않으려면 다음을 사용하세요.
-keep class com.myapp.MyClass
대부분의 경우 일부 구성원을 지정하는 것이 좋습니다. 예를 들어 다음 예시에서는 공개 필드 text
와 공개 메서드 updateText()
을 클래스 MyClass
내에 유지합니다.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
모든 공개 필드와 공개 메서드를 유지하려면 다음 예시를 참고하세요.
-keep public class com.example.api.ApiClient {
public *;
}
메서드
유지 규칙의 멤버 사양에서 메서드를 지정하는 문법은 다음과 같습니다.
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
예를 들어 다음 유지 규칙은 void를 반환하고 String
를 사용하는 setLabel()
라는 공개 메서드를 유지합니다.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
다음과 같이 <methods>
를 바로가기로 사용하여 클래스의 모든 메서드를 일치시킬 수 있습니다.
-keep class com.example.MyView {
<methods>;
}
반환 유형 및 매개변수 유형의 유형을 지정하는 방법을 자세히 알아보려면 유형을 참고하세요.
생성자
생성자를 지정하려면 <init>
를 사용합니다. 보관 규칙의 멤버 사양에서 생성자를 지정하는 문법은 다음과 같습니다.
[<access_modifier>] <init>(parameter_types);
예를 들어 다음 유지 규칙은 Context
와 AttributeSet
를 사용하는 맞춤 View
생성자를 유지합니다.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
모든 공개 생성자를 유지하려면 다음 예시를 참고하세요.
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
필드
보관 규칙의 구성원 사양에서 필드를 지정하는 문법은 다음과 같습니다.
[<access_modifier>...] [<type>] <field_name>;
예를 들어 다음 유지 규칙은 userId
라는 비공개 문자열 필드와 STATUS_ACTIVE
라는 공개 정적 정수 필드를 유지합니다.
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
다음과 같이 <fields>
을 단축키로 사용하여 클래스의 모든 필드를 일치시킬 수 있습니다.
-keep class com.example.models.User {
<fields>;
}
패키지 수준 함수
클래스 외부에 정의된 Kotlin 함수 (일반적으로 최상위 함수라고 함)를 참조하려면 Kotlin 컴파일러가 암시적으로 추가한 클래스에 생성된 Java 이름을 사용해야 합니다. 클래스 이름은 Kotlin 파일 이름에 Kt
가 추가된 것입니다. 예를 들어 다음과 같이 정의된 MyClass.kt
라는 Kotlin 파일이 있다고 가정해 보겠습니다.
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
isEmailValid
함수의 유지 규칙을 작성하려면 클래스 사양이 생성된 클래스 MyClassKt
을 타겟팅해야 합니다.
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
유형
이 섹션에서는 keep 규칙 멤버 사양에서 반환 유형, 매개변수 유형, 필드 유형을 지정하는 방법을 설명합니다. Kotlin 소스 코드와 다른 경우 생성된 Java 이름을 사용하여 유형을 지정해야 합니다.
기본 유형
기본 유형을 지정하려면 Java 키워드를 사용합니다. R8은 boolean
, byte
, short
, char
, int
, long
, float
, double
기본 유형을 인식합니다.
기본 유형이 있는 규칙의 예는 다음과 같습니다.
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
일반 유형
컴파일 중에 Kotlin/Java 컴파일러는 일반 유형 정보를 삭제하므로 일반 유형이 포함된 유지 규칙을 작성할 때는 원래 소스 코드가 아닌 컴파일된 코드 표현을 타겟팅해야 합니다. 일반 유형이 변경되는 방식에 대해 자세히 알아보려면 타입 삭제를 참고하세요.
예를 들어 Box.kt
에 정의된 경계가 없는 일반 유형이 있는 다음 코드가 있다고 가정해 보겠습니다.
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
타입 삭제 후 T
이 Object
로 대체됩니다. 클래스 생성자와 메서드를 유지하려면 규칙에서 일반 T
대신 java.lang.Object
를 사용해야 합니다.
보관 규칙의 예는 다음과 같습니다.
# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
public init(java.lang.Object);
public java.lang.Object getItem();
}
NumberBox.kt
에 바운드된 일반 유형이 있는 다음 코드가 있는 경우:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
이 경우 유형 삭제는 T
을 바운드인 java.lang.Number
으로 대체합니다.
보관 규칙의 예는 다음과 같습니다.
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
앱별 일반 유형을 기본 클래스로 사용하는 경우 기본 클래스의 keep 규칙도 포함해야 합니다.
예를 들어 다음 코드의 경우
package com.myapp.data
data class UnpackOptions(val useHighPriority: Boolean)
// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}
다음과 같이 단일 규칙으로 UnpackOptions
클래스와 Box
클래스 메서드를 모두 유지하기 위해 includedescriptorclasses
와 함께 유지 규칙을 사용할 수 있습니다.
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
객체 목록을 처리하는 특정 함수를 유지하려면 함수의 서명과 정확히 일치하는 규칙을 작성해야 합니다. 일반 유형은 삭제되므로 List<Product>
과 같은 매개변수는 java.util.List
로 표시됩니다.
예를 들어 다음과 같이 Product
객체 목록을 처리하는 함수가 있는 유틸리티 클래스가 있다고 가정해 보겠습니다.
package com.myapp.utils
import com.myapp.data.Product
import android.util.Log
class DataProcessor {
// This is the function we want to keep
fun processProducts(products: List<Product>) {
Log.d("DataProcessor", "Processing ${products.size} products.")
// Business logic ...
}
}
// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)
다음 keep 규칙을 사용하여 processProducts
함수만 보호할 수 있습니다.
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
배열 유형
배열의 각 차원에 대한 구성요소 유형에 []
를 추가하여 배열 유형을 지정합니다. 이는 클래스 유형과 기본 유형 모두에 적용됩니다.
- 1차원 클래스 배열:
java.lang.String[]
- 2차원 기본 배열:
int[][]
예를 들어 다음과 같은 코드가 있다고 가정해 보겠습니다.
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
다음 보관 규칙을 사용할 수 있습니다.
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
와일드 카드
다음 표에서는 와일드 카드를 사용하여 특정 패턴과 일치하는 여러 클래스 또는 멤버에 유지 규칙을 적용하는 방법을 보여줍니다.
와일드 카드 | 수업 또는 회원에 적용 | 설명 |
---|---|---|
** | 둘 다 | 가장 일반적으로 사용됩니다. 패키지 구분 기호 수를 포함한 모든 유형 이름과 일치합니다. 이는 패키지 및 하위 패키지 내의 모든 클래스를 일치시키는 데 유용합니다. |
* | 둘 다 | 클래스 사양의 경우 패키지 구분자 (. )가 포함되지 않은 유형 이름의 모든 부분과 일치합니다. 멤버 사양의 경우 모든 메서드 또는 필드 이름과 일치합니다. 단독으로 사용되는 경우 ** 의 별칭이기도 합니다. |
? | 둘 다 | 클래스 또는 구성원 이름의 모든 단일 문자와 일치합니다. |
*** | 멤버 | 기본 유형 (예: int ), 클래스 유형 (예: java.lang.String ), 모든 차원의 배열 유형 (예: byte[][] )을 비롯한 모든 유형과 일치합니다. |
... | 멤버 | 메서드의 매개변수 목록과 일치합니다. |
% | 멤버 | `int`, `float`, `boolean` 등 모든 기본 유형과 일치합니다. |
다음은 특수 와일드 카드를 사용하는 방법의 몇 가지 예입니다.
입력으로 다른 기본 유형을 사용하는 이름이 같은 메서드가 여러 개 있는 경우
%
를 사용하여 모든 메서드를 유지하는 유지 규칙을 작성할 수 있습니다. 예를 들어 이DataStore
클래스에는 여러setValue
메서드가 있습니다.class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }
다음 유지 규칙은 모든 메서드를 유지합니다.
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }
이름이 한 글자씩 다른 클래스가 여러 개 있는 경우
?
를 사용하여 모든 클래스를 유지하는 유지 규칙을 작성하세요. 예를 들어 다음과 같은 클래스가 있다고 가정해 보겠습니다.com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}
다음 유지 규칙은 모든 클래스를 유지합니다.
-keep class com.example.models.UserV?
Example
및AnotherExample
클래스 (루트 수준 클래스인 경우)는 일치시키되com.foo.Example
는 일치시키지 않으려면 다음 유지 규칙을 사용하세요.-keep class *Example
*를 단독으로 사용하면 **의 별칭으로 작동합니다. 예를 들어 다음 보관 규칙은 동일합니다.
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
생성된 Java 이름 검사
유지 규칙을 작성할 때는 Java 바이트 코드로 컴파일된 후의 이름을 사용하여 클래스와 기타 참조 유형을 지정해야 합니다 (예는 클래스 사양 및 유형 참고). 코드에 생성된 Java 이름을 확인하려면 Android 스튜디오에서 다음 도구 중 하나를 사용하세요.
- APK 분석 도구
- Kotlin 소스 파일을 연 상태에서 Tools > Kotlin > Show Kotlin Bytecode > Decompile로 이동하여 바이트 코드를 검사합니다.