سیستم ساخت پلاتفرم 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 بااستفاده از فراخوانیهای
mmapصفحهبندیشده براساس تقاضا،.odex،.vdex، و جدولهای منابع APK را در حافظه پردازش نگاشت میکند، دودوییهای کوچکتر خطاهای اصلی صفحه را درطول راهاندازی برنامه کاهش میدهند و کد مقیم و حافظه منابع را در سراسر فرایندها کاهش میدهند. برای اطلاعات بیشتر درباره اینکه کد کامپایلشده برنامه چگونه بر حافظه دستگاه تأثیر میگذارد، کد برنامه حافظه است را ببینید. - بهینهسازی مؤثرتر کل برنامه: محدودیتهای حفظ باریک به 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 روی هدف اجرا شود یا نه. بهطور پیشفرض روی true
برای همه هدفهای android_app تنظیم شده است.
|
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 |
هدفهای وابسته به کامپایل DEX مستقیم را فهرست میکند (متصلشده ازطریق
instrumentation_for یا libs) که بایتکد آنها
به این هدف ارجاع میدهد، R8 بهطور خودکار ردیابی و حفظ میکند (همچنین
در انواع override_android_app و
override_android_test منتشر میشود، و برای
کتابخانههای Java همراه غیروابسته هنگام جفت شدن با
تقسیم .impl پشتیبانی میشود).
|
allow_traced_references |
به اهداف وابسته به کامپایل مستقیم DEX (متصل ازطریق
instrumentation_for یا libs، مانند
آزمایشهای بین مخزنی یا فروشنده که نمیتوانند در
trace_references_from فهرست شوند) اجازه میدهد تا با تنظیم
trace_references: true، برای ردیابی ارجاعهای بایتکد خود به این هدف موافقت کنند.
|
trace_references |
وقتی روی true در یک وابسته مستقیم کامپایل DEX از
هدف با allow_traced_references: true تنظیم شود، R8
tracereferences را روی این ماژول اجرا میکند و قوانین نگهداری تولیدشده را
به ماژول هدف منتقل میکند.
|
protect_api_surface |
--protect-api-surface را به R8 منتقل میکند. وقتی روی
java_sdk_library تنظیم شود، بهطور خودکار قوانین حفظ Metalava API را در همه دامنههای API فعالشده (عمومی،
سیستم، module-lib، سرور سیستم، و آزمایش) تولید و به کتابخانه پیادهسازی
پیوست میکند.
|
کتابخانههای ایستا (android_library، java_library، و واردات AAR) نیز میتوانند
required_downstream_optimization: { enabled: true, optimize: true } را در
Android.bp تنظیم کنند تا هر هدف کامپایل DEX پاییندستی را که بهصورت ایستا
آنها را (مستقیماً یا بهصورت انتقالی) پیوند میدهد، ملزم به فعال کردن کوچکسازی R8 و بهینهسازی
کد بایت کند. Soong این ویژگی را بهطور خودکار در
androidx.compose.runtime_runtime فعال میکند تا هدفهای پیونددهنده Jetpack Compose
با 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ساخت قرار میدهد، یک درهمسازی نگاشت (--map-id-template) را در سرایند DEX جاسازی میکند، و مشخصههایSourceFileکلاس (--source-file-template) را بازنویسی میکند تاretraceبتواند ردهای پشته را بهطور خودکار نمادین کند.
obfuscate: false را (یا بهطور صریح نامهای میانای برنامهسازی کاربردی عمومی را) فقط برای موارد زیر حفظ کنید:
- کتابخانههای مشترک در bootclasspath یا
system_serverclasspath: مدولهایی (java_libraryیاjava_sdk_library) که سطح میانای برنامهسازی کاربردی خارجی را که بهطور پویا توسط مدولهای دیگر در زمان اجرا پیوند داده شده است (مانند هدفهایframework.jar،services.jar، یا<uses-library>) نمایش میدهند بایدobfuscate: falseرا حفظ کنند یا صریحاً سطح میانای برنامهسازی کاربردی خود را بااستفاده از قوانینprotect_api_surface: true،@KeepForApi، یا-keepحفظ کنند تا فراخوانندههای کامپایلشده دربرابر کدهای ناقص آنها بتوانند نامهای کلاس و عضو را در زمان اجرا حل کنند. در ماژولهایjava_sdk_library، تنظیمprotect_api_surface: trueبهطور خودکار قوانین نگهداری Metalava ProGuard را در همه دامنههای API فعال (عمومی، سیستم، کتابخانه ماژول، سرور سیستم، و آزمایش) تولید میکند و به کتابخانه پیادهسازی پیوست میکند. وقتی بهینهسازی R8 تعمیمیافته در هدفهای bootclasspath غیرقابلبهروزرسانی (framework-minus-apex،ext، وframework-location) فعال باشد، آنها همچنین شاملframework-hiddenapi-proguard-rules(تولیدشده توسطhiddenapi_to_proguard) هستند تا اعضای فهرستشده در فایلهایboot/hiddenapi/*.txtفعال را حفظ کنند (hiddenapi-max-target-o.txtحذف میشود).
قبلاز فعال کردن obfuscate: true در برنامه، پیشنیازهای انتقال زیر را
تأیید کنید:
- وابستگیهای APK آزمایش خارجی: اگر یک
android_testAPK خارجیinstrumentation_for: "MyApp"را تنظیم کند و مستقیماً کلاسها یا روشهای داخلیMyAppرا فراخوانی کند، تغییر نام این نمادها باعث ایجادNoSuchMethodErrorیاNoClassDefFoundErrorدر زمان اجرای آزمایش میشود.trace_references_fromرا پیکربندی کنید (یاallow_traced_referencesباtrace_references: true)، آزمایش را بهعنوان یک APK خودابزارساز که بهصورت ایستا بهMyApp.implپیوند داده شده است ساختاردهی کنید، یا قلابهای آزمایش را با@VisibleForTestingحاشیهنویسی کنید (به مرحله ۲: انتقال قوانین نگهداری آزمایشمحور مراجعه کنید) تا نمادهای داخلی موردنیاز آزمایشها حفظ شوند و بقیه برنامه مبهمسازی شود. - انعکاس مبتنی بر رشته و 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}) بیصدا میکند و حاشیهنویسیهایDebugMetadataKotlin را در ساختهای نسخه پخش حذف میکند.build/make/core/proguard/checknotnull.flags: فراخوانیهای کمکی بررسی null رایج (com.google.common.base.Preconditions.checkNotNullوdagger.internal.Preconditions.checkNotNull*) را با بررسیهای null بایتکد مختصر جایگزین میکند. این فایل بهطور عمدیObjects.requireNonNullرا حذف میکند تا پیامهای استثنای صریح را در مرزهای میانای برنامهسازی کاربردی چارچوب حفظ کند.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 خارج کنید:
Trace references from test or companion targets (
trace_references_fromorallow_traced_references): When both the target and a direct dependent module compile their own DEX (android_app,android_test, or a dexedjava_library) and are connected throughinstrumentation_fororlibs, Soong can run R8tracereferenceson the dependent module and feed the generated keep rules into the target's R8 compilation without creating a dependency cycle or requiring a manual.impllibrary split:آزمایشهای همان مخزن (
trace_references_from): موارد وابسته مستقیمinstrumentation_forیاlibsرا در ماژول هدف فهرست کنید (این همچنین بهطور خودکار در انواعoverride_android_appوoverride_android_testمنتشر میشود و همچنان از کتابخانههای Java همراه غیروابسته هنگام جفت شدن با تقسیم.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"], }آزمایشهای بین مخزنی یا فروشنده (
allow_traced_referencesوtrace_references): وقتی واحد هدف نمیتواند وابسته را مستقیماً درtrace_references_fromفهرست کند (مانند آزمایشها در مخزن دیگر یا درخت فروشنده)،allow_traced_references: trueرا روی واحد هدف تنظیم کنید و از هر وابسته باtrace_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, }, }بازتاب مبتنی بر رشته در آزمایشها:
tracereferencesارجاعهای مجموعه ثابت بایتکد را بازرسی میکند و نمیتواند بازتاب مبتنی بر رشته را در آزمایشها (Class.forName،getDeclaredField،getDeclaredMethod، یا کمککنندههای تزریق فیلد بازتابی) شناسایی کند. فیلدها و روشهای تولیدی را که بهصورت بازتابی به آنها دسترسی پیدا شده است با@VisibleForTesting(یا@UsedByReflection) حاشیهنویسی کنید.
پیوند دادن کتابخانه پیادهسازی به آزمایشها (
static_libs): اگر میخواهید هیچ نقطه ورودی مختص آزمایش در APK تولید حفظ نشود و نمیخواهید تغییرات آزمایش باعث شود R8 دوباره در هدف تولید راهاندازی شود، فایلهای منبع برنامه را در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اجازه میدهد صرفاً دربرابر نقاط ورودی تولید کوچک و مبهم شود.حاشیهنویسی قلابهای آزمایشی با
@VisibleForTesting: اگر یک APK آزمایشی خارجی فقط چند روش یا سازنده داخلی را در یک هدفandroid_appفراخوانی کند، میتوانید آن اعلانها را با@VisibleForTestingحاشیهنویسی کنید. خط مبنای جهانیbuild/make/core/proguard.flagsبهطور خودکار@VisibleForTestingمورد را در بستههایandroid.**،com.android.**، وcom.google.android.**بدون فایلهای.flagsسفارشی حفظ میکند. (برای ماژولهای فروشنده خارج از این فضاهای نام، ازtrace_references_from،@UsedByReflection، یا قوانین کتابخانه صادرشده استفاده کنید.) When hand-annotating with@VisibleForTestinginstead of usingtrace_references_fromortrace_references, also annotate classes passed to Mockitomock(...)orspy(...)(not just the stubbed methods), because on-devicemockito-target(dexmaker-mockito) generates runtime subclasses and R8 can mark un-subclassed classesfinal(MockitoException: Can't mock/spy class ... - final class).از کتابخانههای ایستا همپوشان اجتناب کنید یا آنها را دوباره بستهبندی کنید (
jarjar_rules): وقتی همandroid_appو هم APK آزمایشیinstrumentation_forآن بهصورت ایستا به همان کتابخانه پیوند داده میشوند (مستقیماً، مانندguava، یا بهصورت انتقالی)،tracereferencesآن کلاسهای تعریفشده در آزمایش را میبیند و هیچ قانون نگهداریای برای برنامه صادر نمیکند، و در زمان اجرا، بارکننده کلاس والد برنامه، کپی آزمایش را با کلاسهای کاهشیافته R8 خود سایه میاندازد، که باعثIllegalAccessErrorیاNoSuchMethodErrorمیشود. اگر آزمایش به نسخه بستهبندیشده خودش نیاز ندارد، نسخه تکراری را ازstatic_libsآزمایش بردارید (یا آن را ازطریقlibsپیوند دهید) تا از تداخل جلوگیری شود؛ درغیراینصورت، بستههای کتابخانه ایستا همپوشان برنامه را بااستفاده ازjarjar_rulesتحت پیشوند مختص ماژول دوباره بستهبندی کنید.
مرحله ۳: صادر کردن قوانین از کتابخانه مالک
اگر 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 را با حاشیهنویسیهای منبع از راهنمای حفظ حاشیهنویسیها جایگزین کنید (به مرجع Javadoc keepanno مراجعه کنید):
@UsesReflection: وقتی کد انجامدهنده بازتاب را کنترل میکنید، این حاشیهنویسی را ترجیح دهید. آن را در محل فراخوانی بازتاب قرار دهید تا مشخص شود به کدام کلاسها، روشها، یا فیلدهای هدف بهصورت پویا دسترسی پیدا میشود. Because@UsesReflectionautomatically encodes a precondition that the annotated call site itself is reachable, R8 prunes both the caller and the reflective target if the call site is unused.@UsedByReflection: Place on classes, methods, fields, or constructors that are instantiated or invoked reflectively by external code or libraries, such as classes loaded withClass.forNamefromBundleorSettingskeys, or plugin classes loaded across dynamic classloaders.kind(مانندKeepItemKind.CLASS_AND_METHODS)،preconditions(@KeepCondition)، و محدودیتهای پارامتر را مشخص کنید تا R8 فقط قرارداد بازتابی دقیق را حفظ کند و همچنان بتواند اعضای استفادهنشده کلاس را بهینهسازی یا هرس کند.@UsedByReflectionرا به مؤلفههای ثبتشده در مانیفست مثلJobServiceیاBroadcastReceiverاضافه نکنید، چون AAPT2 قبلاً آنها را نگه داشته است.@UsedByNative: Place on methods or fields accessed from C or C++ JNI code usingGetMethodID,GetStaticMethodID, orGetFieldID.@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را که در هرجایی از classpath پیدا میشود حفظ کند، ازجمله مؤلفههای کتابخانه استفادهنشده و فعالیتهای اشکالزدایی غیرفعالشده. - اصلاح توصیهشده: قانون را حذف کنید. AAPT2 مانیفست ادغامشده را بررسی میکند و
قوانین دقیق
-keepرا برای مؤلفههای ثبتشده تولید میکند.
قوانین حفظ کنترلکننده کلیک XML گسترده
# Don't do this:
-keepclassmembers class * {
public void *(android.view.View);
}
- چرا به عملکرد آسیب میرساند: حفظ هر روش
public void *(View)در هر کلاس در ماژول مانع از آن میشود که R8 هر روشی را که اتفاقاً یک پارامترViewرا میپذیرد، حذف یا درونخطی کند. - اصلاح توصیهشده: قانون را حذف کنید. AAPT2 فایلهای XML چیدمان را اسکن میکند و
قوانین نگهداری هدفمند برای روشهای ارجاعشده توسط
مشخصههای
android:onClickتولید میکند.
Broad View getter and setter keep rules
# 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سفارشی صرفاً برای دسترسی آزمایشی باعث میشود آن کد مبهمسازینشده باقی بماند و در نسخههای تولید حفظ شود. درحالیکه پیکربندیtrace_references_from(یاallow_traced_referencesباtrace_references: true) یا حاشیهنویسی روشها با@VisibleForTestingاز حفظ قوانین دستی-keepجلوگیری میکند و فقط آنچه را که آزمایشها ارجاع میدهند حفظ میکند، آن نمادها همچنان در باینری تولید ارسال میشوند. - اصلاح توصیهشده: با فعال کردن
trace_references_from(یاallow_traced_referencesوtrace_references: true)، قوانین آزمایش-keepسفارشی را حذف کنید، یا برنامه را در کتابخانه.implساختاربندی کنید که بااستفاده ازstatic_libsبه فایل APK آزمایشی خودابزارسازی پیوند داده شده است تا نقاط ورودی فقط آزمایشی کاملاً خارج از فایل APK تولید نگه داشته شود، همانطور که در مرحله ۲: انتقال قوانین حفظ آزمایشمحور شرح داده شده است.
Blanket resource wildcards in 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) و افزایش حجم APK و جدولresources.arscنگاشتشده میشود. - اصلاح توصیهشده:
- ارجاعهای منبع ایستا را به
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هنگام اجرای مستقیم
prebuilts/r8/r8.jarروی pre-R8 یک ماژولclasses.jarبرای تأیید تغییرات پرچم بهصورت آفلاین بدون ساخت کامل پلاتفرم، هم JARهای مسیر کلاس--libماژول (android.jar،framework.jar، وlibsمشترک) و هم قوانینproguard.optionsتولیدشده توسط AAPT2 ازAndroidManifest.xmlو XMLهای چیدمان را ارسال کنید. بدون این ورودیها، R8 نمیتواند اَبَرنوعهای چارچوب (Activity،Service،Phone،Handler) را حل کند و روشهای سلسلهمراتب را کمتر از حد لازم حفظ میکند.
تجزیهوتحلیل شعاع نگهداری و شمول قانون با R8
کامپایلر متنباز R8 شامل یک تحلیلگر Keep Radius است
(که در Android Studio و Gradle نیز بهعنوان
R8 Configuration Analyzer نمایش داده میشود) که تأثیر دقیق
هر قانون حفظ را درطول کامپایل اندازهگیری میکند. 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 بررسی کند. -
SOONG_SKIP_TRACE_REFERENCES=true: از اجرای قوانینtracereferencesپیکربندیشده توسطtrace_references_fromیاtrace_referencesصرفنظر میکند تا ساختهای حلقه داخلی محلی را هنگام تکرار روی کد آزمایشی تسریع کند.
اعتبارسنجی قوانین مصرفکننده کتابخانه
وقتی 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
تیمهای پلاتفرم و OEM میتوانند pgaudit.py را برای تریاژ سریع درخت منبع،
process-keep-rules را برای اعتبارسنجی قوانین کتابخانه صادرشده، و
R8_DUMP_KEEP_RADIUS=true را با KeepRadiusHtmlReportGenerator ترکیب کنند تا
شعاع نگهداری دقیق کلاس، فیلد، و روش هر قانون را در ساخت اندازهگیری کنند.