Hệ thống tạo 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), các bài kiểm thử (android_test) và các 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 các thư viện tĩnh (android_library và java_library tĩnh), Soong không chạy R8 trực tiếp mà sử dụng khối thuộc tính optimize để đính kèm và truyền các quy tắc giữ lại người dùng (export_proguard_flags_files: true) đến các mục tiêu DEX hạ lưu liên kết tĩnh chúng.
Đố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 nhà cung cấp, việc loại bỏ các quy tắc giữ lại quá rộng 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 dùng đến 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 có kích thước nhỏ hơn: Ít phương thức DEX hơn có nghĩa là các tệp
.odexvà.vdexdodex2oattạo ra có kích thước nhỏ hơn trong thời gian tạo hoặc biên dịch trên thiết bị. - Giảm áp lực bộ nhớ trong thời gian chạy: Vì Android ánh xạ
.odex,.vdexvà các bảng tài nguyên APK vào bộ nhớ quy trình bằng cách sử dụng các lệnh gọimmapđược phân trang theo yêu cầu, các tệp nhị phân nhỏ hơn sẽ giảm các lỗi trang chính trong quá trình khởi động ứng dụng và giảm bộ nhớ mã và tài nguyên thường trú trên các quy trình. Để biết thêm thông tin về cách mã ứng dụng đã biên dịch ảnh hưởng đến 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 ràng buộc giữ lại hẹp cho phép R8 nội tuyến các phương thức, huỷ ảo hoá giao diện và các lệnh gọi ảo, cắt tỉa các trường không dùng đến và truyền hằng số trên các lớp.
Định cấu hình chế độ tối ưu hoá R8 trong Soong
Trong các tệp Android.bp, hãy định cấu hình chế độ tối ưu hoá R8 bằng cách sử dụng khối thuộc tính optimize trên android_app và các mục tiêu java_library có thể cài đặt (hoặc trên android_library và các mô-đun java_library tĩnh để xuất các 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ả các mục tiêu android_app.
|
shrink |
Kiểm soát việc loại bỏ mã không dùng đến để 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 không hoạt động. 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. Mặc định là false để tương thích với các phiên bản trướ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 chế độ 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 (các mục res/) khỏi APK được đó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 mã và tài nguyên R8 tích hợp để R8 theo dõi đồng thời các tham chiếu mã và tài nguyên trong một lần truyền. Khi bạn đặt shrink_resources: true, giá trị mặc định sẽ 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 có chứa các quy tắc tuỳ chỉnh để giữ lại. Tránh thêm các tệp tuỳ chỉnh khi chú thích hoặc giá trị mặc định tiêu chuẩn là đủ.
|
export_proguard_flags_files |
Truyền proguard_flags_files của thư viện này đến các mô-đun hạ lưu phụ thuộc vào thư viện này một cách tĩnh.
|
trace_references_from |
Liệt kê các mục tiêu đồng hành của thư viện Java mà mã byte tham chiếu vào mục tiêu này. R8 sẽ tự động theo dõi và giữ lại. |
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 các phiên bản trước, nhưng bạn nên đặt obfuscate: true một cách rõ ràng trên android_app và các mục tiêu DEX độc lập bất cứ khi nào có thể:
- Dấu vết
.vdexvà DEX nhỏ hơn: Việc đổi tên các gói, lớp, trường và phương thức thành các giá trị nhận dạng ngắn (a,b) sẽ thu hẹp nhóm chuỗi DEX (string_idsvàstring_data_item) cũng như các giá trị mô tả kiểu. Vì Android ánh xạ các tệp.vdexvà.odexvào bộ nhớ xử lý, 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ớ thời gian chạy. - Tối ưu hoá toàn bộ chương trình sâu hơn: Cho phép R8 đổi tên các giá trị nhận dạng sẽ mở khoá tính năng hợp nhất lớp, làm phẳng gói và loại bỏ các lớp trùng lặ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 toàn bộ dấu vết ngăn xếp: 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 R8 biên dịch, Soong sẽ phát ra một tệp ánh xạ
proguard_dictionarytrong thư mục trung gian của mô-đun, kết hợp tất cả cá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 một hàm băm ánh xạ (--map-id-template) vào tiêu đề DEX và ghi lại các thuộc tính lớpSourceFile(--source-file-template) đểretracecó thể tự động biểu thị các dấu vết ngăn xếp.
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 đường dẫn lớp
system_server: Các mô-đun (java_libraryhoặcjava_sdk_library) hiển thị một bề mặt API bên ngoài được các mô-đun khác liên kết động trong thời gian chạy (chẳng hạn như các mục tiêuframework.jar,services.jarhoặc<uses-library>) phải giữobfuscate: falsehoặc giữ lại rõ ràng bề mặt API của chúng bằng các quy tắc@KeepForApihoặc-keep(cùng vớiprotect_api_surface: truecho các mục tiêu bootclasspath) để những đối tượng gọi được biên dịch dựa trên các phần giữ chỗ 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ướ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:
- Các 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 biểu tượng đó sẽ gây raNoSuchMethodErrorhoặcNoClassDefFoundErrortại thời gian chạy kiểm thử. Bạn nên cấu trúc bài kiểm thử dưới dạng một APK tự đo lường liên kếtMyApp.implmột cách tĩnh, chú thích các lệnh gọi kiểm thử bằng@VisibleForTestinghoặc định cấu hìnhtrace_references_from(xem Bước 2: Di chuyển các quy tắc giữ dựa trên kiểm thử) để các biểu tượng nội bộ cần thiết cho các bài kiểm thử được giữ lại trong khi làm rối 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 chữ (
Class.forName,getDeclaredMethodhoặc JNIFindClassvàGetMethodID) mà không có quy tắc hoặc chú giải giữ lại, thì quá trình làm rối mã nguồn sẽ đổi tên các mục tiêu đó và làm gián đoạn quá trình tra cứu thời gian chạy. (Xin lưu ý rằng hoạt động phản chiếu không được chú thích cũng sẽ không thành công trongshrink: truevàoptimize: true.) Chú thích những điểm truy cập đó bằng Hướng dẫn về cách giữ lại chú thích (@UsesReflection,@UsedByReflectionhoặc@UsedByNative) để R8 giữ lại 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 thủ 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 dư 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 tự động giữ lại theo đường cơ sở của nền tảng chung (chẳng hạn như
@Keep, các 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). - Các lựa chọn thay thế được nhắm đến: Trong trường hợp phải giữ lại các điểm truy cập, hãy ưu tiên chú giải tại vị trí khai báo (
keepanno,@VisibleForTesting), các 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.flagsriêng biệt (xem phần Kiểm tra và di chuyển các quy tắc giữ lại 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ý bằng các đường cơ sở mặc định hay có thể di chuyển sang chú thích mã hay không.
Tìm hiểu các quy tắc mặc định về việc giữ lại dữ liệu trên nền tảng
Soong tự động truyền các quy tắc cơ sở toàn cầu sau đây đến mọi lệnh gọi R8 trên các mục tiêu biên dịch DEX (android_app, android_test và các mô-đun 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ữ lại 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, đồng thời giữ lại@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ữ lại các thuộc tínhSourceFilecho dấu vết ngăn xếp, chú thích hiển thị thời gian chạy (RuntimeVisible*Annotations), các thuộc tínhExceptionsvàAnnotationDefault, các phương thứcnative, các thành viênSerializable, các phương thức@JavascriptInterface, các hàm khởi tạoThrowable(String), các trườngParcelable$CREATORvà các 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 cụ thể của Kotlin (kotlin.Metadatavàkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) và loại bỏ chú thíchDebugMetadatacủa Kotlin trong các bản dựng phát hành.build/make/core/proguard/checknotnull.flags: Thay thế các lệnh gọi 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 mã byte ngắn gọn. Tệp này cố ý bỏ quaObjects.requireNonNullđể giữ lại các thông báo ngoại lệ rõ ràng trên các ranh giới API của 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ị vô hiệu hoá theo cấu hình bản dựng.- Các quy tắc do AAPT2 tự động tạo: AAPT2 kiểm tra các tệp XML
AndroidManifest.xml, bố cục XML và tệp XML ưu tiên đã hợp nhất để tạo các 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 giữ lại 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ệ thống phân cấp gồm 4 bước sau:
Bước 1: Xoá các quy tắc dư thừa hoặc lỗi thời
Xoá các quy tắc đã được bao gồm trong các đường cơ sở toàn cầu, các quy tắc về bố cục và tệp kê khai AAPT2 hoặc cá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ả các quy tắc trong tệp .flags đều dư 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 dư 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 đi kèm 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ữ dựa trên kiểm thử ra khỏi các tệp .flags tuỳ chỉnh của phiên bản phát hành công khai bằng một trong các mẫu sau:
Liên kết thư viện triển khai vào các kiểm thử (
static_libs): Khiandroid_testthực hiện các chi tiết triển khai nội bộ hoặc riêng tư theo gói của một ứng dụng, cấu trúc nền tảng được đề xuất là đặt các tệp nguồn ứng dụng trongandroid_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 một thử nghiệm tự đo lường với quyền truy cập đầy đủ vào các lớp nội bộ, cho phépMyAppbậtobfuscate: truemột cách tự do mà không làm hỏng các thử nghiệm, tránh vận chuyển các điểm truy cập chỉ dành cho thử nghiệm trong APK phát hành công khai và loại bỏ việc tạo lạiMyAppkhi mã kiểm thử thay đổi.Chú thích các lệnh gọi kiểm thử bằng
@VisibleForTesting: Nếu một APK kiểm thử bên ngoài gọi một số ít phương thức hoặc hàm khởi tạo nội bộ trên mục tiêuandroid_appvà việc tái cấu trúc thành thư viện.impllà không thực tế, hãy chú thích những khai báo đó bằng@VisibleForTesting. Đường cơ sởbuild/make/core/proguard.flagstoàn cầu sẽ tự động giữ lại@VisibleForTestingmục trong các góiandroid.**,com.android.**vàcom.google.android.**mà không cần các 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ụng@UsedByReflectionhoặc các quy tắc thư viện đã xuất.)Sử dụng
trace_references_fromtrên các ranh giới thư viện trên nền tảng: Khi các hoạt động kiểm thử không thể liên kết tĩnh việc triển khai đích (ví dụ: các hoạt động kiểm thử thực thi các dịch vụ máy chủ hệ thống hoặc các tệp JAR nền tảng nhưframework-connectivity), hãy định cấu hìnhtrace_references_fromtrên mô-đun đích trỏ đến một mô-đun đồng hành thư viện Java chứa các nguồn kiểm thử. R8 theo dõi và giữ lại tất cả các lớp và thành phần mà mã byte đồng hành tham chiếu.
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 các quy tắc giữ lại cho hoạt động phản chiếu nội bộ hoặc lệnh gọi lại JNI của riêng nó, hãy xác định các 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ả các mục tiêu android_app và thư viện ở hạ nguồn liên kết tĩnh my-shared-library đều tự động kế thừa những quy tắc đó, vì vậy, các ứng dụng ở hạ nguồn không cần 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 dùng chung một bộ 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 một 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 hạ nguồn, hãy xem phần Tối ưu hoá cho tác giả thư viện.
Bước 4: Chú thích các 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 tính năng phản chiếu hoặc JNI và không được AAPT2 hoặc các đường cơ sở toàn cầu bao gồm, hãy thay thế các quy tắc -keep riêng biệt trong tệp .flags bằng chú giải nguồn trong Hướng dẫn về chú giải giữ lại (xem tài liệu tham khảo keepanno Javadoc):
@UsesReflection: Ưu tiên chú thích này khi bạn kiểm soát mã thực hiện hoạt động phản chiếu. Đặt chú thích này tại vị trí gọi phản chiếu để khai báo những lớp, phương thức hoặc trường đích được truy cập một cách linh động. Vì@UsesReflectiontự động mã hoá một điều kiện tiên quyết rằng bản thân vị trí gọi được chú thích có thể truy cập được, nên R8 sẽ cắt tỉa cả phương thức gọi và đích phản chiếu nếu vị trí gọi không được 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 mã hoặc thư viện bên ngoài khởi tạo hoặc gọi một cách phản chiếu, chẳng hạn như các lớp được tải bằngClass.forNametừ các khoáBundlehoặcSettings, hoặc các lớp trình bổ trợ được tải trên các trình tải lớp động. Chỉ địnhkind(chẳng hạn nhưKeepItemKind.CLASS_AND_METHODS),preconditions(@KeepCondition) và các ràng buộc 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 cắt tỉa các thành phầ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ữ 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ằng cách sử dụngGetMethodID,GetStaticMethodIDhoặcGetFieldID.@KeepForApi: Đặt trên các lớp hoặc thành phần API của thư viện phải giữ nguyên khi bản thân 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 khikeepannokhông áp dụng. 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 đó vô điều kiện. Việc áp dụng@Keepcho một phương thức hoặc trường sẽ hoạt động như một điểm truy cập vô điều kiện, giữ lại thành phần và lớp chứa của thành phần đó ngay cả khi lớp không bao giờ được khởi tạo, trong khikeepannothể hiện khả năng tiếp cận có điều kiện.
Cách sử dụng chú thích keepanno R8 trong một 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 khai báo hoặc vị trí 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 chú thích
keepannotrực tiếp thành mô hình quy tắc giữ lại nội bộ và loại bỏ chú thích khỏi đầu ra DEX cuối cùng, đồng thời không làm tăng 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 các lỗi đó.
Quy tắc giữ lại thành phần chung
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- Tại sao quy tắc này ảnh hưởng đến hiệu suất: Quy tắc này hoàn toàn dư thừa 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 nó buộc R8 giữ lại mọi lớp conActivity,ServicehoặcBroadcastReceiverđược tìm thấy ở bất kỳ đâu trên đường dẫn lớp, bao gồm cả các thành phần thư viện không dùng đến và các hoạt động gỡ lỗi bị vô hiệu hoá. - Cách khắc phục được đề xuất: Xoá quy tắc. AAPT2 kiểm tra tệp kê khai sáp nhập và tạo các quy tắc
-keepchính xác cho các thành phần đã đăng ký.
Quy tắc giữ trình xử lý lượt nhấp XML mở rộng
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- Lý do gây ả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 nội tuyến bất kỳ phương thức nào chấp nhận một tham sốViewduy nhất. - Cách khắc phục được đề xuất: Xoá quy tắc. AAPT2 quét các tệp XML bố cục và tạo các quy tắc giữ mục tiêu cho các phương thức được tham chiếu theo các thuộc tính
android:onClick.
Các quy tắc giữ lại phương thức getter và phương thức setter 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 gây ả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ả cá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 dùng đến và nội tuyến trên mã giao diện người dùng. - Cách khắc phục được đề xuất: 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 của thành phần hiển thị bằng cách sử dụng tên chuỗi phản chiếu, chẳng hạn nhưObjectAnimator.ofFloat(view, "translationZ", ...), hãy thay thế tên chuỗi bằng một tham chiếu thuộc tính được nhập (View.TRANSLATION_Zhoặc một 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 truy cập vào 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ính năng tối ưu hoá hoặc làm rối mã nguồn toàn bộ sẽ bị vô hiệu hoá
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- Lý do khiến hiệu suất bị ảnh hưởng: Việc đặt
-dontoptimizehoặc-dontshrinkbên trong tệp.flagssẽ âm thầm ghi đè các 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_appở hạ lưu liên kết thư viện. - Cách khắc phục được đề xuất: Xoá
-dontoptimize,-dontshrinkvà-dontobfuscatekhỏi tệp.flags. Kiểm soát rõ ràng hành vi tối ưu hoá 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 gây ảnh hưởng đến bản dựng: Cờ chẩn đoán tràn nhật ký bản dựng hoặc cố gắng ghi tệp đầu ra vào các đường dẫn cục bộ trong quá trình tạo bản dựng Soong có hộp cát.
- Cách khắc phục được đề xuất: Xoá các cờ này khỏi các tệp
.flagsđã cam kết. Soong tự động ghi đầu ra về việc sử dụng và liên kết 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/.
Các quy tắc giữ lại trong bản phát hành chính cho mã kiểm thử
- Lý do khiến hiệu suất bị ảnh hưởng: Việc chỉ thêm các quy tắc
-keeptuỳ chỉnh cho quyền truy cập kiểm thử buộc mã đó phải duy trì trạng thái không bị làm rối mã nguồn và được giữ lại trong các bản dựng phát hành công khai. Mặc dù việc chú thích các phương thức bằng@VisibleForTestinghoặc định cấu hìnhtrace_references_fromgiúp tránh duy trì các quy tắc-keeptheo cách thủ công, nhưng những biểu tượng đó vẫn được gửi trong tệp nhị phân sản xuất. - Giải pháp được đề xuất: Để loại bỏ hoàn toàn các điểm truy cập chỉ dành cho kiểm thử khỏi APK phát hành chính thức, hãy cấu trúc ứng dụng thành một thư viện
.implvà liên kết thư viện đó vào một APK kiểm thử tự đo lường bằngstatic_libs, như mô tả trong Bước 2: Di chuyển các quy tắc giữ dựa trên kiểm thử.
Ký tự đại diện chung chung dùng để khớp với mọi tài nguyên 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/*" />
- Tại sao điều này ảnh hưởng đến hiệu suất: Ký tự đại diện bao quát giữ lại mọi tài nguyên thuộc loại phù hợp trong mô-đun và các phần phụ thuộc của mô-đun, làm mất tác dụng của việc giảm kích thước tài nguyên (
shrink_resources: true) và tăng kích thước của tệp APK và bảngresources.arscđược ánh xạ. - Cách khắc phục được đề xuất:
- Ưu tiên các 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ư các chuỗi thử nghiệm được đánh số hoặc các đối tượng có thể vẽ theo chủ đề. Thay vào đó, hãy sử dụng câu lệnhswitchtại thời gian biên dịch hoặc ánh xạ qua các hằng số tĩnhR.id,R.stringhoặcR.drawable. Các 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ỏ chi phí tra cứu chuỗi 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ê chính xác giá trị nhận dạng tài nguyên trong
tools:keep(ví dụ:tools:keep="@raw/third_party_licenses"). - Xoá các tệp
keep.xmldư thừ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), 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 các 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 các 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 một cách rõ ràng, 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.
Tạo và kiểm thử các mô-đun
Tạo 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 bài kiểm thử đơn vị và bài kiểm thử đo lường cho mô-đun bằng cách sử dụng
atest:atest <TEST_MODULE_NAME>Đối với các ứ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 các kiểm thử đo lường và kiểm thử giao diện người dùng trên các thiết bị thực mục tiêu hoặc trong phòng kiểm thử thiết bị để thực hiện các đường dẫn phản chiếu thời gian chạy, các liên kết IPC và việc tăng tài nguyên.
Kiểm tra các khác biệt về DEX và tài nguyên
So sánh 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 loại bỏ 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 bắt buộc:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
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 mã nguồn mở có 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 chính xác mức tác động của từng quy tắc giữ lại trong quá trình biên dịch. Soong tích hợp trình phân tích này trực tiếp 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 các 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
R8_DUMP_KEEP_RADIUS=trueđược thiết lập, 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 từng mô-đun đã 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:- Bán kính giữ lại tức thì: 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 điều kiện ràng buộc cụ thể mà quy tắc đó thực thi đối với việc thu nhỏ, tối ưu hoá hoặc làm rối mã nguồn.
- Quy tắc bao hàm: Những quy tắc lưu giữ hoặc chú thích khác đã lưu giữ cùng các mục. Nếu một quy tắc tuỳ chỉnh hoàn toàn được bao hàm bởi một quy tắc AAPT2 hoặc một quy tắc cơ sở toàn cầu, thì bạn có thể xoá quy tắc đó một cách an toàn.
- Quy tắc trên toàn gói và quy tắc chung: Quy tắc sử dụng ký tự đại diện trên toàn gói hoặc áp dụng chỉ thị cấu hình chung.
Chuyển đổi đầu ra
r8keepradius.pbthành báo cáo HTML tương tác bằng cách sử dụngKeepRadiusHtmlReportGenerator(được 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ả cá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 trực tiếp, số lượng mục được giữ lại và các quy tắc giữ lại có bán kính lớn nhất trong bản dựng. Để biết thêm thông tin về cách diễn giải điểm số rút gọn, tối ưu hoá và làm rối mã nguồn trong báo cáo đã tạo, hãy xem phần Sử dụng Trình phân tích cấu hình R8.
Soong cũng hỗ trợ 2 biến môi trường chẩn đoán R8 bổ sung 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ả các JAR đầu vào, 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.
Xác thực các quy tắc dành cho 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 người dùng hạ nguồn (export_proguard_flags_files: true), những quy tắc đó không được bao gồm các cờ chung làm thay đổi hoặc vô hiệu hoá hoạt động tối ưu hoá cho ứng dụng sử dụng.
R8 cung cấp một trình phân tích quy tắc giữ 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 sẽ phân tích cú pháp từng tệp cấu hình và gặp lỗi với thông tin chẩn đoán về tệp và dòng nếu gặp phải các chỉ thị không được phép trong các quy tắc của người dùng thư viện. Các chỉ thị không được phép bao gồm tối ưu hoá toàn cục, thu hẹp hoặc vô hiệu hoá việc làm rối mã nguồn (chẳng hạn như -dontoptimize hoặc -dontshrink), đóng gói lại và các cờ sửa đổi quyền truy cập, cờ chẩn đoán hoặc 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 trong một kho lưu trữ để gắn cờ các quy tắc đã được AAPT2 hoặc đường cơ sở nền tảng toàn cầu, ký tự đại diện rộng, các chế độ ghi đè -dontoptimize và các quy tắc chỉ giữ lại để kiểm thử bao gồm:
# 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 bán kính lưu giữ chính xác của lớp, trường và phương thức của mọi quy tắc trong bản dựng.