추가 규칙 유형

R8을 사용하면 keep 규칙 외에 앱 최적화에 영향을 미치는 규칙을 추가할 수 있습니다. 보관 규칙을 유지하는 동일한 proguard-rules.pro 파일에 이러한 규칙을 추가합니다.

규칙은 다음 카테고리로 분류됩니다.

  • 가정
    • -assumevalues
    • -assumenosideeffects
  • 기타 최적화
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

가정

이러한 규칙은 R8에 런타임에 특정 코드 동작에 관해 특정 가정을 할 수 있다고 알려줍니다.

-assumevalues

-assumevalues 규칙은 필드의 값이나 메서드의 반환 값이 항상 특정 상수이거나 런타임에 정의된 범위 내에 있음을 R8에 알려줍니다. -assumevalues는 빌드 시 런타임에 특정 값이 있는 것으로 알려진 플래그 값과 같은 항목에 사용됩니다.

R8의 표준 정적 분석은 멤버의 런타임 값을 확인할 수 없습니다. -assumevalues을 사용하면 코드를 최적화할 때 지정된 값 또는 범위를 가정하도록 R8에 지시할 수 있습니다. 이렇게 하면 R8이 적극적인 최적화를 실행할 수 있습니다.

-assumevalues 문법은 member_specification을 유지하는 문법과 유사하지만 다음과 같이 return clause이 추가로 포함됩니다.

<member_specification> return <value> | <range>

<value><range> 인수는 다음 값과 유형을 지원합니다.

  • 특수 값: true, false, null, @NonNull
  • 원시 값: int
  • 정적 필드 참조 (열거형 필드 포함)

범위를 정의하려면 포함된 min..max 형식을 사용합니다. 예를 들어 다음 스니펫은 변수 CUSTOM_VAL가 26~2147483647을 허용함을 보여줍니다.

-assumevalues public class com.example.Foo {
    public static int CUSTOM_VAL return 26..2147483647;
}

이 규칙은 다음과 같은 상황에서 사용할 수 있습니다.

  • 라이브러리: 앱이 최적화될 때 모든 로컬 디버깅 후크가 공개 라이브러리 코드에서 삭제되도록 합니다.
  • 앱의 경우: 출시 앱에서 디버그 코드와 같은 항목을 삭제합니다. 빌드 변형과 특정 소스 세트 또는 상수의 변형을 사용하는 것이 좋지만 변형 소스 세트가 적합하지 않거나 코드 경로가 완전히 삭제된다는 더 강력한 보장이 필요한 경우 -assumevalues를 사용하세요.

다음 예는 R8이 최적화된 앱 버전에서 디버그 도구를 삭제하는 클래스를 보여줍니다.

package com.example;

public class MyConfig {
    // This field is initialized to false but is overwritten by a resource
    // value or other mechanism in the final build process. R8's static analysis
    // might see the initial 'false' but the runtime value is known to be
    // 'true'.
    public static final boolean IS_OPTIMIZED_VERSION = false;
}

// In another class:
public void initFeatures() {
    if (MyConfig.IS_OPTIMIZED_VERSION) {
        System.out.println("Starting optimized features...");
        android.util.Log.d(TAG, "Starting optimized features...");
        initOptimizedService();
    } else {
        android.util.Log.d(TAG, "Starting debug/logging features...");
        initDebugTools();
    }
}

다음 규칙은 변수 IS_OPTIMIZED_VERSION가 항상 true로 설정되어야 한다고 R8에 알리는 방법을 보여줍니다.

-assumevalues class com.example.MyConfig {
    public static final boolean IS_OPTIMIZED_VERSION return true;
}

-assumenosideeffects

-assumenosideeffects 규칙은 지정된 멤버에게 부작용이 없다고 가정할 수 있다고 R8에 알려줍니다. R8은 반환 값이 없거나 고정 값을 반환하는 이러한 메서드에 대한 호출을 완전히 삭제할 수 있습니다.

-assumenosideeffects의 문법은 member_specification을 유지하는 문법과 비슷합니다.

