Outros tipos de regra

O R8 permite adicionar regras que afetam a otimização do app, além das regras de manutenção. Adicione essas regras no mesmo arquivo proguard-rules.pro em que você mantém as regras de retenção.

As regras se enquadram nas seguintes categorias:

  • Suposições
    • -assumevalues
    • -assumenosideeffects
  • Outras otimizações
    • -convertchecknotnull
    • -maximumremovedandroidloglevel

Suposições

Essas regras informam ao R8 que ele pode fazer suposições específicas sobre o comportamento de um código específico em tempo de execução.

-assumevalues

A regra -assumevalues informa ao R8 que o valor de um campo ou o valor de retorno de um método é sempre uma constante específica ou está dentro de um intervalo definido no tempo de execução. -assumevalues é destinado a coisas como valores de flag que, no momento da criação, são conhecidos por ter valores específicos durante a execução.

A análise estática padrão do R8 talvez não consiga determinar os valores de tempo de execução dos membros. Com -assumevalues, você informa ao R8 para presumir o valor ou intervalo especificado ao otimizar o código. Isso permite que o R8 faça otimizações agressivas.

A sintaxe de -assumevalues é semelhante à de manter um member_specification, mas também inclui um return clause da seguinte forma:

<member_specification> return <value> | <range>

Os argumentos <value> e <range> aceitam os seguintes valores e tipos:

  • Valores especiais: true, false, null, @NonNull
  • Valores primitivos: int
  • Referências de campo estático (incluindo campos de enumeração)

Para definir um intervalo, use o formato inclusivo min..max. Por exemplo, o snippet a seguir mostra que a variável CUSTOM_VAL aceita de 26 a 2147483647:

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

Você pode usar essa regra nas seguintes situações:

  • Para bibliotecas: para garantir que, quando os apps forem otimizados, todos os hooks de depuração locais sejam removidos do código da biblioteca pública.
  • Para apps: para remover itens como código de depuração de um app de lançamento. É preferível usar variantes de build e variantes de conjuntos de origem ou constantes específicas, mas se os conjuntos de origem de variantes não funcionarem para seu caso ou se você precisar de uma garantia maior de que os caminhos de código foram totalmente removidos, use -assumevalues.

O exemplo a seguir mostra uma classe em que o R8 remove ferramentas de depuração da versão otimizada de um app:

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();
    }
}

A regra a seguir mostra como informar ao R8 que a variável IS_OPTIMIZED_VERSION sempre deve ser definida como true.

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

-assumenosideeffects

A regra -assumenosideeffects informa ao R8 que ele pode presumir que os membros especificados não têm efeitos colaterais. O R8 pode remover completamente chamadas para esses métodos que não têm valores de retorno ou que retornam um valor fixo.

A sintaxe de -assumenosideeffects é semelhante à de manter um member_specification.

O exemplo a seguir mostra como informar ao R8 que todos os métodos public static chamados log na classe DebugLogger não têm efeitos colaterais, o que permite remover chamadas para esses métodos.

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

Outras otimizações

Estas são algumas otimizações mais avançadas que não estão ativadas por padrão. Ao ativá-las, você permite que o R8 otimize o código conforme instruído, além das otimizações padrão.

-convertchecknotnull

Use a regra -convertchecknotnull para otimizar as verificações de nulo. Isso se aplica a qualquer método que usa um parâmetro de objeto e gera uma exceção se o objeto for nulo, semelhante a uma asserção padrão do Kotlin. O tipo e a mensagem da exceção não são necessariamente os mesmos, mas o comportamento de falha condicional é.

Se uma regra -convertchecknotnull corresponder a um determinado método, cada chamada a esse método será substituída por uma chamada a getClass() no primeiro argumento. As chamadas para getClass() servem como uma verificação nula substituta e permitem que o R8 remova argumentos extras da verificação nula original, como alocações de strings caras.

A sintaxe de -convertchecknotnull é a seguinte:

-convertchecknotnull <class_specification> {
   <member_specification>;
}

Por exemplo, se você tiver a classe Preconditions com o método checkNotNull da seguinte forma:

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

Use a seguinte regra:

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

A regra converte todas as chamadas para checkNotNull() em chamadas para getClass no primeiro argumento. Neste exemplo, uma chamada para checkNotNull(bar) é substituída por bar.getClass(). Se bar fosse null, bar.getClass() geraria um NullPointerException, alcançando um efeito semelhante de verificação de nulo, mas de maneira mais eficiente.

-maximumremovedandroidloglevel

Esse tipo de regra remove instruções de registro do Android (como Log.w(...) e Log.isLoggable(...)) em um determinado nível de registro ou abaixo dele.

A sintaxe de maximumremovedandroidloglevel é a seguinte:

-maximumremovedandroidloglevel <log_level> [<class_specification>]

Se você não fornecer o class_specification opcional, o R8 vai aplicar a remoção de registros a todo o app.

Os níveis de registro são os seguintes:

Rótulo do registro

Nível do registro

VERBOSE

2

DEBUG

3

INFO

4

AVISO

5

ERROR

6

ASSERT

7

Por exemplo, se você tiver o seguinte código:

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");
  }
}

Com a seguinte regra:

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

O código otimizado é este:

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

Se você fornecer vários níveis máximos de registro para o mesmo método, o R8 usará o nível mínimo. Por exemplo, considerando as seguintes regras:

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

então o nível máximo de remoção de registros para foo() é 4.