R8 cho phép bạn thêm các quy tắc ảnh hưởng đến việc tối ưu hoá ứng dụng của mình, ngoài các quy tắc giữ lại. Thêm các quy tắc này vào cùng một tệp proguard-rules.pro nơi bạn duy trì các quy tắc giữ lại.
Các quy tắc này thuộc các danh mục sau:
- Các giả định
-assumevalues-assumenosideeffects
- Các điểm tối ưu hoá khác
-convertchecknotnull-maximumremovedandroidloglevel
Các giả định
Các quy tắc này cho R8 biết rằng R8 có thể đưa ra các giả định cụ thể về hành vi của mã cụ thể trong thời gian chạy.
-assumevalues
Quy tắc -assumevalues cho R8 biết rằng giá trị của một trường hoặc giá trị trả về của một phương thức luôn là một hằng số cụ thể hoặc nằm trong một phạm vi được xác định tại thời gian chạy. -assumevalues dành cho những thứ như giá trị cờ mà tại thời gian xây dựng, được biết là có các giá trị cụ thể tại thời gian chạy.
Phân tích tĩnh tiêu chuẩn của R8 có thể không xác định được các giá trị thời gian chạy của thành viên. Với -assumevalues, bạn yêu cầu R8 giả định giá trị hoặc dải giá trị được chỉ định khi tối ưu hoá mã. Điều này cho phép R8 thực hiện các hoạt động tối ưu hoá linh hoạt hơn.
Cú pháp cho -assumevalues tương tự như cú pháp để giữ member_specification, nhưng ngoài ra còn có return clause như sau:
<member_specification> return <value> | <range>
Đối số <value> và <range> hỗ trợ các giá trị và loại sau:
- Giá trị đặc biệt:
true, false, null, @NonNull - Giá trị nguyên thuỷ:
int - Tham chiếu đến trường tĩnh (kể cả trường liệt kê)
Để xác định một dải ô, hãy sử dụng định dạng min..max bao gồm. Ví dụ: đoạn mã sau cho thấy biến CUSTOM_VAL chấp nhận giá trị từ 26 đến 2147483647:
-assumevalues public class com.example.Foo {
public static int CUSTOM_VAL return 26..2147483647;
}
Bạn có thể sử dụng quy tắc này trong các trường hợp sau:
- Đối với các thư viện: Để đảm bảo rằng khi các ứng dụng được tối ưu hoá, tất cả các lệnh gỡ lỗi cục bộ sẽ bị xoá khỏi mã thư viện công khai.
- Đối với ứng dụng: Để xoá những thứ như mã gỡ lỗi khỏi một ứng dụng phát hành. Bạn nên sử dụng các biến thể bản dựng và biến thể của các nhóm tài nguyên hoặc hằng số cụ thể, nhưng nếu các nhóm tài nguyên biến thể không hoạt động trong trường hợp của bạn hoặc nếu bạn cần đảm bảo chắc chắn rằng các đường dẫn mã đã được xoá hoàn toàn, hãy sử dụng
-assumevalues.
Ví dụ sau đây cho thấy một lớp mà R8 xoá các công cụ gỡ lỗi khỏi phiên bản được tối ưu hoá của một ứng dụng:
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();
}
}
Quy tắc sau đây cho biết cách thông báo cho R8 rằng biến IS_OPTIMIZED_VERSION luôn được đặt thành true.
-assumevalues class com.example.MyConfig {
public static final boolean IS_OPTIMIZED_VERSION return true;
}
-assumenosideeffects
Quy tắc -assumenosideeffects cho R8 biết rằng R8 có thể giả định rằng các thành viên được chỉ định không có tác dụng phụ. R8 có thể hoàn toàn xoá các lệnh gọi đến những phương thức không có giá trị trả về hoặc trả về một giá trị cố định.
Cú pháp cho -assumenosideeffects tương tự như cú pháp để giữ một member_specification.
Mẫu sau đây cho biết cách thông báo cho R8 rằng tất cả các phương thức public static có tên log trong lớp DebugLogger đều không có tác dụng phụ, cho phép R8 xoá các lệnh gọi đến những phương thức này.
-assumenosideeffects class com.example.DebugLogger {
public static void log(...);
}
Các điểm tối ưu hoá khác
Đây là một số chế độ tối ưu hoá nâng cao khác không được bật theo mặc định. Khi bật các tính năng này, bạn cho phép R8 tối ưu hoá mã theo hướng dẫn, ngoài các chế độ tối ưu hoá mặc định.
-convertchecknotnull
Bạn có thể sử dụng quy tắc -convertchecknotnull để tối ưu hoá các lệnh kiểm tra giá trị rỗng. Điều này áp dụng cho mọi phương thức lấy một tham số đối tượng và gửi nếu đối tượng đó có giá trị rỗng, tương tự như một câu khẳng định Kotlin tiêu chuẩn. Loại và thông báo ngoại lệ không nhất thiết phải giống nhau, nhưng hành vi gặp sự cố có điều kiện thì giống nhau.
Nếu một quy tắc -convertchecknotnull khớp với một phương thức nhất định, thì mỗi lệnh gọi đến phương thức đó sẽ được thay thế bằng một lệnh gọi đến getClass() trên đối số đầu tiên. Các lệnh gọi đến getClass() đóng vai trò là một lệnh kiểm tra giá trị rỗng thay thế và cho phép R8 xoá mọi đối số bổ sung của lệnh kiểm tra giá trị rỗng ban đầu, chẳng hạn như các hoạt động phân bổ chuỗi tốn kém.
Cú pháp cho -convertchecknotnull như sau:
-convertchecknotnull <class_specification> {
<member_specification>;
}
Ví dụ: nếu bạn có lớp Preconditions với phương thức checkNotNull như sau:
class Preconditions {
fun <T> checkNotNull(value: T?): T {
if (value == null) {
throw NullPointerException()
} else {
return value
}
}
}
Hãy sử dụng quy tắc sau:
-convertchecknotnull class com.example.package.Preconditions {
void checkNotNull(java.lang.Object);
}
Quy tắc này chuyển đổi tất cả các lệnh gọi đến checkNotNull() thành lệnh gọi đến getClass trên đối số đầu tiên. Trong ví dụ này, lệnh gọi đến checkNotNull(bar) được thay thế bằng bar.getClass(). Nếu bar là null, thì bar.getClass() sẽ tạo ra một NullPointerException, đạt được hiệu ứng kiểm tra giá trị rỗng tương tự nhưng hiệu quả hơn.
-maximumremovedandroidloglevel
Loại quy tắc này sẽ xoá các câu lệnh ghi nhật ký Android (chẳng hạn như Log.w(...) và Log.isLoggable(...)) ở hoặc dưới một cấp nhật ký nhất định.
Cú pháp cho maximumremovedandroidloglevel như sau:
-maximumremovedandroidloglevel <log_level> [<class_specification>]
Nếu bạn không cung cấp class_specification không bắt buộc, R8 sẽ áp dụng tính năng xoá nhật ký cho toàn bộ ứng dụng.
Sau đây là các cấp độ nhật ký:
Nhãn nhật ký |
Cấp độ nhật ký |
VERBOSE (Chi tiết) |
2 |
DEBUG (Gỡ lỗi) |
3 |
INFO (THÔNG TIN) |
4 |
CẢNH BÁO |
5 |
ERROR (LỖI) |
6 |
ASSERT |
7 |
Ví dụ: nếu bạn có mã sau:
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");
}
}
Với quy tắc sau:
# A level of 5 corresponds to a log level of WARNING.
-maximumremovedandroidloglevel 5 class Foo { void logSomething(); }
Mã được tối ưu hoá sẽ có dạng như sau:
class Foo {
private static final String TAG = "Foo";
void logSomething() {
Log.e(TAG, "Will be logged");
}
}
Nếu bạn cung cấp nhiều cấp độ nhật ký tối đa cho cùng một phương thức, thì R8 sẽ sử dụng cấp độ tối thiểu. Ví dụ: với các quy tắc sau:
-maximumremovedandroidloglevel 7 class ** { void foo(); }
-maximumremovedandroidloglevel 4 class ** { void foo(); }
thì cấp độ nhật ký bị xoá tối đa cho foo() là 4.