Ở cấp độ cao, quy tắc giữ lại chỉ định một lớp (hoặc lớp con hoặc phương thức triển khai), sau đó là các thành phần (phương thức, hàm khởi tạo hoặc trường) trong lớp đó để giữ lại.
Cú pháp chung cho quy tắc giữ lại như sau, tuy nhiên một số lựa chọn giữ lại nhất định không chấp nhận keep_option_modfier
không bắt buộc.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
Sau đây là ví dụ về một quy tắc giữ lại sử dụng keepclassmembers
làm lựa chọn giữ lại, allowoptimization
làm đối tượng sửa đổi và giữ lại someSpecificMethod()
từ com.example.MyClass
:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Lựa chọn giữ lại
Lựa chọn giữ lại là phần đầu tiên trong quy tắc giữ lại. Nó chỉ định những khía cạnh của một lớp cần giữ lại. Có 6 lựa chọn giữ lại, cụ thể là keep
, keepclassmembers
, keepclasseswithmembers
, keepnames
, keepclassmembernames
, keepclasseswithmembernames
.
Bảng sau đây mô tả các lựa chọn giữ lại này:
Lựa chọn giữ lại | Nội dung mô tả |
---|---|
keepclassmembers |
Chỉ giữ lại các thành phần được chỉ định nếu lớp tồn tại sau khi tối ưu hoá. |
keep |
Giữ lại các lớp và thành viên (trường và phương thức) được chỉ định, ngăn không cho các lớp và thành viên đó được tối ưu hoá. Lưu ý: Thông thường, bạn chỉ nên sử dụng keep với các đối tượng sửa đổi tuỳ chọn giữ lại vì bản thân keep sẽ ngăn chặn mọi loại hoạt động tối ưu hoá diễn ra trên các lớp được so khớp. |
keepclasseswithmembers |
Chỉ giữ lại một lớp và các thành viên được chỉ định của lớp đó nếu lớp có tất cả các thành viên trong quy cách lớp. |
keepclassmembernames |
Ngăn việc đổi tên các thành viên lớp được chỉ định, nhưng không ngăn việc xoá lớp hoặc các thành viên của lớp. Lưu ý: Mọi người thường hiểu sai ý nghĩa của lựa chọn này; hãy cân nhắc sử dụng -keepclassmembers,allowshrinking tương đương. |
keepnames |
Ngăn việc đổi tên các lớp và thành viên, nhưng không ngăn việc xoá hoàn toàn các lớp và thành viên nếu chúng được coi là không còn được sử dụng. Lưu ý: Mọi người thường hiểu sai ý nghĩa của lựa chọn này; hãy cân nhắc sử dụng -keep,allowshrinking tương đương. |
keepclasseswithmembernames |
Ngăn việc đổi tên các lớp và thành viên được chỉ định của các lớp đó, nhưng chỉ khi các thành viên đó có trong mã cuối cùng. Tuỳ chọn này không ngăn việc xoá mã. Lưu ý: Ý nghĩa của tuỳ chọn này thường bị hiểu lầm; hãy cân nhắc sử dụng -keepclasseswithmembers,allowshrinking tương đương. |
Chọn chế độ giữ lại phù hợp
Việc chọn đúng tuỳ chọn giữ lại là rất quan trọng trong việc xác định chế độ tối ưu hoá phù hợp cho ứng dụng của bạn. Một số tuỳ chọn giữ lại sẽ rút gọn mã (một quy trình xoá mã không được tham chiếu), trong khi những tuỳ chọn khác sẽ làm rối mã hoặc đổi tên mã. Bảng sau đây cho biết các thao tác của nhiều lựa chọn giữ lại:
Lựa chọn giữ lại | Giảm kích thước các lớp | Làm rối mã nguồn các lớp | Shrinks members | Làm rối mã thành viên |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Giữ giá trị bổ sung của lựa chọn
Đối tượng sửa đổi tuỳ chọn giữ lại được dùng để kiểm soát phạm vi và hành vi của quy tắc giữ lại. Bạn có thể thêm 0 hoặc nhiều giá trị sửa đổi lựa chọn giữ lại vào quy tắc giữ lại.
Các giá trị có thể có cho một đối tượng sửa đổi tuỳ chọn giữ lại được mô tả trong bảng sau:
Giá trị | Mô tả |
---|---|
allowoptimization |
Cho phép tối ưu hoá các phần tử được chỉ định. Tuy nhiên, các phần tử được chỉ định sẽ không được đổi tên hoặc xoá. |
allowobfucastion |
Cho phép đổi tên các phần tử được chỉ định. Tuy nhiên, các phần tử này sẽ không bị xoá hoặc được tối ưu hoá theo cách khác. |
allowshrinking |
Cho phép xoá các phần tử đã chỉ định nếu R8 không tìm thấy các tham chiếu đến các phần tử đó. Tuy nhiên, các phần tử này không được đổi tên hoặc tối ưu hoá theo cách khác. |
includedescriptorclasses |
Hướng dẫn R8 giữ lại tất cả các lớp xuất hiện trong các bộ mô tả của phương thức (loại tham số và loại trả về) và các trường (loại trường) đang được giữ lại. |
allowaccessmodification |
Cho phép R8 thay đổi (thường là mở rộng) các đối tượng sửa đổi quyền truy cập (public , private , protected ) của các lớp, phương thức và trường trong quá trình tối ưu hoá. |
allowrepackage |
Cho phép R8 di chuyển các lớp vào các gói khác nhau, bao gồm cả gói mặc định (gói gốc). |
Quy cách của lớp học
Bạn phải chỉ định một lớp, siêu lớp hoặc giao diện đã triển khai trong quy tắc giữ lại. Tất cả các lớp, bao gồm cả các lớp trong không gian tên java.lang
như java.lang.String
, đều phải được chỉ định bằng tên Java đủ điều kiện. Để hiểu rõ những tên cần sử dụng, hãy kiểm tra mã byte bằng các công cụ được mô tả trong phần Lấy tên Java đã tạo.
Ví dụ sau đây cho biết cách bạn nên chỉ định lớp MaterialButton
:
- Đúng:
com.google.android.material.button.MaterialButton
- Không chính xác:
MaterialButton
Thông số kỹ thuật về lớp cũng chỉ định các thành phần trong một lớp cần được giữ lại. Quy tắc sau đây giữ lại lớp MaterialButton
và tất cả thành phần của lớp đó:
-keep class com.google.android.material.button.MaterialButton { *; }
Lớp con và cách triển khai
Để nhắm đến một lớp con hoặc lớp triển khai một giao diện, hãy sử dụng lần lượt extend
và implements
.
Ví dụ: nếu bạn có lớp Bar
với lớp con Foo
như sau:
class Foo : Bar()
Quy tắc giữ lại sau đây sẽ giữ lại tất cả các lớp con của Bar
. Xin lưu ý rằng quy tắc giữ lại không bao gồm chính siêu lớp Bar
.
-keep class * extends Bar
Nếu bạn có lớp Foo
triển khai Bar
:
class Foo : Bar
Quy tắc giữ lại sau đây sẽ giữ lại tất cả các lớp triển khai Bar
. Xin lưu ý rằng quy tắc giữ lại không bao gồm chính giao diện Bar
.
-keep class * implements Bar
Giá trị bổ sung về quyền truy cập
Bạn có thể chỉ định các đối tượng sửa đổi quyền truy cập như public
, private
, static
và final
để giúp các quy tắc giữ lại của bạn chính xác hơn.
Ví dụ: quy tắc sau đây sẽ giữ tất cả các lớp public
trong gói api
và các gói con của gói này, cũng như tất cả các thành phần công khai và được bảo vệ trong các lớp này.
-keep public class com.example.api.** { public protected *; }
Bạn cũng có thể dùng các hệ số sửa đổi cho các thành phần trong một lớp. Ví dụ: quy tắc sau đây chỉ giữ lại các phương thức public static
của lớp Utils
:
-keep class com.example.Utils {
public static void *(...);
}
Các hệ số sửa đổi dành riêng cho Kotlin
R8 không hỗ trợ các hệ số sửa đổi dành riêng cho Kotlin, chẳng hạn như internal
và suspend
.
Hãy tuân thủ các nguyên tắc sau để giữ lại những trường như vậy.
Để giữ lại một lớp, phương thức hoặc trường
internal
, hãy coi đó là công khai. Ví dụ: hãy xem xét nguồn Kotlin sau:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
Các lớp, phương thức và trường
internal
làpublic
trong các tệp.class
do trình biên dịch Kotlin tạo ra, vì vậy, bạn phải dùng từ khoápublic
như trong ví dụ sau:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
Khi một thành viên
suspend
được biên dịch, hãy so khớp chữ ký đã biên dịch của thành viên đó trong quy tắc giữ lại.Ví dụ: nếu bạn có hàm
fetchUser
được xác định như trong đoạn mã sau:suspend fun fetchUser(id: String): User
Khi được biên dịch, chữ ký của nó trong mã byte sẽ có dạng như sau:
public final Object fetchUser(String id, Continuation<? super User> continuation);
Để viết một quy tắc giữ lại cho hàm này, bạn phải so khớp chữ ký đã biên dịch này hoặc sử dụng
...
.Sau đây là ví dụ về cách sử dụng chữ ký đã biên dịch:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }
Sau đây là một ví dụ về cách sử dụng
...
:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Quy cách của thành viên
Thông số kỹ thuật của lớp có thể bao gồm các thành viên lớp cần được giữ lại. Nếu bạn chỉ định một hoặc nhiều thành viên cho một lớp học, thì quy tắc đó chỉ áp dụng cho những thành viên đó.
Ví dụ: để giữ lại một lớp cụ thể và tất cả các thành phần của lớp đó, hãy dùng như sau:
-keep class com.myapp.MyClass { *; }
Để chỉ giữ lại lớp mà không giữ lại các thành phần của lớp, hãy sử dụng nội dung sau:
-keep class com.myapp.MyClass
Hầu hết thời gian, bạn sẽ muốn chỉ định một số thành viên. Ví dụ: ví dụ sau đây giữ lại trường công khai text
và phương thức công khai updateText()
trong lớp MyClass
.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
Để giữ tất cả các trường công khai và phương thức công khai, hãy xem ví dụ sau:
-keep public class com.example.api.ApiClient {
public *;
}
Phương thức
Cú pháp để chỉ định một phương thức trong quy cách thành phần cho quy tắc giữ lại như sau:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
Ví dụ: quy tắc giữ sau đây giữ một phương thức công khai có tên là setLabel()
, trả về giá trị rỗng và nhận một String
.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
Bạn có thể sử dụng <methods>
làm lối tắt để so khớp tất cả các phương thức trong một lớp như sau:
-keep class com.example.MyView {
<methods>;
}
Để tìm hiểu thêm về cách chỉ định các loại cho loại dữ liệu trả về và loại tham số, hãy xem phần Các loại.
Hàm khởi tạo
Để chỉ định một hàm khởi tạo, hãy dùng <init>
. Cú pháp để chỉ định một hàm khởi tạo trong quy cách thành phần cho quy tắc giữ như sau:
[<access_modifier>] <init>(parameter_types);
Ví dụ: quy tắc giữ sau đây sẽ giữ lại một hàm khởi tạo View
tuỳ chỉnh nhận một Context
và một AttributeSet
.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
Để giữ lại tất cả hàm khởi tạo công khai, hãy tham khảo ví dụ sau:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Trường
Cú pháp để chỉ định một trường trong quy cách thành phần cho quy tắc giữ lại như sau:
[<access_modifier>...] [<type>] <field_name>;
Ví dụ: quy tắc giữ sau đây sẽ giữ lại một trường chuỗi riêng tư có tên là userId
và một trường số nguyên tĩnh công khai có tên là STATUS_ACTIVE
:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
Bạn có thể sử dụng <fields>
làm lối tắt để so khớp tất cả các trường trong một lớp như sau:
-keep class com.example.models.User {
<fields>;
}
Hàm cấp gói
Để tham chiếu một hàm Kotlin được xác định bên ngoài một lớp (thường được gọi là hàm cấp cao nhất), hãy nhớ sử dụng tên Java đã tạo cho lớp được trình biên dịch Kotlin thêm một cách ngầm ẩn. Tên lớp là tên tệp Kotlin có thêm Kt
. Ví dụ: nếu bạn có một tệp Kotlin tên là MyClass.kt
được xác định như sau:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
Để viết quy tắc giữ lại cho hàm isEmailValid
, bạn cần chỉ định lớp nhắm đến lớp MyClassKt
đã tạo:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Loại
Phần này mô tả cách chỉ định các loại dữ liệu trả về, loại tham số và loại trường trong thông số thành phần của quy tắc giữ lại. Hãy nhớ sử dụng tên Java đã tạo để chỉ định các loại nếu chúng khác với mã nguồn Kotlin.
Kiểu nguyên bản
Để chỉ định một kiểu nguyên thuỷ, hãy dùng từ khoá Java của kiểu đó. R8 nhận dạng các kiểu nguyên thuỷ sau: boolean
, byte
, short
, char
, int
, long
, float
, double
.
Sau đây là một ví dụ về quy tắc có kiểu nguyên thuỷ:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
Kiểu chung
Trong quá trình biên dịch, trình biên dịch Kotlin/Java sẽ xoá thông tin về kiểu chung, vì vậy, khi viết các quy tắc giữ lại liên quan đến các kiểu chung, bạn phải nhắm đến bản trình bày đã biên dịch của mã, chứ không phải mã nguồn ban đầu. Để tìm hiểu thêm về cách các kiểu chung được thay đổi, hãy xem phần Xoá kiểu.
Ví dụ: nếu bạn có mã sau đây với một loại chung không giới hạn được xác định trong Box.kt
:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
Sau khi xoá kiểu, T
sẽ được thay thế bằng Object
. Để giữ lại hàm dựng và phương thức của lớp, quy tắc của bạn phải sử dụng java.lang.Object
thay cho T
chung.
Sau đây là ví dụ về quy tắc giữ lại:
# 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();
}
Nếu bạn có mã sau đây với một loại chung bị ràng buộc trong NumberBox.kt
:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
Trong trường hợp này, thao tác xoá kiểu sẽ thay thế T
bằng giới hạn của nó, java.lang.Number
.
Sau đây là ví dụ về quy tắc giữ lại:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
Khi sử dụng các loại chung dành riêng cho ứng dụng làm lớp cơ sở, bạn cũng cần phải thêm các quy tắc giữ lại cho các lớp cơ sở.
Ví dụ: đối với mã sau:
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) {
}
Bạn có thể sử dụng quy tắc giữ lại với includedescriptorclasses
để giữ lại cả lớp UnpackOptions
và phương thức lớp Box
bằng một quy tắc duy nhất như sau:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
Để giữ một hàm cụ thể xử lý danh sách các đối tượng, bạn cần viết một quy tắc khớp chính xác với chữ ký của hàm. Xin lưu ý rằng vì các kiểu chung bị xoá, nên một tham số như List<Product>
sẽ được xem là java.util.List
.
Ví dụ: nếu bạn có một lớp tiện ích có hàm xử lý danh sách các đối tượng Product
như sau:
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)
Bạn có thể sử dụng quy tắc giữ lại sau đây để chỉ bảo vệ hàm processProducts
:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Các loại mảng
Chỉ định một loại mảng bằng cách thêm []
vào loại thành phần cho từng phương diện của mảng. Điều này áp dụng cho cả loại lớp và loại cơ bản.
- Mảng lớp một chiều:
java.lang.String[]
- Mảng nguyên thuỷ hai chiều:
int[][]
Ví dụ: nếu bạn có mã sau:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
Bạn có thể sử dụng quy tắc giữ lại sau đây:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Ký tự đại diện
Bảng sau đây cho thấy cách sử dụng ký tự đại diện để áp dụng các quy tắc giữ lại cho nhiều lớp hoặc thành viên khớp với một mẫu nhất định.
Ký tự đại diện | Áp dụng cho lớp học hoặc thành viên | Nội dung mô tả |
---|---|---|
** | Cả hai | Thường được sử dụng nhất. Khớp với mọi tên loại, bao gồm cả số lượng bất kỳ của dấu phân cách gói. Điều này hữu ích khi so khớp tất cả các lớp trong một gói và các gói con của gói đó. |
* | Cả hai | Đối với quy cách lớp, hãy so khớp mọi phần của tên loại không chứa dấu phân cách gói (. ) Đối với quy cách thành viên, hãy so khớp mọi tên phương thức hoặc trường. Khi được dùng riêng, đây cũng là một bí danh cho ** . |
? | Cả hai | Khớp với một ký tự bất kỳ trong tên lớp hoặc tên thành viên. |
*** | Hội viên | Khớp với mọi loại, bao gồm cả các loại nguyên thuỷ (như int ), các loại lớp (như java.lang.String ) và các loại mảng thuộc mọi phương diện (như byte[][] ). |
... | Hội viên | Khớp với mọi danh sách tham số cho một phương thức. |
% | Hội viên | Khớp với mọi kiểu dữ liệu gốc (chẳng hạn như "int", "float", "boolean" hoặc các kiểu dữ liệu khác). |
Sau đây là một số ví dụ về cách sử dụng các ký tự đại diện đặc biệt:
Nếu có nhiều phương thức có cùng tên và nhận các kiểu nguyên thuỷ khác nhau làm đầu vào, bạn có thể dùng
%
để viết một quy tắc giữ lại nhằm giữ lại tất cả các phương thức đó. Ví dụ: lớpDataStore
này có nhiều phương thứcsetValue
:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }
Quy tắc lưu giữ sau đây sẽ lưu giữ tất cả các phương thức:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }
Nếu bạn có nhiều lớp có tên khác nhau một ký tự, hãy dùng
?
để viết một quy tắc giữ lại nhằm giữ lại tất cả các lớp đó. Ví dụ: nếu bạn có các lớp sau:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}
Quy tắc giữ sau đây sẽ giữ lại tất cả các lớp:
-keep class com.example.models.UserV?
Để so khớp các lớp
Example
vàAnotherExample
(nếu chúng là các lớp cấp gốc), nhưng không phảicom.foo.Example
, hãy sử dụng quy tắc giữ lại sau:-keep class *Example
Nếu bạn chỉ dùng *, thì nó sẽ đóng vai trò là một bí danh cho **. Ví dụ: các quy tắc giữ sau đây là tương đương:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Kiểm tra tên Java đã tạo
Khi viết các quy tắc giữ lại, bạn phải chỉ định các lớp và các loại tham chiếu khác bằng tên của chúng sau khi được biên dịch thành mã byte Java (xem Quy cách lớp và Các loại để biết ví dụ). Để kiểm tra tên Java đã tạo cho mã của bạn, hãy sử dụng một trong các công cụ sau trong Android Studio:
- Công cụ phân tích APK
- Khi tệp nguồn Kotlin đang mở, hãy kiểm tra mã byte bằng cách chuyển đến Tools > Kotlin > Show Kotlin Bytecode > Decompile (Công cụ > Kotlin > Hiện mã byte Kotlin > Giải mã).