Em um nível alto, uma regra de preservação especifica uma classe (ou subclasse ou implementação) e, em seguida, membros (métodos, construtores ou campos) dentro dessa classe para preservar.
A sintaxe geral de uma regra de preservação é a seguinte, mas algumas opções de preservação não aceitam o keep_option_modfier
opcional.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Confira um exemplo de regra de preservação que usa keepclassmembers
como a opção de preservação, allowoptimization
como o modificador e mantém someSpecificMethod()
de com.example.MyClass
:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Opção "Manter"
A opção "manter" é a primeira parte da regra de retenção. Ele especifica quais aspectos de uma classe preservar. Há seis opções de retenção diferentes: keep
, keepclassmembers
, keepclasseswithmembers
, keepnames
, keepclassmembernames
e keepclasseswithmembernames
.
A tabela a seguir descreve essas opções de retenção:
Opção "Keep" | Description |
---|---|
keepclassmembers |
Preserva apenas os membros especificados se a classe existir após a otimização. |
keep |
Preserva as classes e os membros (campos e métodos) especificados, impedindo que sejam otimizados. Observação: geralmente, keep só deve ser usado com modificadores de opção de manutenção porque keep por si só impede que otimizações de qualquer tipo ocorram em classes correspondentes. |
keepclasseswithmembers |
Preserva uma classe e os membros especificados somente se a classe tiver todos os membros da especificação. |
keepclassmembernames |
Impede a renomeação de membros de classe especificados, mas não impede a remoção da classe ou dos membros dela. Observação:o significado dessa opção costuma ser mal compreendido. Considere usar o equivalente -keepclassmembers,allowshrinking . |
keepnames |
Impede a mudança de nome de classes e membros, mas não impede que eles sejam removidos completamente se forem considerados não utilizados. Observação:o significado dessa opção costuma ser mal compreendido. Considere usar o equivalente -keep,allowshrinking . |
keepclasseswithmembernames |
Impede a renomeação de classes e dos membros especificados, mas apenas se eles existirem no código final. Ele não impede a remoção de código. Observação:o significado dessa opção costuma ser mal compreendido. Considere usar a opção equivalente -keepclasseswithmembers,allowshrinking . |
Escolher a opção de retenção certa
Escolher a opção de manutenção certa é crucial para determinar a otimização adequada para seu app. Algumas opções de manutenção reduzem o código, um processo em que o código não referenciado é removido, enquanto outras ofuscam ou renomeiam o código. A tabela a seguir indica as ações das várias opções de manter:
Opção "Keep" | Reduz classes | Ofusca classes | Reduz os membros | Ofusca membros |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Modificador de opção "Manter"
Um modificador de opção de manutenção é usado para controlar o escopo e o comportamento de uma regra de manutenção. É possível adicionar zero ou mais modificadores de opção de retenção à sua regra de retenção.
Os valores possíveis para um modificador de opção de manutenção são descritos na tabela a seguir:
Valor | Descrição |
---|---|
allowoptimization |
Permite a otimização dos elementos especificados. No entanto, os elementos especificados não são renomeados nem removidos. |
allowobfucastion |
Permite renomear os elementos especificados. No entanto, os elementos não são removidos nem otimizados de outra forma. |
allowshrinking |
Permite a remoção dos elementos especificados se o R8 não encontrar referências a eles. No entanto, os elementos não são renomeados nem otimizados de outra forma. |
includedescriptorclasses |
Instrui o R8 a manter todas as classes que aparecem nos descritores dos métodos (tipos de parâmetro e tipos de retorno) e campos (tipos de campo) que estão sendo mantidos. |
allowaccessmodification |
Permite que o R8 mude (normalmente amplie) os modificadores de acesso (public , private , protected ) de classes, métodos e campos durante o processo de otimização. |
allowrepackage |
Permite que o R8 mova classes para pacotes diferentes, incluindo o pacote padrão (raiz). |
Especificação de classe
É necessário especificar uma classe, uma superclasse ou uma interface implementada como parte de uma regra de
preservação. Todas as classes, incluindo as do namespace java.lang
, como
java.lang.String
, precisam ser especificadas usando o nome Java totalmente qualificado. Para
entender os nomes que devem ser usados, inspecione o bytecode usando as ferramentas
descritas em Receber nomes Java gerados.
O exemplo a seguir mostra como especificar a classe MaterialButton
:
- Correto:
com.google.android.material.button.MaterialButton
- Incorreto:
MaterialButton
As especificações de classe também especificam os membros dentro de uma classe que
precisam ser mantidos. A regra a seguir mantém a classe MaterialButton
e todos os membros dela:
-keep class com.google.android.material.button.MaterialButton { *; }
Subclasses e implementações
Para segmentar uma subclasse ou classe que implementa uma interface, use extend
e implements
, respectivamente.
Por exemplo, se você tiver a classe Bar
com a subclasse Foo
da seguinte maneira:
class Foo : Bar()
A regra de preservação a seguir preserva todas as subclasses de Bar
. A regra de
preservação não inclui a superclasse Bar
.
-keep class * extends Bar
Se você tiver a classe Foo
que implementa Bar
:
class Foo : Bar
A regra de preservação a seguir preserva todas as classes que implementam Bar
. A regra de preservação não inclui a interface Bar
.
-keep class * implements Bar
Modificador de acesso
É possível especificar modificadores de acesso, como public
, private
, static
e
final
, para tornar as regras de retenção mais precisas.
Por exemplo, a regra a seguir mantém todas as classes public
no pacote api
e nos subpacotes, além de todos os membros públicos e protegidos nessas classes.
-keep public class com.example.api.** { public protected *; }
Você também pode usar modificadores para os membros de uma classe. Por exemplo, a regra a seguir mantém apenas os métodos public static
de uma classe Utils
:
-keep class com.example.Utils {
public static void *(...);
}
Modificadores específicos do Kotlin
O R8 não é compatível com modificadores específicos do Kotlin, como internal
e suspend
.
Siga estas diretrizes para manter esses campos.
Para manter uma classe, um método ou um campo
internal
, trate-o como público. Por exemplo, considere a seguinte origem Kotlin:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
As classes, os métodos e os campos
internal
sãopublic
nos arquivos.class
produzidos pelo compilador Kotlin. Portanto, use a palavra-chavepublic
conforme mostrado no exemplo a seguir:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
Quando um membro
suspend
é compilado, corresponda à assinatura compilada na regra de permanência.Por exemplo, se você tiver a função
fetchUser
definida conforme mostrado no snippet a seguir:suspend fun fetchUser(id: String): User
Quando compilada, a assinatura no bytecode fica assim:
public final Object fetchUser(String id, Continuation<? super User> continuation);
Para escrever uma regra de preservação para essa função, é preciso corresponder a essa assinatura compilada ou usar
...
.Veja um exemplo de uso da assinatura compilada:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }
Um exemplo de uso de
...
é:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Especificação de membro
A especificação de classe inclui opcionalmente os membros da classe a serem preservados. Se você especificar um ou mais membros para uma turma, a regra será aplicada apenas a eles.
Por exemplo, para preservar uma classe específica e todos os membros dela, use o seguinte:
-keep class com.myapp.MyClass { *; }
Para preservar apenas a classe e não os membros dela, use o seguinte:
-keep class com.myapp.MyClass
Na maioria das vezes, você vai querer especificar alguns membros. Por exemplo, o
exemplo a seguir mantém o campo público text
e o método público
updateText()
na classe MyClass
.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
Para manter todos os campos e métodos públicos, consulte o exemplo a seguir:
-keep public class com.example.api.ApiClient {
public *;
}
Métodos
A sintaxe para especificar um método na especificação de membro de uma regra de retenção é a seguinte:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
Por exemplo, a regra de preservação a seguir mantém um método público chamado setLabel()
que retorna void e usa um String
.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Use <methods>
como um atalho para corresponder a todos os métodos em uma classe da seguinte forma:
-keep class com.example.MyView {
<methods>;
}
Para saber mais sobre como especificar tipos para tipos de retorno e tipos de parâmetros, consulte Tipos.
Construtores
Para especificar um construtor, use <init>
. A sintaxe para especificar um construtor
na especificação de membro de uma regra de manutenção é esta:
[<access_modifier>] <init>(parameter_types);
Por exemplo, a regra de preservação a seguir mantém um construtor View
personalizado que
usa um Context
e um AttributeSet
.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
Para manter todos os construtores públicos, use o exemplo a seguir como referência:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Campos
A sintaxe para especificar um campo na especificação de membro de uma regra de permanência é a seguinte:
[<access_modifier>...] [<type>] <field_name>;
Por exemplo, a regra de preservação a seguir mantém um campo de string particular chamado
userId
e um campo de número inteiro estático público chamado STATUS_ACTIVE
:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
É possível usar <fields>
como um atalho para corresponder a todos os campos em uma classe da seguinte maneira:
-keep class com.example.models.User {
<fields>;
}
Funções no nível do pacote
Para referenciar uma função Kotlin definida fora de uma classe (geralmente chamada de função de nível superior), use o nome Java gerado para a classe implicitamente adicionada pelo compilador Kotlin. O nome da classe é o nome do arquivo Kotlin com Kt
anexado. Por exemplo, se você tiver um arquivo Kotlin chamado
MyClass.kt
definido da seguinte maneira:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
Para escrever uma regra de permanência para a função isEmailValid
, a especificação de classe
precisa segmentar a classe gerada MyClassKt
:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Tipos
Esta seção descreve como especificar tipos de retorno, tipos de parâmetros e tipos de campos em especificações de membros de regras de permanência. Não se esqueça de usar os nomes Java gerados para especificar tipos se eles forem diferentes do código-fonte Kotlin.
Tipos primitivos
Para especificar um tipo primitivo, use a palavra-chave Java dele. O R8 reconhece os seguintes tipos primitivos: boolean
, byte
, short
, char
, int
, long
, float
, double
.
Confira um exemplo de regra com um tipo primitivo:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
Tipos genéricos
Durante a compilação, o compilador Kotlin/Java apaga as informações de tipo genérico. Portanto, ao escrever regras de manutenção que envolvem tipos genéricos, é necessário direcionar a representação compilada do código, e não o código-fonte original. Para saber mais sobre como os tipos genéricos são alterados, consulte Eliminação de tipos.
Por exemplo, se você tiver o seguinte código com um tipo genérico sem limites
definido em Box.kt
:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
Após a eliminação de tipo, o T
é substituído por Object
. Para manter o construtor e o método da classe, sua regra precisa usar java.lang.Object
em vez do T
genérico.
Um exemplo de regra de retenção seria:
# 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();
}
Se você tiver o seguinte código com um tipo genérico limitado em NumberBox.kt
:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
Nesse caso, o apagamento de tipo substitui T
pelo limite java.lang.Number
.
Um exemplo de regra de retenção seria:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
Ao usar tipos genéricos específicos do app como uma classe de base, é necessário incluir regras de manutenção também para as classes de base.
Por exemplo, para o seguinte código:
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) {
}
Você pode usar uma regra de preservação com includedescriptorclasses
para preservar a classe UnpackOptions
e o método de classe Box
com uma única regra da seguinte maneira:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
Para manter uma função específica que processa uma lista de objetos, escreva
uma regra que corresponda exatamente à assinatura da função. Como os tipos genéricos são apagados, um parâmetro como List<Product>
é visto como java.util.List
.
Por exemplo, se você tiver uma classe utilitária com uma função que processa uma lista de objetos Product
da seguinte maneira:
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)
Você pode usar a seguinte regra de preservação para proteger apenas a função processProducts
:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Tipos de matriz
Para especificar um tipo de matriz, adicione []
ao tipo de componente de cada dimensão da matriz. Isso se aplica a tipos de classe e primitivos.
- Matriz de classe unidimensional:
java.lang.String[]
- Matriz primitiva bidimensional:
int[][]
Por exemplo, se você tiver o seguinte código:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
Você pode usar a seguinte regra de retenção:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Caracteres curinga
A tabela a seguir mostra como usar caracteres curinga para aplicar regras de preservação a várias classes ou membros que correspondem a um determinado padrão.
Curinga | Aplica-se a turmas ou participantes | Descrição |
---|---|---|
** | Ambos | Mais usado. Corresponde a qualquer nome de tipo, incluindo qualquer número de separadores de pacote. Isso é útil para corresponder a todas as classes em um pacote e seus subpacotes. |
* | Ambos | Para especificações de classe, corresponde a qualquer parte de um nome de tipo que não contenha separadores de pacote (. ) . Para especificações de membro, corresponde a qualquer nome de método ou campo. Quando usado sozinho, ele também é um alias de ** . |
? | Ambos | Corresponde a qualquer caractere único em um nome de classe ou membro. |
*** | Membros | Corresponde a qualquer tipo, incluindo tipos primitivos (como int ), tipos de classe (como java.lang.String ) e tipos de matriz de qualquer dimensão (como byte[][] ). |
… | Membros | Corresponde a qualquer lista de parâmetros de um método. |
% | Membros | Corresponde a qualquer tipo primitivo, como "int", "float", "boolean" ou outros. |
Confira alguns exemplos de como usar os caracteres curinga especiais:
Se você tiver vários métodos com o mesmo nome que usam diferentes tipos primitivos como entradas, use
%
para escrever uma regra de permanência que os mantenha. Por exemplo, esta classeDataStore
tem vários métodossetValue
:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }
A regra de preservação a seguir mantém todos os métodos:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }
Se você tiver várias classes com nomes que variam em um caractere, use
?
para escrever uma regra de manutenção que as mantenha. Por exemplo, se você tiver as seguintes classes:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}
A regra de preservação a seguir mantém todas as classes:
-keep class com.example.models.UserV?
Para corresponder às classes
Example
eAnotherExample
(se elas fossem classes de nível raiz), mas nãocom.foo.Example
, use a seguinte regra de permanência:-keep class *Example
Se você usar * sozinho, ele vai funcionar como um alias para **. Por exemplo, as seguintes regras de retenção são equivalentes:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Inspecionar nomes Java gerados
Ao escrever regras de preservação, é necessário especificar classes e outros tipos de referência usando os nomes deles depois que são compilados em bytecode Java. Consulte Especificação de classe e Tipos para exemplos. Para verificar quais são os nomes Java gerados para seu código, use uma das seguintes ferramentas no Android Studio:
- APK Analyzer
- Com o arquivo de origem Kotlin aberto, inspecione o bytecode acessando Ferramentas > Kotlin > Mostrar bytecode Kotlin > Descompilar.