다음 샘플에서는 DebugLogger 클래스 내에서 이름이 log인 모든 public static 메서드에 부작용이 없어야 함을 R8에 알리는 방법을 보여줍니다. 이렇게 하면 이러한 메서드 호출을 삭제할 수 있습니다.

-assumenosideeffects class com.example.DebugLogger {
    public static void log(...);
}

기타 최적화

다음은 기본적으로 사용 설정되지 않는 고급 최적화입니다. 이러한 기능을 사용 설정하면 기본 최적화 외에도 R8이 지침에 따라 코드를 최적화할 수 있습니다.

-convertchecknotnull

-convertchecknotnull 규칙을 사용하여 null 검사를 최적화할 수 있습니다. 이는 객체 매개변수를 사용하고 객체가 null인 경우 예외를 발생시키는 모든 메서드에 적용되며 표준 Kotlin 어설션과 유사합니다. 예외 유형과 메시지는 반드시 동일하지는 않지만 조건부 비정상 종료 동작은 동일합니다.

-convertchecknotnull 규칙이 지정된 메서드와 일치하면 해당 메서드에 대한 각 호출이 첫 번째 인수에 대한 getClass() 호출로 대체됩니다. getClass() 호출은 대체 null 검사 역할을 하며 R8이 비용이 많이 드는 문자열 할당과 같은 원래 null 검사의 추가 인수를 삭제하도록 합니다.

-convertchecknotnull의 문법은 다음과 같습니다.

-convertchecknotnull <class_specification> {
   <member_specification>;
}

예를 들어 다음과 같이 checkNotNull 메서드가 있는 Preconditions 클래스가 있는 경우

class Preconditions {
    fun <T> checkNotNull(value: T?): T {
        if (value == null) {
            throw NullPointerException()
        } else {
            return value
        }
    }
}

다음 규칙을 사용합니다.

-convertchecknotnull class com.example.package.Preconditions {
  void checkNotNull(java.lang.Object);
}

이 규칙은 checkNotNull()에 대한 모든 호출을 첫 번째 인수의 getClass에 대한 호출로 변환합니다. 이 예시에서는 checkNotNull(bar) 호출이 bar.getClass()로 대체됩니다. barnull이면 bar.getClass()에서 NullPointerException를 발생시켜 유사한 null 검사 효과를 더 효율적으로 달성합니다.

-maximumremovedandroidloglevel

이 규칙 유형은 특정 로그 수준 이하에서 Android 로깅 문 (예: Log.w(...) 및 Log.isLoggable(...))을 삭제합니다.

maximumremovedandroidloglevel의 문법은 다음과 같습니다.

-maximumremovedandroidloglevel <log_level> [<class_specification>]

선택사항인 class_specification를 제공하지 않으면 R8이 전체 앱에 로그 삭제를 적용합니다.

로그 수준은 다음과 같습니다.

로그 라벨

로그 수준

VERBOSE

2

DEBUG

3

INFO

4

주의

5

ERROR

6

ASSERT

7

예를 들어 다음과 같은 코드가 있다고 가정해 보겠습니다.

class Foo {
  private static final String TAG = "Foo";
  void logSomething() {
    if (Log.isLoggable(TAG, WARNING)) {
      Log.e(TAG, "Won't be logged");
    }
    Log.w(TAG, "Won't be logged");
    Log.e(TAG, "Will be logged");
  }
}

다음 규칙을 사용합니다.

# A level of 5 corresponds to a log level of WARNING.
-maximumremovedandroidloglevel 5 class Foo { void logSomething(); }

최적화된 코드는 다음과 같습니다.

class Foo {
  private static final String TAG = "Foo";
  void logSomething() {
    Log.e(TAG, "Will be logged");
  }
}

동일한 메서드에 최대 로그 수준을 여러 개 제공하면 R8은 최소 수준을 사용합니다. 예를 들어 다음과 같은 규칙이 있다고 가정해 보겠습니다.

-maximumremovedandroidloglevel 7 class ** { void foo(); }
-maximumremovedandroidloglevel 4 class ** { void foo(); }

foo()의 최대 삭제된 로그 수준은 4입니다.