سیستم ساخت پلاتفرم Android (Soong) کامپایلر R8 را روی
هدفهایی که کدبایت DEX را کامپایل میکنند اجرا میکند، ازجمله برنامههای Android (android_app)، آزمایشها
(android_test)، و کتابخانههای نصبشدنی Java با compile_dex: true (مثل services.jar)، برای کوچک کردن، بهینهسازی، و حذف کد و منابع استفادهنشده. در کتابخانههای
ایستا (android_library و java_library ایستا)، Soong مستقیماً R8 را اجرا نمیکند، بلکه از بلوک دارایی optimize برای پیوست و انتشار قوانین نگهداری مصرفکننده (export_proguard_flags_files: true) به هدفهای DEX پاییندستی که آنها را بهصورت ایستا پیوند میدهند استفاده میکند.
برای مهندسان پلاتفرمی که تصاویر سیستم یا بستههای فروشنده میسازند، حذف قوانین نگهداری بیشازحد گسترده مزایای مستقیمی برای سلامت سیستم دارد:
- ردپای کوچکتر پارتیشن سیستم: R8 کلاسها، روشها، و منابع مرده را قبلاز بستهبندی فایلهای APK و JAR در
/system،/system_ext،/product، و/vendorبرمیدارد. - آرتیفکتهای کامپایلشده کوچکتر: روشهای DEX کمتر به معنی فایلهای
.odexو.vdexکوچکتر تولیدشده توسطdex2oatدرطول زمان ساخت یا کامپایل در دستگاه است. - فشار کمتر بر حافظه زمان اجرا: چون Android جدولهای منبع
.odex،.vdex، و APK را بااستفاده از فراخوانیهایmmapصفحهبندی براساس تقاضا به حافظه پردازش نگاشت میکند، باینریهای کوچکتر خطاهای صفحه اصلی را درطول راهاندازی برنامه کاهش میدهند و حافظه کد و منبع مقیم را در سراسر فرایندها کاهش میدهند. برای کسب اطلاعات بیشتر درباره اینکه کد برنامه کامپایلشده چگونه بر حافظه دستگاه تأثیر میگذارد، کد برنامه حافظه است را ببینید. - بهینهسازی مؤثرتر کل برنامه: محدودیتهای نگهداری محدود به R8 اجازه میدهد روشهای درونخطی، واسط غیرمجازی، و تماسهای مجازی را انجام دهد، فیلدهای استفادهنشده را حذف کند، و ثابتها را در سراسر کلاسها منتشر کند.
پیکربندی بهینهسازی R8 در Soong
در فایلهای Android.bp، بهینهسازی R8 را بااستفاده از دارایی optimize
در بلوک android_app و هدفهای java_library قابلنصب (یا در
android_library و واحدهای java_library ایستا برای صادر کردن قوانین
نگهداری مصرفکننده) پیکربندی کنید:
android_app {
name: "MySystemApp",
srcs: ["src/**/*.java"],
optimize: {
obfuscate: true,
shrink_resources: true,
},
}
جدول زیر رایجترین ویژگیهای optimize در Soong را خلاصه میکند
(تعریفشده در build/soong/java/dex.go):
| دارایی | شرح |
|---|---|
enabled |
کنترل میکند که آیا R8 روی هدف اجرا شود یا نه. برای همه هدفهای android_app،
مقدار پیشفرض true است.
|
shrink |
کنترل لرزش درخت برای برداشتن کلاسها، فیلدها، و روشهای غیرقابلدسترس. بهطور پیشفرض روی true برای android_app
هدف (false برای java_library مستقل و
واحدهای آزمایش که -dontshrink را میگذرانند، مگر اینکه بهطور صریح
تنظیم شده باشد) تنظیم میشود.
|
optimize |
بهینهسازیهای کدبایت مانند درونخطیسازی روش، ادغام کلاس،
انتشار ثابت، و حذف شاخه مرده را کنترل میکند. بهطور پیشفرض روی
true برای android_app هدف تنظیم میشود (کنترلشده توسط
پرچم ساخت نسخه RELEASE_R8_OPTIMIZE_BY_DEFAULT).
|
obfuscate |
کوچکسازی و تغییر نام شناسه را کنترل میکند. بهطور پیشفرض روی
false برای سازگاری تاریخی تنظیم میشود، اما در هرجا که ممکن است آن را روی
true تنظیم کنید تا اندازه DEX کاهش یابد و
بهینهسازیهای عمیقتر باز شود (به
فعال کردن مبهمسازی در هرجا که ممکن است مراجعه کنید).
|
shrink_resources |
منابع استفادهنشده (res/ ورودی) را از
فایل APK بستهبندیشده پساز کوچک کردن کد برمیدارد. پیشفرض false است.
|
optimized_shrink_resources |
خط لوله کوچککننده منبع و کد R8 یکپارچه را اجرا میکند تا R8
ارجاعهای کد و منبع را بهطور مشترک در یک گذر ردیابی کند. وقتی
shrink_resources: true تنظیم میشود، بهطور پیشفرض
RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT
(true در ساختهای پلاتفرم استاندارد) است.
|
proguard_flags_files |
فایلهای .flags یا .pro مخصوص واحد را که حاوی قوانین نگهداری سفارشی است فهرست میکند. از افزودن فایلهای سفارشی زمانیکه
گزارمانهای استاندارد یا پیشفرضها کافی هستند، خودداری کنید.
|
export_proguard_flags_files |
proguard_flags_files این کتابخانه را به
واحدهای پاییندستی که بهصورت ایستا به آن وابسته هستند منتشر میکند.
|
trace_references_from |
فهرست هدفهای همراه کتابخانه Java که کدبایت آنها به این هدف ارجاع میدهد را R8 بهطور خودکار ردیابی و حفظ میکند. |
درصورت امکان، مبهمسازی را فعال کنید
در Soong، obfuscate بهطور پیشفرض برای سازگاری تاریخی روی false تنظیم میشود، اما
باید obfuscate: true را بهطور صریح روی android_app و هدفهای DEX مستقل
در هرجا که ممکن است تنظیم کنید:
- ردپای کوچکتر DEX و
.vdex: تغییر نام بستهها، کلاسها، فیلدها، و روشها به شناسههای کوتاه (a،b) استخر رشته DEX (string_idsوstring_data_item) و توصیفگرهای نوع را کوچک میکند. ازآنجاییکه Android فایلهای.vdexو.odexرا در حافظه پردازش نگاشت میدهد، جدولهای نماد کوچکتر مستقیماً هم اندازه پارتیشن سیستم و هم ردپای حافظه زمان اجرا را کاهش میدهند. - بهینهسازیهای عمیقتر کل برنامه: اجازه دادن به R8 برای تغییر نام شناسهها ادغام کلاس، مسطح کردن بسته، و حذف تکراری کلاس مصنوعی در سراسر بستههایی را که R8 درغیراینصورت باید از آنها صرفنظر کند تا از تصادم نام جلوگیری کند، باز میکند.
- نمادگذاری ردیابی پشته کامل: فعال کردن مبهمسازی در Soong باعث کاهش قابلیت اشکالزدایی نمیشود. برای هر هدف کامپایلشده R8، Soong فایل
نگاشت
proguard_dictionaryرا در پوشه واسط واحد تولید میکند، همه فرهنگهای لغت واحد را درproguard-dict.zipساختمان آرتیفکت دستهبندی میکند، در سرصفحه DEX درهمسازی نگاشت (--map-id-template) جاسازی میکند، و ویژگیهای کلاسSourceFile(--source-file-template) را بازنویسی میکند تاretraceبتواند ردیابی پشته را بهطور خودکار نمادگذاری کند.
obfuscate: false را فقط برای موارد زیر نگه دارید (یا نامهای میانای برنامهسازی کاربردی عمومی را بهطور صریح حفظ کنید):
- کتابخانههای مشترک در bootclasspath یا
system_serverclasspath: واحدها (java_libraryیاjava_sdk_library) که یک سطح API خارجی را نمایان میکنند و بهصورت پویا توسط واحدهای دیگر در زمان اجرا پیوند داده میشوند (مثلframework.jar،services.jar، یا<uses-library>هدفها) باید یاobfuscate: falseرا حفظ کنند یا سطح API خود را بااستفاده از قوانین@KeepForApiیا-keep(همراه باprotect_api_surface: trueبرای هدفهای bootclasspath) بهطور صریح حفظ کنند تا فراخوانندگان کامپایلشده دربرابر کدگذاریهای آنها بتوانند نامهای کلاس و عضو را در زمان اجرا حل کنند.
پیشاز فعال کردن obfuscate: true در برنامه، پیشنیازهای انتقال زیر را
تأیید کنید:
- وابستگیهای APK آزمایش خارجی: اگر یک
android_testمجموعه APK خارجیinstrumentation_for: "MyApp"کلاسها یا روشهای داخلیMyAppرا مستقیماً فراخوانی کند، تغییر نام آن نمادها باعثNoSuchMethodErrorیاNoClassDefFoundErrorدر زمان اجرای آزمایش میشود. ترجیحاً آزمایش را بهصورت پیوند APK خودابزارسازیشدهMyApp.implایستا ساختاردهی کنید، قلابهای آزمایش را با@VisibleForTestingحاشیهنویسی کنید، یاtrace_references_fromرا پیکربندی کنید (به مرحله ۲: انتقال قوانین نگهداری آزمونمحور مراجعه کنید) تا نشانهای داخلی موردنیاز آزمایشها حفظ شود و بقیه برنامه مبهمسازی شود. - بازتاب مبتنی بر رشته و JNI: اگر واحدی کلاسها، روشها،
یا فیلدها را براساس نامهای رشتهای تحتاللفظی (
Class.forName،getDeclaredMethod، یا JNIFindClassوGetMethodID) بدون قوانین یا گزارمانهای نگهداری جستجو کند، تغییر نام مبهمسازی آن هدفها را تغییر میدهد و جستجوهای زمان اجرا را مختل میکند. (توجه داشته باشید که انعکاس بدون حاشیهنویسی نیز تحتshrink: trueوoptimize: trueناموفق است.) آن نقاط ورودی را با راهنمای حفظ حاشیهنویسیها (@UsesReflection،@UsedByReflection، یا@UsedByNative) حاشیهنویسی کنید تا R8 نامهای آنها را حفظ کند و بقیه واحد را مبهم کند.
از اصل صفر پرچم سفارشی پیروی کنید
واحد پلاتفرم Android ایدهآل هیچ فایل proguard.flags یا keep.xml سفارشی ندارد. در ساخت پلاتفرم Android، اکثریت قریب به اتفاق قوانین نگهداری سفارشی
اضافی هستند:
- خطوط پایه پلاتفرم و AAPT2: اکثر نقاط ورودی ازقبل بهطور خودکار توسط خطوط پایه پلاتفرم جهانی (مثل
@Keep، روشهای JNInative، و@VisibleForTesting) حفظ میشوند یا توسط AAPT2 ازAndroidManifest.xmlو منابع چیدمان تولید میشوند (به درک قوانین حفظ پلاتفرم پیشفرض مراجعه کنید). - جایگزینهای هدفیابیشده: در مواردی که باید نقاط ورودی حفظ شوند،
شرحهای سایت اعلانی (
keepanno،@VisibleForTesting)، قوانین کتابخانه صادرشده (export_proguard_flags_files: true)، یا پیوند آزمایش ایستا را به فایلهای.flagsجداشده ترجیح دهید (به ممیزی و انتقال قوانین نگهداری موجود مراجعه کنید).
قبلاز افزودن یا حفظ فایل سفارشی proguard.flags یا keep.xml، بررسی کنید
آیا قانون ازقبل توسط خطوط پایه پیشفرض مدیریت میشود یا میتواند به
گزارمانهای کد منتقل شود.
آشنایی با قوانین نگهداری پلاتفرم پیشفرض
بنابراین، Soong بهطور خودکار قوانین پایه جهانی زیر را به هر فراخوانی R8 در هدفهای کامپایل DEX (android_app، android_test، و
واحدهای java_library قابلنصب) که در build/soong/java/dex.go پیکربندی شدهاند منتقل میکند:
build/make/core/proguard.flags: کلاسها و اعضایی را که با@com.android.internal.annotations.VisibleForTestingدر همه بستهها و با@VisibleForTesting(androidx.annotation.VisibleForTestingیاcom.google.common.annotations.VisibleForTesting) در بستههایandroid.**،com.android.**، وcom.google.android.**حاشیهنویسی شدهاند حفظ میکند. همچنین@TestApi،@Keep(androidx.annotation،android.support.annotation،com.android.internal.annotations)،@KeepForWeakReference،@WeaklyReferencedCallback، و@dalvik.annotation.optimization.**را حفظ میکند.build/make/core/proguard_basic_keeps.flags:SourceFileویژگیها را برای ردیابی پشتهای، شرحهای نمایان بودن زمان اجرا (RuntimeVisible*Annotations)، ویژگیهایExceptionsوAnnotationDefault، روشهایnative، اعضایSerializable، روشهای@JavascriptInterface، سازندههایThrowable(String)، فیلدهایParcelable$CREATOR، و فیلدهای protobufMessageLiteحفظ میکند.build/make/core/proguard/kotlin.flags: هشدارهای بیضرر را برای فرادادههای Kotlin خاص (kotlin.Metadataوkotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) بیصدا میکند و حاشیهنویسیهای KotlinDebugMetadataرا در ساختهای انتشار حذف میکند.build/make/core/proguard/checknotnull.flags: تماسهای کمکی بررسی تهی رایج (com.google.common.base.Preconditions.checkNotNullوdagger.internal.Preconditions.checkNotNull*) را با بررسیهای تهی بایتکد مختصر جایگزین میکند. این فایل بهطور عمدیObjects.requireNonNullرا حذف میکند تا پیامهای استثنای صریح را در مرزهای API چارچوب حفظ کند.build/make/core/proguard/enumvalues.flags: روشهایvaluesوvalueOfرا در انواعenumحفظ میکند، مگراینکه پیکربندی ساخت آن را غیرفعال کرده باشد.- قوانین تولیدشده خودکار AAPT2: AAPT2 فایلهای
AndroidManifest.xml، چیدمان XML، و اولویت XML ادغامشده را بررسی میکند تا قوانین دقیق نگهداری برای هرActivity،Service،BroadcastReceiver،ContentProvider،BackupAgent،Application، زیرکلاس سفارشیView، زیرکلاسPreference، و روش XMLandroid:onClickثبتشده تولید کند.
ممیزی و انتقال قوانین نگهداری موجود
هنگام ممیزی فایلهای proguard.flags یا keep.xml موجود در مخزن پلاتفرم، هر قانون را بهترتیب دربرابر سلسلهمراتب چهار مرحلهای زیر ارزیابی کنید:
مرحله ۱: حذف قوانین زائد یا منسوخ
قوانینی را که قبلاً با خطوط مبنای سراسری، مانیفست AAPT2، و قوانین چیدمان، یا پیشفرضهای R8 پوشش داده شدهاند، و همچنین قوانینی را که به کلاسها یا بستههایی ارجاع میدهند که دیگر وجود ندارند حذف کنید.
اگر همه قوانین در فایل .flags اضافی هستند، فایل را حذف کنید و proguard_flags_files را از Android.bp بردارید. اگر optimize بلوک باقیمانده فقط
تنظیمات پیشفرض را بازگو میکند، بلوک اضافی را بردارید و فایل ساخت را
با bpfmt -w Android.bp قالببندی کنید. هنگام پاکسازی بسته، بررسی کنید که آیا واحدهای همراه در زیرشاخهها (مثل گونههای هدف Kotlin) به فایل پرچم یکسانی ارجاع میدهند و آنها را با هم بهروزرسانی کنید.
مرحله ۲: انتقال قوانین نگهداری تستمحور
قوانین نگهداری تستمحور را بااستفاده از یکی از الگوهای زیر از فایلهای .flags تولید سفارشی خارج کنید:
پیوند دادن کتابخانه پیادهسازی به آزمایشها (
static_libs): وقتیandroid_testجزئیات پیادهسازی داخلی یا بسته خصوصی برنامه را اجرا میکند، معماری پلاتفرم توصیهشده این است که فایلهای منبع برنامه درandroid_library(MyApp.impl) قرار داده شود و بهصورت ایستا بهMyApp.implدرMyAppو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"], }این الگوی ۳ واحدی به
MyAppTestsاجازه میدهد بهعنوان آزمایشی خودکار اجرا شود و به کلاسهای داخلی دسترسی کامل داشته باشد، بهMyAppاجازه میدهدobfuscate: trueرا بدون اینکه آزمایشها را خراب کند آزادانه فعال کند، از ارسال نقاط ورودی فقط آزمایشی در فایل APK تولید جلوگیری میکند، و نیاز به بازسازیMyAppرا هنگام تغییر کد آزمایش برطرف میکند.قلابهای آزمایش را با
@VisibleForTestingحاشیهنویسی کنید: اگر فایل APK آزمایش خارجی تعداد کمی از روشها یا سازندههای داخلی را درandroid_appهدف فراخوانی میکند و بازسازی آن به کتابخانه.implغیرعملی است، آن اظهارنامهها را با@VisibleForTestingحاشیهنویسی کنید. خط پایه جهانیbuild/make/core/proguard.flagsبهطور خودکار@VisibleForTestingمورد را درandroid.**،com.android.**، وcom.google.android.**بستهها بدون فایلهای سفارشی.flagsحفظ میکند. (برای واحدهای فروشنده خارج از این فضای نام، از@UsedByReflectionیا قوانین کتابخانه صادرشده استفاده کنید.)استفاده از
trace_references_fromدر مرزهای کتابخانه پلاتفرم: وقتی آزمایشها نمیتوانند پیادهسازی هدف را بهصورت ایستا پیوند دهند (برای مثال، آزمایشهایی که خدمات سرور سیستم یا فایلهای JAR پلاتفرم مثلframework-connectivityرا اجرا میکنند)،trace_references_fromرا در واحد هدف پیکربندی کنید تا به واحد همراه کتابخانه Java حاوی منابع آزمایش اشاره کند. R8 همه کلاسها و اعضایی را که توسط بایتکد همراه ارجاع داده شدهاند ردیابی و حفظ میکند.
مرحله ۳: صادر کردن قوانین از کتابخانه مالک
اگر java_library یا android_library مشترکی برای بازتاب داخلی یا
بازخوانهای JNI خود به قوانین نگهداری نیاز دارد، قوانین را در هدف کتابخانه تعریف کنید و
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,
},
}
همه اهداف کتابخانه و android_app پاییندست که بهصورت ایستا پیوند میدهند
my-shared-library بهطور خودکار این قوانین را بهارث میبرند، بنابراین برنامههای پاییندست
نیازی به تکرار آنها ندارند. وقتی چندین واحد در سراسر دایرکتوریهای مختلف باید مجموعه قانونی را که به یک کتابخانه کد واحد مرتبط نیست همرسانی کنند، فایل را ازطریق .flags
filegroup صریح در Android.bp منتشر کنید تا واحدها بتوانند بهطور
:my-shared-flags پاکیزه در مرزهای بسته به آن ارجاع دهند. برای راهنمایی کلی درباره
قوانین نگهداری مصرفکننده کتابخانه نویسندگی بدون محدود کردن بهینهسازی برنامه پاییندست، به بهینهسازی برای نویسندگان کتابخانه مراجعه کنید.
مرحله ۴: افزودن شرح به بیانیهها در کد منبع
وقتی کلاس، روش، سازنده، یا فیلدی ازطریق انعکاس یا JNI قابلدسترسی باشد و تحت پوشش AAPT2 یا خطوط پایه جهانی نباشد، قوانین -keep
جداشده در فایلهای .flags را با گزارمانهای منبع از
راهنمای حفظ گزارمانها جایگزین کنید (به
مرجع keepanno Javadoc مراجعه کنید):
@UsesReflection: وقتی بر کدی که انعکاس را انجام میدهد کنترل دارید، این حاشیهنویسی را ترجیح دهید. آن را در سایت فراخوانی انعکاس قرار دهید تا مشخص کنید کدام کلاسهای هدف، روشها، یا فیلدها بهصورت پویا قابلدسترسی هستند. چون@UsesReflectionپیششرطی را بهطور خودکار کدبندی میکند که سایت تماس حاشیهنویسیشده خود قابلدسترس است، اگر سایت تماس استفادهنشده باشد، R8 هم فراخواننده و هم هدف انعکاسی را هرس میکند.@UsedByReflection: در کلاسها، روشها، فیلدها، یا سازندههایی که بهصورت انعکاسی توسط کد یا کتابخانههای خارجی نمونهسازی یا فراخوانی میشوند قرار دهید، مثل کلاسهایی که باClass.forNameازBundleیا کلیدهایSettingsبارگیری میشوند، یا کلاسهای افزایهای که در سراسر بارکنندههای کلاس پویا بارگیری میشوند.kind(مثلKeepItemKind.CLASS_AND_METHODS)،preconditions(@KeepCondition)، و محدودیتهای پارامتر را مشخص کنید تا R8 فقط قرارداد انعکاسی دقیق را نگه دارد و همچنان بتواند اعضای استفادهنشده کلاس را بهینهسازی یا حذف کند. @UsedByReflectionرا به عناصر ثبتشده در مانیفست مثلJobServiceیاBroadcastReceiverاضافه نکنید، زیرا AAPT2 قبلاً آنها را نگه میدارد.@UsedByNative: در روشها یا فیلدهایی قرار دهید که از کد C یا C++ JNI بااستفاده ازGetMethodID،GetStaticMethodID، یاGetFieldIDقابلدسترسی هستند.@KeepForApi: در کلاسهای میانای برنامهسازی کاربردی کتابخانه یا اعضایی قرار دهید که باید هنگام کوچک شدن کتابخانه قبلاز توزیع دستنخورده باقی بمانند.@Keep(androidx.annotation.Keep): وقتیkeepannoقابلاعمال نیست، از آن بهعنوان جایگزین استفاده کنید. اعمال@Keepروی کلاس باعث میشود کلاس و همه اعضای آن بدون قیدوشرط حفظ شوند. اعمال@Keepبه روش یا فیلد بهعنوان نقطه ورودی بدون قیدوشرطی عمل میکند که عضو و کلاس حاوی آن را حفظ میکند، حتی اگر کلاس هرگز نمونهسازی نشود، درحالیکهkeepannoدسترسیپذیری شرطی را بیان میکند.
برای استفاده از حاشیهنویسیهای R8 keepanno در واحد Soong:
"keepanno-annotations"را بهlibsدرAndroid.bpاضافه کنید:libs: [ "keepanno-annotations", ],
com.android.tools.r8.keepanno.annotations.*را وارد کنید و سایت فراخوانی یا بیانیه را در کد منبع Java یا 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
keepannoگزارمانها را مستقیماً به مدل قانون نگهداری داخلی خود ترجمه میکند و گزارمانها را از برونداد نهایی DEX حذف میکند و سربار کدبایت زمان اجرای صفر را اضافه میکند.
از اشتباهات رایج اجتناب کنید
بخشهای زیر درباره proguard.flags و keep.xml
اشتباهات رایج در کد پلاتفرم و نحوه رفع آنها توضیح میدهد.
قوانین نگهداری عنصر گسترده
# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
- چرا به عملکرد آسیب میزند: این قانون کاملاً با AAPT2 زائد است.
چون از نویسه عام استفاده میکند بدون اینکه بررسی کند آیا عنصر در
AndroidManifest.xmlادغامشده نهایی ثبت شده است یا نه، R8 را مجبور میکند هر زیرکلاسActivity،Service، یاBroadcastReceiverرا که در هر جایی از مسیر کلاس پیدا میشود، ازجمله عناصر کتابخانه استفادهنشده و فعالیتهای اشکالزدایی غیرفعال، حفظ کند. - اصلاح پیشنهادی: قانون را حذف کنید. AAPT2 مانیفست ادغامشده را بازرسی میکند و
قوانین دقیق
-keepرا برای عناصر ثبتشده تولید میکند.
قوانین نگهداری کنترلکننده کلیک XML گسترده
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- چرا به عملکرد آسیب میزند: نگه داشتن هر روش
public void *(View)در هر کلاس در واحد مانع از آن میشود که R8 هر روشی را که پارامترViewواحدی را میپذیرد حذف کند یا درخط کند. - اصلاح پیشنهادی: قانون را حذف کنید. AAPT2 فایلهای چیدمان XML را اسکن میکند و
قوانین نگهداری هدفدار برای روشهای ارجاعشده توسط
android:onClickمشخصهها تولید میکند.
قوانین نگه داشتن دریافتکننده و تنظیمکننده «نمای گسترده»
# 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*();
}
- چرا به عملکرد آسیب میزند: این قانون R8 را مجبور میکند همه دریافتکنندهها و تنظیمکنندهها را در همه زیرکلاسهای
Viewدر برنامه و همه کتابخانههای پیوندشده (ازجمله کتابخانههای AndroidX و Material) حفظ کند و از برداشتن روشهای مرده و درونخطی کردن در سراسر کد واسط کاربر جلوگیری میکند. - اصلاح پیشنهادی: قانون را حذف کنید. AAPT2 ازقبل سازندهها را برای
کلاسهای سفارشی
Viewکه از فایلهای چیدمان XML باد میشوند حفظ میکند. اگر کد دارایی نمایشی را بااستفاده از نامهای رشتهای بازتابی مثلObjectAnimator.ofFloat(view, "translationZ", ...)پویانمایی میکند، نام رشتهای را با مرجع دارایی تایپشده (View.TRANSLATION_Zیا پیادهسازی سفارشیFloatPropertyیاIntProperty) جایگزین کنید تا بهطور کامل از بازتاب اجتناب کنید. اگر نمیتوان از دسترسی به دارایی بازتابی اجتناب کرد، دریاکننده یا تنظیمکننده خاص را با@UsedByReflectionحاشیهنویسی کنید.
بهینهسازی یا مبهمسازی کلی غیرفعال میشود
# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
- چرا به عملکرد آسیب میزند: قرار دادن
-dontoptimizeیا-dontshrinkدر فایل.flagsتنظیماتAndroid.bpواحد را بیصدا ملغی میکند و مراحل بهینهسازی را در سراسر هدف غیرفعال میکند. بدتر از آن، اگر کتابخانهای فایل.flagsحاوی-dontoptimizeرا صادر کند، بهینهسازی R8 را برای هرandroid_appپاییندستی که کتابخانه را پیوند میدهد غیرفعال میکند. - اصلاح توصیهشده:
-dontoptimize،-dontshrink، و-dontobfuscateرا از.flagsفایل بردارید. رفتار بهینهسازی را بهطور صریح درAndroid.bpبااستفاده از بلوکoptimize(shrink،optimize،obfuscate) در هدف برگ کنترل کنید.
پرچمهای تشخیص خرابی در قوانین تعهدشده
# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
- چرا به ساختار آسیب میزند: پرچمهای تشخیص خرابی گزارشهای ساختار را پر میکنند یا تلاش میکنند فایلهای برونداد را در مسیرهای محلی درطول ساختارهای Soong در محیط امن بنویسند.
- اصلاح پیشنهادی: این پرچمها را از فایلهای
.flagsتعهدشده حذف کنید. Soong بهطور خودکار بروندادهای نگاشت و استفاده R8، مثلproguard_dictionaryوproguard_usage.zip، را در دایرکتوری واسط واحد تحتout/soong/.intermediates/مینویسد.
قوانین نگهداری تولید برای کد آزمایشی
- چرا به عملکرد آسیب میزند: افزودن قوانین سفارشی
-keepصرفاً برای دسترسی آزمایشی باعث میشود آن کد در ساختهای تولید مبهمسازینشده و حفظ شود. درحالیکه حاشیهنویسی روشها با@VisibleForTestingیا پیکربندیtrace_references_fromاز نگهداری قوانین دستی-keepجلوگیری میکند، آن نمادها همچنان در باینری تولید ارسال میشوند. - اصلاح پیشنهادی: برای اینکه نقاط ورودی فقط آزمایشی را کاملاً از فایل APK تولید خارج کنید، برنامه را به
.implکتابخانه ساختاربندی کنید و آن را بااستفاده ازstatic_libsبه فایل APK آزمایشی خودابزاربندی پیوند دهید، همانطور که در مرحله ۲: انتقال قوانین نگهداری آزمونمحور توضیح داده شده است.
کاراکترهای جانشین منبع فراگیر در keep.xml
<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@raw/*,@drawable/*,@string/*" />
- چرا عملکرد را کاهش میدهد: نویسههای عام گسترده همه منابع را در نوع مطابقت در سراسر واحد و وابستگیهای آن حفظ میکند و کوچکسازی منبع (
shrink_resources: true) را شکست میدهد و جدولresources.arscنگاشتشده و APK را بزرگ میکند. - اصلاح توصیهشده:
- ارجاعهای منبع ایستا را بر
keep.xmlترجیح دهید: از روشResources.getIdentifierبرای جستجوی مجموعه محدود منابع بهصورت پویا، مثل رشتههای آزمایش شمارهدار یا طراحیهای با موضوع، استفاده نکنید. درعوض، از دستورswitchزمان ترجمه یا نگاشت روی ثابتهایR.id،R.string، یاR.drawableایستا استفاده کنید. مرجعهای ایستا به R8 و AAPT2 امکان میدهند منابع زنده دقیق را ردیابی کنند، سربار جستجوی رشته زمان اجرا را حذف کنند، و نیاز بهkeep.xmlرا بهطور کامل برطرف کنند. - فهرست کردن نامهای منبع خاص: اگر جستجوی پویایی لازم است، مثلاً
توسط یک خواننده پروانه طرف سوم، شناسههای منبع دقیق را در
tools:keep(برای مثال،tools:keep="@raw/third_party_licenses") فهرست کنید. - حذف فایلهای
keep.xmlاضافی: اگر منابع فهرستشده ازقبل بهصورت ایستا در کد (R.raw.foo) یا XML (@raw/foo) ارجاع داده شده باشند، کوچککننده آنها را بهطور خودکار حفظ میکند، بنابراین میتوانیدres/raw/keep.xmlرا حذف کنید.
- ارجاعهای منبع ایستا را بر
درستیسنجی و ممیزی تغییرات قانون
هرگاه قوانین نگهداری را در proguard.flags یا keep.xml بردارید یا محدود کنید،
تأیید کنید که واحد بهطور کامل ساخته میشود، آزمونهای واحد و ابزار را میگذراند،
و همه نقاط ورودی موردنیاز را حفظ میکند.
ساختن و آزمایش کردن واحدها
واحد را بهطور کامل بسازید تا مطمئن شوید R8 و AAPT2 بدون هشدارهای مرجع ازدسترفته تکمیل میشوند:
m <MODULE_NAME>اجرای آزمونهای واحد و ابزارگری برای واحد بااستفاده از
atest:atest <TEST_MODULE_NAME>برای برنامههای سیستم، خدمات دارای امتیاز، یا واحدهای سختافزارمحور، ابزارهای دقیق و آزمایشهای میانای کاربر را در دستگاههای فیزیکی هدف یا در آزمایشگاه آزمایش دستگاهتان اجرا کنید تا مسیرهای بازتاب زمان اجرا، پیوندهای IPC، و تورم منابع را تمرین کنید.
بررسی تفاوتهای منبع و DEX
APK یا JAR را قبل و بعداز تغییرات قانون مقایسه کنید تا مطمئن شوید که R8 کد و منابع مرده را بدون حذف نقاط ورودی موردانتظار برمیدارد:
از
apkanalyzerیاdexdumpبرای بازرسی کلاسها، روشها، و فیلدهای نگهداریشده در فایلهای APK یا DEX برونداد استفاده کنید:apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apkهنگام اصلاح
keep.xmlیا فعال کردنshrink_resources، ازaapt2 dump resourcesبرای تأیید اینکه ساختار منابع استفادهنشده را حذف میکند و منابع موردنیاز را حفظ میکند استفاده کنید:aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
با R8، شعاع نگهداری و شمول قاعده را تجزیهوتحلیل کنید
گردآورنده منبع باز R8 شامل تجزیهوتحلیلگر شعاع نگهداری است
(که در Android Studio و Gradle نیز بهعنوان
تجزیهوتحلیلگر پیکربندی R8 آشکار میشود) که تأثیر دقیق
هر قانون نگهداری را درطول گردآوری اندازهگیری میکند. Soong این تحلیلگر را
مستقیماً در ساخت پلاتفرم Android ادغام میکند (در
build/soong/java/dex.go پیکربندی شده است).
برای تجزیهوتحلیل قوانین نگهداری برای واحد پلاتفرم یا در کل ساختار:
ساخت را با متغیر محیطی
R8_DUMP_KEEP_RADIUS=trueاجرا کنید:R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>وقتی
R8_DUMP_KEEP_RADIUS=trueتنظیم میشود، Soong به R8 دستور میدهد تا سنجههای قانون نگهداری را در فایلr8keepradius.pbمیانهای برای هر واحد کامپایلشده تحتout/soong/.intermediates/ضبط کند. برای هر قانون نگهداری وkeepannoگزارمان، R8 موارد زیر را ضبط میکند:- شعاع نگهداری فوری: کلاسها، فیلدها، و روشهای دقیقی که توسط قانون حفظ میشوند، همراه با محدودیتهای خاصی که دربرابر کوچکسازی، بهینهسازی، یا مبهمسازی اعمال میکند.
- شمول قانون: کدام قوانین نگهداری یا گزارمانهای دیگر ازقبل همان موارد را حفظ میکنند. اگر یک قانون سفارشی کاملاً تحت پوشش یک قانون AAPT2 یا یک قانون پایه جهانی قرار گیرد، میتوانید آن را با خیال راحت حذف کنید.
- قوانین سراسری و در سطح بسته: قوانینی که از حروف عام در سطح بسته استفاده میکنند یا دستورات پیکربندی سراسری را اعمال میکنند.
برونداد
r8keepradius.pbرا بااستفاده ازKeepRadiusHtmlReportGenerator(دستهبندیشده درprebuilts/r8/r8.jar) به گزارش تعاملی HTML تبدیل کن:# 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_reportsوقتی دایرکتوری داده میشود،
KeepRadiusHtmlReportGeneratorهمه*keepradius*.pbفایلها را پیمایش میکند، برای هر واحد گزارش HTML تولید میکند، وout/keep_radius_reports/keepradius.htmlرا ایجاد میکند که تعداد عناصر زنده ، تعداد عناصر نگهداریشده، و قوانین نگهداری با بیشترین شعاع در سراسر ساخت را خلاصه میکند. برای کسب اطلاعات بیشتر درباره تفسیر امتیازهای کوچکسازی، بهینهسازی، و مبهمسازی در گزارش تولیدشده، به استفاده از «تحلیلگر پیکربندی R8» مراجعه کنید.
Soong همچنین از دو متغیر محیطی تشخیصی R8 اضافی در
build/soong/java/dex.go پشتیبانی میکند:
R8_DUMP_INPUT=true:r8inputs.zipرا در فهرست واسط واحد که حاوی همه فایلهای ورودی JAR، فایلهای کتابخانه JAR، و پیکربندیهای ادغامشده ProGuard برای بازتولید مستقل R8 است مینویسد.R8_DUMP_PERFETTO_TRACE=true:r8trace.ptraceرا در دایرکتوری واسطه ماژول برای بازرسی مراحل تدوین R8 در Perfetto مینویسد.
قوانین مصرفکننده کتابخانه را اعتبارسنجی کنید
وقتی java_library یا android_library قوانین نگهداری را به مصرفکنندگان پاییندست (export_proguard_flags_files: true) صادر میکند، آن قوانین نباید شامل پرچمهای جهانی باشد که بهینهسازی را برای برنامه مصرفکننده تغییر میدهد یا غیرفعال میکند.
R8 یک تجزیهوتحلیلگر قانون نگهداری منبع باز ارائه میدهد
(ProcessKeepRules) که در ساخت پلاتفرم Android بهعنوان ابزار میزبان process-keep-rules (تعریفشده در
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>
ابزار process-keep-rules هر فایل پیکربندی را تجزیه میکند و اگر با دستوراتی مواجه شود که در قوانین مصرفکننده کتابخانه مجاز نیستند، با تشخیص خطای فایل و خط مواجه میشود. دستورات غیرمجاز شامل بهینهسازی سراسری، کوچک کردن،
یا غیرفعال کردن مبهمسازی (مثل -dontoptimize یا -dontshrink)، بستهبندی مجدد بسته و پرچمهای اصلاح دسترسی، پرچمهای تشخیصی یا نقشهبرداری، و
سطح برنامه -keepattributes میشود.
ممیزی درختان منبع با pgaudit.py
برای ممیزی کردن دایرکتوریهای منبع ساختهنشده درکنار تحلیلگرهای زمان ساخت R8، درخت پلاتفرم Android شامل دستورگان pgaudit.py در build/make/core/proguard/tools/ است. این ابزار تحلیل ایستا فایلهای Android.bp،
proguard.flags، و keep.xml را در سراسر مخزن اسکن میکند تا قوانینی را که ازقبل تحت پوشش AAPT2 یا خطوط پایه پلاتفرم جهانی، حروف عام گسترده، -dontoptimize
لغو، و قوانین نگهداری فقط آزمایشی هستند پرچمگذاری کند:
# 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
تیمهای پلاتفرم و سازنده تجهیزات اصلی میتوانند pgaudit.py را برای غربالگری سریع درخت منبع،
process-keep-rules را برای اعتبارسنجی قوانین کتابخانه صادرشده، و
R8_DUMP_KEEP_RADIUS=true را با KeepRadiusHtmlReportGenerator برای اندازهگیری
شعاع نگهداری دقیق کلاس، فیلد، و روش هر قانون در ساختار ترکیب کنند.