На высоком уровне правило сохранения определяет класс (или подкласс, или реализацию), а затем элементы — методы, конструкторы или поля — внутри этого класса, которые необходимо сохранить.
Общий синтаксис правила сохранения следующий, однако некоторые параметры сохранения не принимают необязательный keep_option_modfier
.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Ниже приведен пример правила сохранения, которое использует keepclassmembers
в качестве параметра сохранения, allowoptimization
в качестве модификатора и сохраняет someSpecificMethod()
из com.example.MyClass
:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Сохранить вариант
Параметр keep — это первая часть правила keep. Он определяет, какие аспекты класса необходимо сохранить. Существует шесть различных параметров keep: keep
, keepclassmembers
, keepclasseswithmembers
, keepnames
, keepclassmembernames
и keepclasseswithmembernames
.
В следующей таблице описываются эти параметры хранения:
Сохранить вариант | Описание |
---|---|
keepclassmembers | Сохраняет указанные члены только в том случае, если класс существует после оптимизации . |
keep | Сохраняет указанные классы и указанные члены (поля и методы), предотвращая их оптимизацию. Примечание : обычно keep следует использовать только с модификаторами параметров keep, поскольку сам по себе keep предотвращает выполнение какой-либо оптимизации в сопоставленных классах. |
keepclasseswithmembers | Сохраняет класс и его указанные члены только в том случае, если класс содержит все члены из спецификации класса. |
keepclassmembernames | Предотвращает переименование указанных членов класса, но не препятствует удалению класса или его членов. Примечание: значение этой опции часто понимается неправильно; рассмотрите возможность использования эквивалентной опции -keepclassmembers,allowshrinking . |
keepnames | Предотвращает переименование классов и их членов, но не препятствует их полному удалению, если они считаются неиспользуемыми. Примечание: значение этой опции часто понимается неправильно; рассмотрите возможность использования эквивалентной -keep,allowshrinking . |
keepclasseswithmembernames | Запрещает переименование классов и их указанных членов, но только если эти члены присутствуют в конечном коде. Не препятствует удалению кода. Примечание: значение этой опции часто понимается неправильно; рассмотрите возможность использования эквивалентной опции -keepclasseswithmembers,allowshrinking . |
Выберите правильный вариант хранения
Выбор правильного параметра Keep имеет решающее значение для оптимизации вашего приложения. Некоторые параметры Keep сокращают код, удаляя неиспользуемый код, в то время как другие обфусцируют или переименовывают код. В следующей таблице представлены действия различных параметров Keep:
Сохранить вариант | Классы сокращения | Запутывает классы | Уменьшает членов | Запутывает членов |
---|---|---|---|---|
keep | ||||
keepclassmembers | ||||
keepclasseswithmembers | ||||
keepnames | ||||
keepclassmembernames | ||||
keepclasseswithmembernames |
Сохранить модификатор опции
Модификатор параметра Keep используется для управления областью действия и поведением правила Keep. Вы можете добавить 0 или более модификаторов параметра Keep к правилу Keep.
Возможные значения модификатора опции сохранения описаны в следующей таблице:
Ценить | Описание |
---|---|
allowoptimization | Позволяет оптимизировать указанные элементы. При этом указанные элементы не переименовываются и не удаляются. |
allowobfucastion | Позволяет переименовывать указанные элементы. Однако элементы не могут быть удалены или оптимизированы каким-либо другим образом. |
allowshrinking | Позволяет удалить указанные элементы, если R8 не находит ссылок на них. При этом элементы не переименовываются и не оптимизируются каким-либо другим образом. |
includedescriptorclasses | Дает указание R8 сохранить все классы, которые появляются в дескрипторах методов (типы параметров и возвращаемые типы) и полей (типы полей), которые необходимо сохранить. |
allowaccessmodification | Позволяет R8 изменять (обычно расширять) модификаторы доступа ( public , private , protected ) классов, методов и полей в процессе оптимизации. |
allowrepackage | Позволяет R8 перемещать классы в разные пакеты, включая пакет по умолчанию (корневой). |
Спецификация класса
В правиле сохранения необходимо указать класс, суперкласс или реализованный интерфейс. Все классы, включая классы из пространства имён java.lang
, например, java.lang.String
, должны быть указаны с использованием их полного имени Java. Чтобы понять, какие имена следует использовать, проверьте байт-код с помощью инструментов, описанных в разделе Получение сгенерированных имён Java .
В следующем примере показано, как следует указывать класс MaterialButton
:
- Правильно:
com.google.android.material.button.MaterialButton
- Неверно:
MaterialButton
Спецификации классов также определяют, какие элементы класса следует сохранить. Следующее правило сохраняет класс MaterialButton
и все его элементы :
-keep class com.google.android.material.button.MaterialButton { *; }
Подклассы и реализации
Чтобы указать подкласс или класс, реализующий интерфейс, используйте extend
и implements
соответственно.
Например, если у вас есть класс Bar
с подклассом Foo
следующим образом:
class Foo : Bar()
Следующее правило сохранения сохраняет все подклассы Bar
. Обратите внимание, что само суперкласс Bar
оно не распространяется.
-keep class * extends Bar
Если у вас есть класс Foo
, который реализует Bar
:
class Foo : Bar
Следующее правило сохранения сохраняет все классы, реализующие Bar
. Обратите внимание, что само правило сохранения не распространяется на интерфейс Bar
.
-keep class * implements Bar
Модификатор доступа
Вы можете указать модификаторы доступа, такие как public
, private
, static
и final
, чтобы сделать правила хранения более точными.
Например, следующее правило сохраняет все public
классы в пакете api
и его подпакетах, а также все открытые и защищенные члены в этих классах.
-keep public class com.example.api.** { public protected *; }
Вы также можете использовать модификаторы для членов внутри класса. Например, следующее правило сохраняет только public static
методы класса Utils
:
-keep class com.example.Utils {
public static void *(...);
}
Модификаторы, специфичные для Kotlin
R8 не поддерживает специфичные для Kotlin модификаторы, такие как internal
и suspend
. Для сохранения таких полей следуйте следующим рекомендациям.
Чтобы сохранить
internal
класс, метод или поле, относитесь к нему как к открытому. Например, рассмотрим следующий исходный код на Kotlin:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
internal
классы, методы и поля являютсяpublic
в файлах.class
, созданных компилятором Kotlin, поэтому необходимо использовать ключевое словоpublic
, как показано в следующем примере:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
При компиляции
suspend
члена сопоставьте его скомпилированную сигнатуру с правилом сохранения.Например, если у вас есть функция
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>);
Например, следующее правило сохранения сохраняет открытый метод setLabel()
, который возвращает void и принимает String
.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Вы можете использовать <methods>
в качестве сокращения для сопоставления всех методов в классе следующим образом:
-keep class com.example.MyView {
<methods>;
}
Дополнительные сведения о том, как указать типы возвращаемых данных и типы параметров, см. в разделе Типы .
Конструкторы
Чтобы указать конструктор, используйте <init>
. Синтаксис указания конструктора в спецификации члена для правила сохранения следующий:
[<access_modifier>] <init>(parameter_types);
Например, следующее правило сохранения сохраняет пользовательский конструктор View
, который принимает Context
и AttributeSet
.
-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, определённую вне класса (обычно называемую функциями верхнего уровня), обязательно используйте сгенерированное Java-имя для класса, неявно добавленное компилятором Kotlin. Имя класса — это имя файла Kotlin с добавлением Kt
. Например, если у вас есть файл Kotlin с именем MyClass.kt
, определённый следующим образом:
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. Не забудьте использовать сгенерированные имена Java для указания типов, если они отличаются от исходного кода Kotlin.
Примитивные типы
Чтобы указать примитивный тип, используйте ключевое слово 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
. Чтобы сохранить конструктор и метод класса, в правиле необходимо использовать java.lang.Object
вместо универсального T
Пример правила сохранения будет следующим:
# 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);
}
При использовании в качестве базового класса универсальных типов, специфичных для приложения, необходимо также включить правила сохранения для базовых классов.
Например, для следующего кода:
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) {
}
Вы можете использовать правило сохранения с includedescriptorclasses
, чтобы сохранить как класс UnpackOptions
, так и метод класса Box
с помощью одного правила следующим образом:
-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)
Вы можете использовать следующее правило сохранения, чтобы защитить только функцию processProducts
:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Типы массивов
Укажите тип массива, добавив []
к типу компонента для каждого измерения массива. Это применимо как к типам классов, так и к примитивным типам.
- Одномерный массив классов:
java.lang.String[]
- Двумерный примитивный массив:
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();
}
джокеры
В следующей таблице показано, как использовать подстановочные знаки для применения правил сохранения к нескольким классам или членам, соответствующим определенному шаблону.
Wildcard | Применяется к классам или членам | Описание |
---|---|---|
** | Оба | Наиболее часто используется. Соответствует любому имени типа, включая любое количество разделителей пакетов. Это полезно для сопоставления всех классов в пакете и его подпакетах. |
* | Оба | Для спецификаций классов соответствует любой части имени типа, которая не содержит разделителей пакетов ( . )Для спецификаций членов соответствует любому имени метода или поля. При использовании отдельно также является псевдонимом для ** . |
? | Оба | Соответствует любому отдельному символу в имени класса или члена. |
*** | Участники | Соответствует любому типу, включая примитивные типы (например, 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 Studio:
- APK-анализатор
- Открыв исходный файл Kotlin, проверьте байт-код, выбрав Инструменты > Kotlin > Показать байт-код Kotlin > Декомпилировать .