其他规则类型

除了 keep 规则之外,R8 还允许您添加会影响应用优化的规则。在您维护保留规则的同一 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;
}

您可以在以下情况下使用此规则:

  • 对于库:确保在优化应用时,所有本地调试钩子都已从公共库代码中移除。
  • 对于应用:从发布版应用中移除调试代码等内容。最好使用 build 变体和特定源集或常量的变体,但如果变体源集不适合您的情形,或者您需要更强有力的保证来确保代码路径已完全移除,请使用 -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();
    }
}

以下规则展示了如何告知 R8 变量 IS_OPTIMIZED_VERSION 始终应设置为 true

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

-assumenosideeffects

-assumenosideeffects 规则会告知 R8,它可以假设指定成员没有副作用。R8 可以完全移除对没有返回值或返回固定值的此类方法的调用。

-assumenosideeffects 的语法与保留 member_specification 的语法类似。

以下示例展示了如何告知 R8,DebugLogger 类中所有名为 logpublic 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>;
}

例如,如果您有一个类 Preconditions,其方法 checkNotNull 如下所示:

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()。如果 barnullbar.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

错误

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。