Hệ thống xây dựng nền tảng Android (Soong) chạy trình biên dịch R8 trên các mục tiêu biên dịch mã byte DEX, bao gồm cả ứng dụng Android (android_app), kiểm thử (android_test) và thư viện Java có thể cài đặt bằng compile_dex: true (chẳng hạn như services.jar), để rút gọn, tối ưu hoá và loại bỏ mã cũng như tài nguyên không dùng đến. Trên
thư viện tĩnh (android_library và java_library tĩnh), Soong không
chạy trực tiếp R8 mà sử dụng khối thuộc tính optimize để đính kèm và truyền
quy tắc giữ lại của người dùng (export_proguard_flags_files: true) đến các mục tiêu DEX
xuôi dòng liên kết tĩnh với các quy tắc đó.
Đối với các kỹ sư nền tảng xây dựng hình ảnh hệ thống hoặc gói của nhà cung cấp, việc loại bỏ các quy tắc giữ lại quá rộng sẽ mang lại lợi ích trực tiếp cho tình trạng hệ thống:
- Dấu vết phân vùng hệ thống nhỏ hơn: R8 xoá các lớp, phương thức và tài nguyên không hoạt động trước khi đóng gói APK và JAR vào
/system,/system_ext,/productvà/vendor. - Cấu phần phần mềm được biên dịch nhỏ hơn: Ít phương thức DEX hơn đồng nghĩa với việc các tệp
.odexvà.vdexdodex2oattạo ra trong thời gian tạo bản dựng hoặc biên dịch trên thiết bị sẽ nhỏ hơn. - Giảm áp lực bộ nhớ trong thời gian chạy: Vì Android liên kết các bảng tài nguyên
.odex,.vdexvà APK vào bộ nhớ quy trình bằng cách sử dụng lệnh gọimmaptheo trang theo yêu cầu, nên các tệp nhị phân nhỏ hơn sẽ giảm lỗi trang chính trong quá trình khởi động ứng dụng, đồng thời giảm mã thường trú và bộ nhớ tài nguyên trên các quy trình. Để biết thêm thông tin về mức độ ảnh hưởng của mã ứng dụng đã biên dịch đối với bộ nhớ thiết bị, hãy xem bài viết Mã ứng dụng là bộ nhớ. - Tối ưu hoá toàn bộ chương trình hiệu quả hơn: Các quy tắc ràng buộc hẹp cho phép R8 đưa ra các phương thức cùng dòng, huỷ ảo hoá giao diện và lệnh gọi ảo, loại bỏ các trường không dùng đến và truyền hằng số giữa các lớp.
Định cấu hình tính năng tối ưu hoá R8 trong Soong
Trong tệp Android.bp, hãy định cấu hình tính năng tối ưu hoá R8 bằng cách sử dụng khối thuộc tính optimize trên mục tiêu android_app và java_library có thể cài đặt (hoặc trên mô-đun android_library và java_library tĩnh để xuất quy tắc giữ lại của người dùng):
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
Bảng sau đây tóm tắt các thuộc tính optimize phổ biến nhất trong Soong (được xác định trong build/soong/java/dex.go):
| Thuộc tính | Mô tả |
|---|---|
enabled |
Kiểm soát xem R8 có chạy trên mục tiêu hay không. Mặc định là true cho tất cả mục tiêu android_app.
|
shrink |
Kiểm soát việc loại bỏ mã thừa để xoá các lớp, trường và phương thức không thể truy cập. Mặc định là true cho các mục tiêu android_app (false cho các mô-đun kiểm thử và java_library độc lập, truyền -dontshrink trừ phi được đặt rõ ràng).
|
optimize |
Kiểm soát các hoạt động tối ưu hoá mã byte, chẳng hạn như chèn cùng dòng phương thức, hợp nhất lớp, truyền hằng số và xoá nhánh chết. Mặc định là true cho các mục tiêu android_app (do cờ bản phát hành RELEASE_R8_OPTIMIZE_BY_DEFAULT kiểm soát).
|
obfuscate |
Kiểm soát việc rút gọn và đổi tên giá trị nhận dạng. Giá trị mặc định là false để đảm bảo khả năng tương thích với phiên bản cũ, nhưng hãy đặt thành true bất cứ khi nào có thể để giảm kích thước DEX và mở khoá các tính năng tối ưu hoá sâu hơn (xem phần Bật tính năng làm rối mã nguồn bất cứ khi nào có thể).
|
shrink_resources |
Xoá các tài nguyên không dùng đến (mục nhập res/) khỏi tệp APK đóng gói sau khi rút gọn mã. Giá trị mặc định là false.
|
optimized_shrink_resources |
Chạy quy trình rút gọn tài nguyên và mã R8 tích hợp để R8 theo dõi
mã và tham chiếu tài nguyên cùng nhau trong một lượt. Khi
shrink_resources: true được đặt, giá trị mặc định là
RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT
(true trong các bản dựng nền tảng tiêu chuẩn).
|
proguard_flags_files |
Liệt kê các tệp .flags hoặc .pro dành riêng cho mô-đun chứa các quy tắc giữ lại tuỳ chỉnh. Tránh thêm tệp tuỳ chỉnh khi chú thích chuẩn hoặc chú thích mặc định là đủ.
|
export_proguard_flags_files |
Truyền proguard_flags_files của thư viện này đến
các mô-đun hạ nguồn phụ thuộc vào thư viện này theo cách tĩnh.
|
trace_references_from |
Liệt kê các mục tiêu phụ thuộc biên dịch DEX trực tiếp (được kết nối thông qua instrumentation_for hoặc libs) mà các tham chiếu mã byte của chúng vào mục tiêu này được R8 tự động theo dõi và giữ lại (cũng được truyền qua các biến thể override_android_app và override_android_test, đồng thời được hỗ trợ cho các thư viện Java đồng hành không phụ thuộc khi được ghép nối với một phần tách .impl).
|
allow_traced_references |
Cho phép các mục tiêu phụ thuộc biên dịch DEX trực tiếp (được kết nối thông qua instrumentation_for hoặc libs, chẳng hạn như các kiểm thử giữa các kho lưu trữ hoặc kiểm thử của nhà cung cấp không thể liệt kê trong trace_references_from) chọn tham gia theo dõi các tham chiếu mã byte của chúng vào mục tiêu này bằng cách đặt trace_references: true.
|
trace_references |
Khi được đặt thành true trên một phần phụ thuộc biên dịch DEX trực tiếp của một
mục tiêu có allow_traced_references: true, hãy chạy R8
tracereferences trên mô-đun này và truyền các quy tắc
giữ lại đã tạo đến mô-đun mục tiêu.
|
protect_api_surface |
Truyền --protect-api-surface đến R8. Khi được đặt trên một
java_sdk_library, thuộc tính này cũng tự động tạo và
đính kèm các quy tắc giữ lại API Metalava trên tất cả phạm vi API đã bật (công khai,
hệ thống, module-lib, system-server và kiểm thử) vào thư viện triển khai.
|
Thư viện tĩnh (android_library, java_library và tệp nhập AAR) cũng có thể đặt required_downstream_optimization: { enabled: true, optimize: true } trong Android.bp để yêu cầu mọi mục tiêu biên dịch DEX hạ nguồn liên kết tĩnh với chúng (trực tiếp hoặc bắc cầu) bật tính năng tối ưu hoá mã byte và rút gọn R8. Soong tự động bật tính năng này trên androidx.compose.runtime_runtime để các mục tiêu liên kết với Jetpack Compose được tối ưu hoá bằng R8.
Bật tính năng làm rối mã nguồn bất cứ khi nào có thể
Trong Soong, obfuscate mặc định là false để tương thích với phiên bản trước, nhưng bạn nên đặt rõ ràng obfuscate: true trên android_app và các mục tiêu DEX độc lập bất cứ khi nào có thể:
- DEX nhỏ hơn và dấu vết
.vdex: Việc đổi tên các gói, lớp, trường và phương thức thành giá trị nhận dạng ngắn (a,b) sẽ thu nhỏ nhóm chuỗi DEX (string_idsvàstring_data_item) và các chỉ số mô tả loại. Vì Android liên kết các tệp.vdexvà.odexvào bộ nhớ quy trình, nên các bảng ký hiệu nhỏ hơn sẽ trực tiếp giảm cả kích thước phân vùng hệ thống và mức sử dụng bộ nhớ trong thời gian chạy. - Tối ưu hoá toàn bộ chương trình sâu hơn: Việc cho phép R8 đổi tên giá trị nhận dạng sẽ giúp hợp nhất lớp, làm phẳng gói và loại bỏ trùng lặp lớp tổng hợp trên các gói mà R8 phải bỏ qua để tránh xung đột tên.
- Thay thế bằng biểu tượng dấu vết ngăn xếp đầy đủ: Việc bật tính năng làm rối mã nguồn trong Soong không làm giảm khả năng gỡ lỗi. Đối với mọi mục tiêu được biên dịch bằng R8, Soong sẽ phát ra một tệp ánh xạ
proguard_dictionarytrong thư mục trung gian của mô-đun, gói tất cả từ điển mô-đun vào cấu phần phần mềmproguard-dict.zipcủa bản dựng, nhúng hàm băm ánh xạ (--map-id-template) vào tiêu đề DEX và viết lại các thuộc tínhSourceFilecủa lớp (--source-file-template) đểretracecó thể tự động thay thế dấu vết ngăn xếp bằng biểu tượng.
Chỉ giữ lại obfuscate: false (hoặc giữ lại rõ ràng tên API công khai) cho:
- Thư viện dùng chung trên bootclasspath hoặc
system_serverclasspath: Các mô-đun (java_libraryhoặcjava_sdk_library) hiển thị một nền tảng API bên ngoài được liên kết linh động bởi các mô-đun khác trong thời gian chạy (chẳng hạn như mục tiêuframework.jar,services.jarhoặc<uses-library>) phải giữobfuscate: falsehoặc giữ nguyên nền tảng API của chúng một cách rõ ràng bằng cách sử dụng các quy tắcprotect_api_surface: true,@KeepForApihoặc-keepđể phương thức gọi được biên dịch dựa trên mã giả lập của chúng có thể phân giải tên lớp và tên thành viên trong thời gian chạy. Trên các mô-đunjava_sdk_library, việc đặtprotect_api_surface: truesẽ tự động tạo và đính kèm các quy tắc giữ lại Metalava ProGuard trên tất cả phạm vi API đã bật (công khai, hệ thống, thư viện mô-đun, máy chủ hệ thống và kiểm thử) vào thư viện triển khai. Khi tính năng tối ưu hoá R8 tổng quát được bật trên các mục tiêu bootclasspath không thể cập nhật (framework-minus-apex,extvàframework-location), các mục tiêu này cũng bao gồmframework-hiddenapi-proguard-rules(dohiddenapi_to_proguardtạo) để giữ lại các thành viên có trong tệpboot/hiddenapi/*.txtđang hoạt động (bỏ quahiddenapi-max-target-o.txt).
Trước khi bật obfuscate: true trên một ứng dụng, hãy xác minh các điều kiện tiên quyết sau đây để di chuyển:
- Phần phụ thuộc APK kiểm thử bên ngoài: Nếu một tệp APK
android_testbên ngoài đặtinstrumentation_for: "MyApp"và trực tiếp gọi các lớp hoặc phương thức nội bộ củaMyApp, thì việc đổi tên các ký hiệu đó sẽ gây raNoSuchMethodErrorhoặcNoClassDefFoundErrortại thời điểm chạy kiểm thử. Định cấu hìnhtrace_references_from(hoặcallow_traced_referencesbằngtrace_references: true), cấu trúc kiểm thử dưới dạng một tệp APK tự đo lường liên kết tĩnhMyApp.implhoặc chú thích các trình nối kiểm thử bằng@VisibleForTesting(xem Bước 2: Di chuyển các quy tắc giữ lại dựa trên kiểm thử) để các ký hiệu nội bộ mà kiểm thử cần được giữ nguyên trong khi làm rối mã nguồn phần còn lại của ứng dụng. - Phản chiếu dựa trên chuỗi và JNI: Nếu một mô-đun tra cứu các lớp, phương thức hoặc trường theo tên chuỗi cố định (
Class.forName,getDeclaredMethodhoặc JNIFindClassvàGetMethodID) mà không có quy tắc giữ lại hoặc chú thích, thì việc làm rối mã nguồn sẽ đổi tên các mục tiêu đó và làm hỏng các lượt tra cứu trong thời gian chạy. (Lưu ý rằng phản chiếu không có chú thích cũng không thành công trongshrink: truevàoptimize: true.) Chú thích các điểm truy cập đó bằng Hướng dẫn giữ lại chú thích (@UsesReflection,@UsedByReflectionhoặc@UsedByNative) để R8 giữ nguyên tên của các điểm truy cập đó và làm rối mã nguồn phần còn lại của mô-đun.
Tuân theo nguyên tắc không có cờ tuỳ chỉnh
Mô-đun nền tảng Android lý tưởng không có tệp proguard.flags hoặc keep.xml tuỳ chỉnh. Trong bản dựng nền tảng Android, phần lớn các quy tắc giữ lại tuỳ chỉnh đều thừa:
- Đường cơ sở của nền tảng và AAPT2: Hầu hết các điểm truy cập đều được giữ lại tự động theo đường cơ sở của nền tảng chung (chẳng hạn như
@Keep, phương thức JNInativevà@VisibleForTesting) hoặc do AAPT2 tạo từAndroidManifest.xmlvà tài nguyên bố cục (xem phần Tìm hiểu các quy tắc lưu giữ mặc định của nền tảng). - Phương án thay thế có mục tiêu: Khi phải giữ lại các điểm truy cập, hãy ưu tiên chú thích trang web khai báo (
keepanno,@VisibleForTesting), quy tắc thư viện đã xuất (export_proguard_flags_files: true) hoặc liên kết kiểm thử tĩnh qua các tệp.flagstách rời (xem phần Kiểm tra và di chuyển các quy tắc giữ hiện có).
Trước khi thêm hoặc giữ lại tệp proguard.flags hoặc keep.xml tuỳ chỉnh, hãy xác minh xem quy tắc đã được xử lý theo các giá trị cơ sở mặc định hay chưa hoặc có thể di chuyển sang chú thích mã hay không.
Tìm hiểu các quy tắc giữ lại mặc định của nền tảng
Soong tự động chuyển các quy tắc cơ sở chung sau đây đến mọi lệnh gọi R8 trên các mục tiêu biên dịch DEX (mô-đun android_app, android_test và java_library có thể cài đặt), được định cấu hình trong build/soong/java/dex.go:
build/make/core/proguard.flags: Giữ nguyên các lớp và thành viên được chú thích bằng@com.android.internal.annotations.VisibleForTestingtrên tất cả các gói và bằng@VisibleForTesting(androidx.annotation.VisibleForTestinghoặccom.google.common.annotations.VisibleForTesting) trong các góiandroid.**,com.android.**vàcom.google.android.**. Cũng giữ lại@TestApi,@Keep(androidx.annotation,android.support.annotation,com.android.internal.annotations),@KeepForWeakReference,@WeaklyReferencedCallbackvà@dalvik.annotation.optimization.**.build/make/core/proguard_basic_keeps.flags: Giữ nguyên các thuộc tínhSourceFilecho dấu vết ngăn xếp, chú thích chế độ hiển thị thời gian chạy (RuntimeVisible*Annotations), thuộc tínhExceptionsvàAnnotationDefault, phương thứcnative, thành viênSerializable, phương thức@JavascriptInterface, hàm khởi tạoThrowable(String), trườngParcelable$CREATORvà trường protobufMessageLite.build/make/core/proguard/kotlin.flags: Tắt các cảnh báo vô hại cho chú thích meta Kotlin cụ thể (kotlin.Metadatavàkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) và xoá chú thích KotlinDebugMetadatatrong bản phát hành.build/make/core/proguard/checknotnull.flags: Thay thế các lệnh gọi trình trợ giúp kiểm tra giá trị rỗng phổ biến (com.google.common.base.Preconditions.checkNotNullvàdagger.internal.Preconditions.checkNotNull*) bằng các lệnh kiểm tra giá trị rỗng ngắn gọn của mã byte. Tệp này cố ý bỏ quaObjects.requireNonNullđể giữ nguyên thông báo ngoại lệ rõ ràng trên ranh giới API khung.build/make/core/proguard/enumvalues.flags: Giữ lại các phương thứcvaluesvàvalueOftrên các loạienum, trừ phi bị tắt theo cấu hình bản dựng.- Quy tắc được tạo tự động của AAPT2: AAPT2 kiểm tra
AndroidManifest.xmlđã hợp nhất, XML bố cục và tệp XML lựa chọn ưu tiên để tạo quy tắc giữ chính xác cho mọiActivity,Service,BroadcastReceiver,ContentProvider,BackupAgent,Application, lớp conViewtuỳ chỉnh, lớp conPreferencevà phương thứcandroid:onClickXML đã đăng ký.
Kiểm tra và di chuyển các quy tắc lưu giữ hiện có
Khi kiểm tra các tệp proguard.flags hoặc keep.xml hiện có trong kho lưu trữ nền tảng, hãy đánh giá từng quy tắc theo thứ tự dựa trên hệ phân cấp 4 bước sau:
Bước 1: Xoá các quy tắc thừa hoặc lỗi thời
Xoá các quy tắc đã được đề cập trong các quy tắc cơ sở chung, quy tắc bố cục và tệp kê khai AAPT2 hoặc giá trị mặc định của R8, cũng như các quy tắc tham chiếu đến các lớp hoặc gói không còn tồn tại.
Nếu tất cả quy tắc trong tệp .flags đều thừa, hãy xoá tệp đó và xoá proguard_flags_files khỏi Android.bp. Nếu khối optimize còn lại chỉ
nêu lại các chế độ cài đặt mặc định, hãy xoá khối thừa và định dạng tệp bản dựng
bằng bpfmt -w Android.bp. Khi dọn dẹp một gói, hãy kiểm tra xem các mô-đun đồng hành trong thư mục con (chẳng hạn như các biến thể mục tiêu Kotlin) có tham chiếu đến cùng một tệp cờ hay không và cập nhật chúng cùng nhau.
Bước 2: Di chuyển các quy tắc giữ lại dựa trên kiểm thử
Di chuyển các quy tắc giữ lại dựa trên kiểm thử ra khỏi tệp .flags tuỳ chỉnh của bản phát hành công khai bằng một trong các mẫu sau:
Theo dõi tệp đối chiếu từ mục tiêu kiểm thử hoặc mục tiêu đồng hành (
trace_references_fromhoặcallow_traced_references): Khi cả mục tiêu và mô-đun phụ thuộc trực tiếp đều biên dịch DEX riêng (android_app,android_testhoặcjava_libraryđã được dex) và được kết nối thông quainstrumentation_forhoặclibs, Soong có thể chạy R8tracereferencestrên mô-đun phụ thuộc và đưa các quy tắc giữ lại đã tạo vào quá trình biên dịch R8 của mục tiêu mà không cần tạo chu kỳ phụ thuộc hoặc yêu cầu phân tách thư viện.impltheo cách thủ công:Kiểm thử cùng kho lưu trữ (
trace_references_from): Liệt kê các phần phụ thuộc trực tiếpinstrumentation_forhoặclibstrên mô-đun mục tiêu (điều này cũng tự động áp dụng trên các biến thểoverride_android_appvàoverride_android_test, đồng thời tiếp tục hỗ trợ các thư viện Java đồng hành không phụ thuộc khi được ghép nối với phần phân tách.impl):android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { obfuscate: true, shrink_resources: true, trace_references_from: ["MyAppTests"], }, } android_test { name: "MyAppTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], }Kiểm thử trên nhiều kho lưu trữ hoặc kiểm thử của nhà cung cấp (
allow_traced_referencesvàtrace_references): Khi mô-đun mục tiêu không thể liệt kê trực tiếp phần phụ thuộc trongtrace_references_from(chẳng hạn như kiểm thử trong một kho lưu trữ hoặc cây nhà cung cấp khác), hãy đặtallow_traced_references: truetrên mô-đun mục tiêu và chọn sử dụng từ mỗi phần phụ thuộc bằngtrace_references: true:android_app { name: "MyApp", srcs: ["src/**/*.java"], optimize: { allow_traced_references: true, }, } android_test { name: "MyAppVendorTests", instrumentation_for: "MyApp", srcs: ["tests/**/*.java"], optimize: { trace_references: true, }, }Phản chiếu dựa trên chuỗi trong kiểm thử:
tracereferenceskiểm tra các tham chiếu nhóm hằng số mã byte và không thể phát hiện phản chiếu dựa trên chuỗi trong kiểm thử (Class.forName,getDeclaredField,getDeclaredMethodhoặc trình trợ giúp chèn trường phản chiếu). Chú thích các trường và phương thức phát hành công khai được truy cập theo cách phản chiếu bằng@VisibleForTesting(hoặc@UsedByReflection).
Liên kết thư viện triển khai vào các chương trình kiểm thử (
static_libs): Nếu bạn muốn giữ lại không có điểm truy cập chỉ kiểm thử trong APK phát hành công khai và không muốn các thay đổi kiểm thử kích hoạt lại R8 trên mục tiêu phát hành công khai, hãy đặt các tệp nguồn của ứng dụng vào mộtandroid_library(MyApp.impl) và liên kết tĩnhMyApp.implvào cảMyAppvàMyAppTests:android_library { name: "MyApp.impl", srcs: ["src/**/*.java"], manifest: "AndroidManifest.xml", } android_app { name: "MyApp", static_libs: ["MyApp.impl"], optimize: { obfuscate: true, shrink_resources: true, }, } android_test { name: "MyAppTests", srcs: ["tests/**/*.java"], static_libs: ["MyApp.impl"], }Mẫu 3 mô-đun này cho phép
MyAppTestschạy dưới dạng kiểm thử đo lường có quyền truy cập đầy đủ vào các lớp nội bộ, đồng thời cho phépMyAppthu nhỏ và làm rối mã nguồn hoàn toàn dựa trên các điểm truy cập phát hành công khai.Chú thích các hook kiểm thử bằng
@VisibleForTesting: Nếu một tệp APK kiểm thử bên ngoài chỉ gọi một vài phương thức nội bộ hoặc hàm khởi tạo trên một mục tiêuandroid_app, thì bạn có thể chú thích các nội dung khai báo đó bằng@VisibleForTesting. Đường cơ sở chungbuild/make/core/proguard.flagstự động giữ lại@VisibleForTestingmục trong các góiandroid.**,com.android.**vàcom.google.android.**mà không cần tệp.flagstuỳ chỉnh. (Đối với các mô-đun của nhà cung cấp bên ngoài các không gian tên này, hãy sử dụngtrace_references_from,@UsedByReflectionhoặc quy tắc thư viện đã xuất.) Khi chú thích thủ công bằng@VisibleForTestingthay vì sử dụngtrace_references_fromhoặctrace_references, hãy chú thích cả các lớp được truyền đến Mockitomock(...)hoặcspy(...)(không chỉ các phương thức giả lập), vìmockito-target(dexmaker-mockito) trên thiết bị tạo ra các lớp con trong thời gian chạy và R8 có thể đánh dấu các lớp không có lớp con làfinal(MockitoException: Can't mock/spy class ... - final class).Tránh hoặc đóng gói lại các thư viện tĩnh trùng lặp (
jarjar_rules): Khi cả tệp APK kiểm thửandroid_appvàinstrumentation_forđều liên kết tĩnh với cùng một thư viện (trực tiếp, chẳng hạn nhưguavahoặc bắc cầu),tracereferencessẽ thấy các lớp đó được xác định trong quá trình kiểm thử và không phát ra quy tắc giữ lại nào cho ứng dụng. Đồng thời, trong thời gian chạy, trình tải lớp mẹ của ứng dụng sẽ che khuất bản sao của quá trình kiểm thử bằng các lớp đã được R8 thu nhỏ, gây raIllegalAccessErrorhoặcNoSuchMethodError. Nếu kiểm thử không cần bản sao đi kèm, hãy xoá bản sao khỏistatic_libscủa kiểm thử (hoặc liên kết bản sao đó thông qualibs) để tránh xung đột; nếu không, hãy đóng gói lại các gói thư viện tĩnh trùng lặp của ứng dụng theo tiền tố dành riêng cho mô-đun bằng cách sử dụngjarjar_rules.
Bước 3: Xuất quy tắc từ thư viện sở hữu
Nếu java_library hoặc android_library dùng chung yêu cầu quy tắc giữ lại cho phản chiếu nội bộ hoặc lệnh gọi lại JNI, hãy xác định quy tắc trên mục tiêu thư viện và đặt export_proguard_flags_files: true:
java_library {
name: "my-shared-library",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
export_proguard_flags_files: true,
},
}
Tất cả mục tiêu android_app và thư viện hạ nguồn liên kết tĩnh với my-shared-library sẽ tự động kế thừa các quy tắc đó, vì vậy, các ứng dụng hạ nguồn không cần phải sao chép các quy tắc đó. Khi nhiều mô-đun trên các thư mục khác nhau phải chia sẻ một nhóm quy tắc không liên kết với một thư viện mã duy nhất, hãy xuất bản tệp .flags thông qua filegroup rõ ràng trong Android.bp để các mô-đun có thể tham chiếu :my-shared-flags một cách rõ ràng trên các ranh giới gói. Để biết hướng dẫn chung về cách tạo quy tắc giữ lại của bên sử dụng thư viện mà không hạn chế việc tối ưu hoá ứng dụng sau này, hãy xem phần Tối ưu hoá cho tác giả thư viện.
Bước 4: Chú thích nội dung khai báo trong mã nguồn
Khi một lớp, phương thức, hàm khởi tạo hoặc trường được truy cập thông qua phản chiếu hoặc JNI và không được AAPT2 hoặc các quy tắc cơ sở chung bao gồm, hãy thay thế các quy tắc -keep tách rời trong tệp .flags bằng chú thích nguồn trong Hướng dẫn giữ chú thích (xem tài liệu tham khảo Javadoc keepanno):
@UsesReflection: Ưu tiên chú thích này khi bạn kiểm soát mã thực hiện phản chiếu. Hãy đặt chú thích này tại vị trí gọi phản chiếu để khai báo các lớp, phương thức hoặc trường mục tiêu được truy cập linh động. Vì@UsesReflectiontự động mã hoá một điều kiện tiên quyết rằng chính trang web gọi được chú thích có thể truy cập được, nên R8 sẽ loại bỏ cả phương thức gọi và mục tiêu phản chiếu nếu trang web gọi không được sử dụng.@UsedByReflection: Đặt trên các lớp, phương thức, trường hoặc hàm khởi tạo được tạo thực thể hoặc được gọi phản chiếu bằng mã hoặc thư viện bên ngoài, chẳng hạn như các lớp được tải bằngClass.forNametừ khoáBundlehoặcSettings, hoặc các lớp trình bổ trợ được tải trên trình tải lớp động. Chỉ địnhkind(chẳng hạn nhưKeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) và các quy tắc ràng buộc về tham số để R8 chỉ giữ lại hợp đồng phản chiếu chính xác và vẫn có thể tối ưu hoá hoặc loại bỏ các thành viên không dùng đến của lớp. Đừng thêm@UsedByReflectionvào các thành phần đã đăng ký trong tệp kê khai nhưJobServicehoặcBroadcastReceiver, vì AAPT2 đã giữ lại các thành phần này.@UsedByNative: Đặt trên các phương thức hoặc trường được truy cập từ mã JNI C hoặc C++ bằngGetMethodID,GetStaticMethodIDhoặcGetFieldID.@KeepForApi: Đặt trên các lớp hoặc thành phần API thư viện phải được giữ nguyên khi thư viện bị thu nhỏ trước khi phân phối.@Keep(androidx.annotation.Keep): Sử dụng làm phương án dự phòng khi không áp dụng đượckeepanno. Việc áp dụng@Keepcho một lớp sẽ giữ lại lớp đó và tất cả các thành phần của lớp mà không cần điều kiện. Việc áp dụng@Keepcho một phương thức hoặc trường đóng vai trò là điểm truy cập vô điều kiện, giữ lại thành phần và lớp chứa thành phần đó ngay cả khi lớp đó không bao giờ được tạo thực thể, trong khikeepannothể hiện khả năng truy cập có điều kiện.
Cách sử dụng chú thích keepanno của R8 trong mô-đun Soong:
Thêm
"keepanno-annotations"vàolibstrongAndroid.bp:libs: [ "keepanno-annotations", ],Nhập
com.android.tools.r8.keepanno.annotations.*và chú thích nội dung khai báo hoặc trang web gọi trong mã nguồn Java hoặc Kotlin:import com.android.tools.r8.keepanno.annotations.KeepItemKind; import com.android.tools.r8.keepanno.annotations.UsedByReflection; public final class CustomPluginController { @UsedByReflection( description = "Instantiated via Class.forName from plugin config", kind = KeepItemKind.CLASS_AND_METHODS) public CustomPluginController(Context context) { // ... } }R8 dịch trực tiếp các chú giải
keepannovào mô hình quy tắc giữ lại nội bộ và loại bỏ các chú giải khỏi kết quả DEX cuối cùng, không làm tăng thêm chi phí mã byte thời gian chạy.
Tránh các lỗi thường gặp
Các phần sau đây mô tả những lỗi thường gặp về proguard.flags và keep.xml trong mã nền tảng cũng như cách khắc phục.
Quy tắc giữ lại thành phần rộng
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- Lý do ảnh hưởng đến hiệu suất: Quy tắc này hoàn toàn thừa thãi với AAPT2.
Vì sử dụng ký tự đại diện mà không kiểm tra xem thành phần có được đăng ký trong
AndroidManifest.xmlđã hợp nhất cuối cùng hay không, nên quy tắc này buộc R8 phải giữ lại mọi lớp conActivity,ServicehoặcBroadcastReceiverđược tìm thấy ở bất kỳ đâu trên classpath, bao gồm cả các thành phần thư viện không dùng đến và hoạt động gỡ lỗi bị tắt. - Cách khắc phục nên dùng: Xoá quy tắc. AAPT2 kiểm tra tệp kê khai sáp nhập và tạo ra các quy tắc
-keepchính xác cho các thành phần đã đăng ký.
Quy tắc giữ lại trình xử lý lượt nhấp XML rộng
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Lý do ảnh hưởng đến hiệu suất: Việc giữ lại mọi phương thức
public void *(View)trên mọi lớp trong mô-đun sẽ ngăn R8 loại bỏ hoặc chèn bất kỳ phương thức nào chấp nhận một tham sốView. - Cách khắc phục nên dùng: Xoá quy tắc. AAPT2 quét các tệp XML bố cục và tạo quy tắc giữ lại được nhắm mục tiêu cho các phương thức được tham chiếu bằng thuộc tính
android:onClick.
Quy tắc giữ lại phương thức getter và setter của Broad View
# Don't do this:
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
public *** get*();
}
- Lý do ảnh hưởng đến hiệu suất: Quy tắc này buộc R8 phải giữ lại mọi phương thức getter và setter trên mọi lớp con
Viewtrong ứng dụng và tất cả thư viện được liên kết (bao gồm cả thư viện AndroidX và Material), chặn việc xoá phương thức không hoạt động và nội tuyến trên mã giao diện người dùng. - Cách khắc phục nên dùng: Xoá quy tắc. AAPT2 đã giữ lại các hàm khởi tạo cho các lớp
Viewtuỳ chỉnh được tăng cường từ các tệp XML bố cục. Nếu mã tạo ảnh động cho một thuộc tính thành phần hiển thị bằng cách sử dụng tên chuỗi phản chiếu nhưObjectAnimator.ofFloat(view, "translationZ", ...), hãy thay thế tên chuỗi bằng một tệp tham chiếu thuộc tính đã nhập (View.TRANSLATION_Zhoặc cách triển khaiFloatPropertyhoặcIntPropertytuỳ chỉnh) để tránh hoàn toàn việc phản chiếu. Nếu không thể tránh được quyền truy cập thuộc tính phản chiếu, hãy chú thích phương thức getter hoặc setter cụ thể bằng@UsedByReflection.
Tắt tính năng tối ưu hoá hoặc làm rối mã nguồn toàn diện
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Lý do ảnh hưởng đến hiệu suất: Việc đặt
-dontoptimizehoặc-dontshrinkbên trong tệp.flagssẽ tự động ghi đè chế độ cài đặtAndroid.bpcủa mô-đun và tắt các lượt tối ưu hoá trên toàn bộ mục tiêu. Tệ hơn nữa, nếu một thư viện xuất tệp.flagschứa-dontoptimize, thì thư viện đó sẽ tắt tính năng tối ưu hoá R8 cho mọiandroid_apphạ nguồn liên kết với thư viện đó. - Cách khắc phục nên dùng: Xoá
-dontoptimize,-dontshrinkvà-dontobfuscatekhỏi.flagstệp. Kiểm soát hành vi tối ưu hoá một cách rõ ràng trongAndroid.bpbằng cách sử dụng khốioptimize(shrink,optimize,obfuscate) trên mục tiêu lá.
Cờ chẩn đoán trong các quy tắc đã cam kết
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- Lý do ảnh hưởng đến bản dựng: Cờ chẩn đoán làm tràn nhật ký bản dựng hoặc cố gắng ghi tệp đầu ra vào đường dẫn cục bộ trong các bản dựng Soong trong hộp cát.
- Cách khắc phục nên dùng: Xoá các cờ này khỏi tệp
.flagsđã cam kết. Soong tự động ghi kết quả sử dụng và ánh xạ R8 (chẳng hạn nhưproguard_dictionaryvàproguard_usage.zip) vào thư mục trung gian của mô-đun trongout/soong/.intermediates/.
Quy tắc giữ lại trong bản phát hành chính thức cho mã kiểm thử
- Lý do ảnh hưởng đến hiệu suất: Việc thêm các quy tắc
-keeptuỳ chỉnh chỉ để truy cập vào kiểm thử sẽ buộc mã đó không bị làm rối mã nguồn và được giữ lại trong các bản dựng chính thức. Mặc dù việc định cấu hìnhtrace_references_from(hoặcallow_traced_referencesbằngtrace_references: true) hoặc chú thích các phương thức bằng@VisibleForTestinggiúp tránh việc duy trì các quy tắc-keeptheo cách thủ công và chỉ giữ lại những gì mà các kiểm thử tham chiếu, nhưng các ký hiệu đó vẫn được đưa vào tệp nhị phân chính thức. - Cách khắc phục đề xuất: Loại bỏ các quy tắc
-keepkiểm thử tuỳ chỉnh bằng cách bậttrace_references_from(hoặcallow_traced_referencesvàtrace_references: true), hoặc cấu trúc ứng dụng thành thư viện.implđược liên kết với một tệp APK kiểm thử đo lường tự động bằng cách sử dụngstatic_libsđể giữ các điểm truy cập chỉ dành cho kiểm thử hoàn toàn nằm ngoài tệp APK phát hành công khai, như mô tả trong Bước 2: Di chuyển các quy tắc giữ lại dựa trên kiểm thử.
Ký tự đại diện tài nguyên chung chung trong keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- Lý do ảnh hưởng đến hiệu suất: Ký tự đại diện chung giữ lại mọi tài nguyên thuộc loại phù hợp trên mô-đun và các phần phụ thuộc của mô-đun, làm mất tác dụng của tính năng thu gọn tài nguyên (
shrink_resources: true) và làm tăng kích thước tệp APK cũng như bảngresources.arscđược liên kết. - Cách khắc phục nên dùng:
- Ưu tiên tham chiếu tài nguyên tĩnh hơn
keep.xml: Tránh sử dụng phương thứcResources.getIdentifierđể tra cứu một tập hợp tài nguyên có giới hạn một cách linh động, chẳng hạn như chuỗi thử nghiệm được đánh số hoặc đối tượng có thể vẽ theo chủ đề. Thay vào đó, hãy sử dụng câu lệnhswitchthời gian biên dịch hoặc ánh xạ qua các hằng số tĩnhR.id,R.stringhoặcR.drawable. Tham chiếu tĩnh cho phép R8 và AAPT2 theo dõi chính xác các tài nguyên đang hoạt động, loại bỏ hao tổn khi tra cứu chuỗi trong thời gian chạy và loại bỏ hoàn toàn nhu cầu vềkeep.xml. - Liệt kê tên tài nguyên cụ thể: Nếu cần tra cứu động, chẳng hạn như bằng trình đọc giấy phép của bên thứ ba, hãy liệt kê giá trị nhận dạng tài nguyên chính xác trong
tools:keep(ví dụ:tools:keep="@raw/third_party_licenses"). - Xoá các tệp
keep.xmlthừa: Nếu các tài nguyên được liệt kê đã được tham chiếu tĩnh trong mã (R.raw.foo) hoặc XML (@raw/foo), thì trình rút gọn sẽ tự động giữ lại các tài nguyên đó, vì vậy, bạn có thể xoáres/raw/keep.xml.
- Ưu tiên tham chiếu tài nguyên tĩnh hơn
Xác minh và kiểm tra các thay đổi về quy tắc
Bất cứ khi nào bạn xoá hoặc thu hẹp quy tắc giữ lại trong proguard.flags hoặc keep.xml, hãy xác minh rằng mô-đun được tạo sạch, vượt qua các kiểm thử đơn vị và kiểm thử đo lường, đồng thời giữ lại tất cả các điểm truy cập bắt buộc.
Xây dựng và kiểm thử mô-đun
Xây dựng mô-đun một cách rõ ràng để xác minh rằng R8 và AAPT2 hoàn tất mà không có cảnh báo tham chiếu bị thiếu:
m <MODULE_NAME>Chạy kiểm thử đơn vị và kiểm thử đo lường cho mô-đun bằng cách sử dụng
atest:atest <TEST_MODULE_NAME>Đối với ứng dụng hệ thống, dịch vụ đặc quyền hoặc mô-đun phụ thuộc vào phần cứng, hãy chạy kiểm thử đo lường và kiểm thử giao diện người dùng trên thiết bị thực mục tiêu hoặc trong phòng thí nghiệm kiểm thử thiết bị để thực hiện các đường dẫn phản chiếu thời gian chạy, liên kết IPC và tăng cường tài nguyên.
Kiểm tra DEX và sự khác biệt về tài nguyên
So sánh tệp APK hoặc JAR đã biên dịch trước và sau khi thay đổi quy tắc để xác nhận rằng R8 xoá mã và tài nguyên không dùng đến mà không xoá các điểm truy cập dự kiến:
Sử dụng
apkanalyzerhoặcdexdumpđể kiểm tra các lớp, phương thức và trường được giữ lại trong tệp APK hoặc DEX đầu ra:apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkKhi sửa đổi
keep.xmlhoặc bậtshrink_resources, hãy sử dụngaapt2 dump resourcesđể xác minh rằng bản dựng loại bỏ các tài nguyên không dùng đến và giữ lại các tài nguyên cần thiết:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkKhi chạy
prebuilts/r8/r8.jartrực tiếp trênclasses.jartrước R8 của một mô-đun để xác minh các thay đổi về cờ khi không có mạng mà không cần bản dựng nền tảng đầy đủ, hãy truyền cả tệp JAR classpath--libcủa mô-đun (android.jar,framework.jarvàlibsdùng chung) và các quy tắcproguard.optionsdo AAPT2 tạo từAndroidManifest.xmlvà XML bố cục. Nếu không có những dữ liệu đầu vào đó, R8 sẽ không thể phân giải các siêu kiểu khung (Activity,Service,Phone,Handler) và giữ lại các phương thức phân cấp.
Phân tích bán kính giữ lại và quy tắc bao hàm bằng R8
Trình biên dịch R8 nguồn mở bao gồm một trình phân tích Keep Radius (cũng được hiển thị trong Android Studio và Gradle dưới dạng Trình phân tích cấu hình R8) để đo lường tác động chính xác của mọi quy tắc giữ lại trong quá trình biên dịch. Soong tích hợp trực tiếp trình phân tích này vào bản dựng nền tảng Android (được định cấu hình trong build/soong/java/dex.go).
Cách phân tích quy tắc giữ lại cho một mô-đun nền tảng hoặc trên toàn bộ bản dựng:
Chạy bản dựng bằng biến môi trường
R8_DUMP_KEEP_RADIUS=true:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>Khi bạn đặt
R8_DUMP_KEEP_RADIUS=true, Soong sẽ hướng dẫn R8 ghi lại các chỉ số quy tắc giữ lại trong tệpr8keepradius.pbtrung gian cho mỗi mô-đun được biên dịch trongout/soong/.intermediates/. Đối với mỗi quy tắc giữ lại và chú thíchkeepanno, R8 sẽ ghi lại:- Phạm vi giữ lại ngay lập tức: Các lớp, trường và phương thức chính xác được quy tắc giữ lại, cùng với các quy tắc ràng buộc cụ thể mà quy tắc đó thực thi đối với việc thu hẹp, tối ưu hoá hoặc làm rối mã nguồn.
- Quy tắc bao hàm: Những quy tắc hoặc chú thích giữ lại khác đã giữ lại cùng một mục. Nếu một quy tắc tuỳ chỉnh hoàn toàn được bao gồm trong quy tắc AAPT2 hoặc quy tắc cơ sở chung, thì bạn có thể xoá quy tắc đó một cách an toàn.
- Quy tắc chung và quy tắc trên toàn gói: Quy tắc sử dụng ký tự đại diện rộng trên toàn gói hoặc áp dụng lệnh cấu hình chung.
Chuyển đổi kết quả đầu ra
r8keepradius.pbthành báo cáo HTML tương tác bằng cách sử dụngKeepRadiusHtmlReportGenerator(được đóng gói trongprebuilts/r8/r8.jar):# Generate an HTML report for a single module INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME> java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ $INTERMEDIATES/android_common/r8keepradius.pb \ keep_radius_report.html # Scan all built modules under out/soong/.intermediates and generate # per-module HTML reports plus an aggregate keepradius.html summary java -cp prebuilts/r8/r8.jar \ com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \ out/soong/.intermediates \ out/keep_radius_reportsKhi được cung cấp một thư mục,
KeepRadiusHtmlReportGeneratorsẽ duyệt qua tất cả tệp*keepradius*.pb, tạo báo cáo HTML cho từng mô-đun và tạoout/keep_radius_reports/keepradius.htmltóm tắt số lượng mục đang hoạt động, số lượng mục được giữ lại và các quy tắc giữ lại có bán kính cao nhất trong bản dựng. Để biết thêm thông tin về cách diễn giải điểm rút gọn, tối ưu hoá và làm rối mã nguồn trong báo cáo được tạo, hãy xem bài viết Sử dụng Trình phân tích cấu hình R8.
Soong cũng hỗ trợ thêm các biến môi trường vòng lặp bên trong và chẩn đoán R8 trong build/soong/java/dex.go:
R8_DUMP_INPUT=true: Ghir8inputs.zipvào thư mục trung gian của mô-đun chứa tất cả tệp JAR đầu vào, tệp JAR thư viện và cấu hình ProGuard đã hợp nhất để tái tạo R8 độc lập.R8_DUMP_PERFETTO_TRACE=true: Ghir8trace.ptracevào thư mục trung gian của mô-đun để kiểm tra các lượt biên dịch R8 trong Perfetto.SOONG_SKIP_TRACE_REFERENCES=true: Bỏ qua việc chạy các quy tắctracereferencesdotrace_references_fromhoặctrace_referencesđịnh cấu hình để tăng tốc các bản dựng vòng lặp bên trong cục bộ khi lặp lại trên mã kiểm thử.
Xác thực quy tắc người dùng thư viện
Khi java_library hoặc android_library xuất các quy tắc giữ lại cho bên sử dụng thư viện (export_proguard_flags_files: true), các quy tắc đó không được bao gồm cờ chung làm thay đổi hoặc tắt tính năng tối ưu hoá cho ứng dụng sử dụng thư viện.
R8 cung cấp một trình phân tích quy tắc giữ lại nguồn mở (ProcessKeepRules), được hiển thị trong bản dựng nền tảng Android dưới dạng công cụ lưu trữ process-keep-rules (được xác định trong prebuilts/r8/Android.bp):
# Build the R8 keep rules validator host binary
m process-keep-rules
# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>
Công cụ process-keep-rules phân tích cú pháp từng tệp cấu hình và không thành công với thông tin chẩn đoán về tệp và dòng nếu gặp phải các lệnh không được phép trong quy tắc của người dùng thư viện. Các lệnh không được phép bao gồm lệnh tắt tính năng tối ưu hoá, thu hẹp hoặc làm rối mã nguồn trên toàn cục (chẳng hạn như -dontoptimize hoặc -dontshrink), cờ đóng gói lại gói và sửa đổi quyền truy cập, cờ chẩn đoán hoặc ánh xạ và -keepattributes ở cấp ứng dụng.
Kiểm tra cây nguồn bằng pgaudit.py
Để kiểm tra các thư mục nguồn chưa được tạo cùng với trình phân tích thời gian tạo của R8, cây nền tảng Android sẽ bao gồm tập lệnh pgaudit.py trong build/make/core/proguard/tools/. Công cụ phân tích tĩnh này quét các tệp Android.bp, proguard.flags và keep.xml trên một kho lưu trữ để gắn cờ các quy tắc đã được AAPT2 hoặc các quy tắc cơ sở của nền tảng chung, ký tự đại diện rộng, cơ chế ghi đè -dontoptimize và quy tắc giữ lại chỉ dành cho kiểm thử:
# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/
# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json
Các nhóm nền tảng và OEM có thể kết hợp pgaudit.py để phân loại nhanh cây nguồn,
process-keep-rules để xác thực các quy tắc thư viện đã xuất và
R8_DUMP_KEEP_RADIUS=true với KeepRadiusHtmlReportGenerator để đo lường
bán kính giữ lại chính xác của lớp, trường và phương thức của mọi quy tắc trong một bản dựng.