بهینه‌سازی کردن کد و منابع پلاتفرم با R8

سیستم ساخت پلاتفرم 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_server classpath: مدول‌هایی (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_test APK خارجی instrumentation_for: "MyApp" را تنظیم کند و مستقیماً کلاس‌ها یا روش‌های داخلی MyApp را فراخوانی کند، تغییر نام این نمادها باعث ایجاد NoSuchMethodError یا NoClassDefFoundError در زمان اجرای آزمایش می‌شود. trace_references_from را پیکربندی کنید (یا allow_traced_references با trace_references: true)، آزمایش را به‌عنوان یک APK خودابزارساز که به‌صورت ایستا به MyApp.impl پیوند داده شده است ساختاردهی کنید، یا قلاب‌های آزمایش را با @VisibleForTesting حاشیه‌نویسی کنید (به مرحله ۲: انتقال قوانین نگهداری آزمایش‌محور مراجعه کنید) تا نمادهای داخلی موردنیاز آزمایش‌ها حفظ شوند و بقیه برنامه مبهم‌سازی شود.
  • انعکاس مبتنی بر رشته و JNI: اگر پیمانه‌ای کلاس‌ها، روش‌ها، یا فیلدها را براساس نام‌های رشته تحت‌اللفظی (Class.forName، getDeclaredMethod، یا JNI FindClass و GetMethodID) بدون قوانین نگهداری یا حاشیه‌نویسی جستجو کند، مبهم‌سازی نام آن هدف‌ها را تغییر می‌دهد و جستجوهای زمان اجرا را خراب می‌کند. (توجه داشته باشید که بازتاب بدون حاشیه‌نویسی نیز تحت shrink: true و optimize: true ناموفق است.) آن نقاط ورودی را با راهنمای حفظ حاشیه‌نویسی‌ها (@UsesReflection، @UsedByReflection، یا @UsedByNative) حاشیه‌نویسی کنید تا R8 نام آن‌ها را حفظ کند و بقیه ماژول را مبهم کند.

از اصل «بدون پرچم سفارشی» پیروی کنید

ماژول پلاتفرم Android ایده‌آل هیچ فایل proguard.flags یا keep.xml سفارشی‌ای ندارد. در ساخت پلاتفرم Android، اکثریت قریب به اتفاق قوانین حفظ سفارشی زائد هستند:

  • خطوط مبنای پلاتفرم و AAPT2: اکثر نقاط ورودی قبلاً به‌طور خودکار توسط خطوط مبنای پلاتفرم جهانی (مانند @Keep، روش‌های JNI native، و @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، و فیلدهای protobuf MessageLite حفظ می‌کند.
  • build/make/core/proguard/kotlin.flags: هشدارهای بی‌خطر را برای حاشیه‌نویسی‌های متا Kotlin خاص (kotlin.Metadata و kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) بی‌صدا می‌کند و حاشیه‌نویسی‌های DebugMetadata Kotlin را در ساخت‌های نسخه پخش حذف می‌کند.
  • 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، و روش XML android: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_from or allow_traced_references): When both the target and a direct dependent module compile their own DEX (android_app, android_test, or a dexed java_library) and are connected through instrumentation_for or libs, Soong can run R8 tracereferences on the dependent module and feed the generated keep rules into the target's R8 compilation without creating a dependency cycle or requiring a manual .impl library 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 @VisibleForTesting instead of using trace_references_from or trace_references, also annotate classes passed to Mockito mock(...) or spy(...) (not just the stubbed methods), because on-device mockito-target (dexmaker-mockito) generates runtime subclasses and R8 can mark un-subclassed classes final (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 @UsesReflection automatically 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 with Class.forName from Bundle or Settings keys, 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 using GetMethodID, GetStaticMethodID, or GetFieldID.
  • @KeepForApi: روی کلاس‌ها یا اعضای میانای برنامه‌سازی کاربردی کتابخانه قرار دهید که باید هنگام کوچک شدن کتابخانه قبل‌از توزیع، دست‌نخورده باقی بمانند.
  • @Keep (androidx.annotation.Keep): از این گزینه به‌عنوان جایگزین زمانی که keepanno قابل‌اجرا نیست استفاده کنید. اعمال کردن @Keep به کلاس، کلاس و همه اعضای آن را بدون قیدوشرط حفظ می‌کند. اعمال @Keep به یک روش یا فیلد به‌عنوان یک نقطه ورود بدون شرط عمل می‌کند که عضو و کلاس حاوی آن را حفظ می‌کند، حتی اگر کلاس هرگز نمونه‌سازی نشود، درحالی‌که keepanno دسترسی‌پذیری مشروط را بیان می‌کند.

برای استفاده از حاشیه‌نویسی‌های R8 keepanno در یک ماژول Soong:

  1. افزودن "keepanno-annotations" به libs در Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. ‫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 حذف یا محدود می‌کنید، بررسی کنید که واحد به‌درستی ساخته شود، آزمون‌های واحد و ابزار دقیق را با موفقیت پشت سر بگذارد، و همه نقاط ورودی لازم را حفظ کند.

ساختن و آزمایش کردن پیمانه‌ها

  1. ماژول را به‌طور کامل بسازید تا مطمئن شوید R8 و AAPT2 بدون هشدارهای مرجع مفقود تکمیل می‌شوند:

    m <MODULE_NAME>
    
  2. آزمون‌های واحد و ابزارگری را برای ماژول بااستفاده از atest اجرا کنید:

    atest <TEST_MODULE_NAME>
    
  3. برای برنامه‌های سیستم، سرویس‌های ممتاز، یا ماژول‌های وابسته به سخت‌افزار، آزمایش‌های واسط کاربری و ابزار دقیق را در دستگاه‌های فیزیکی هدف یا در آزمایشگاه آزمایش دستگاهتان اجرا کنید تا مسیرهای بازتاب زمان اجرا، اتصال‌های 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).

برای تجزیه‌وتحلیل قوانین نگهداری برای یک ماژول پلاتفرم یا در سراسر یک ساخت:

  1. ساخت را با متغیر محیطی 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 یا قانون خط مبنای جهانی قرار گیرد، می‌توانید آن را با اطمینان حذف کنید.
    • قوانین سراسری و جهانی بسته: قوانینی که از نویسه‌های عام سراسری بسته استفاده می‌کنند یا دستورالعمل‌های پیکربندی جهانی را اعمال می‌کنند.
  2. خروجی 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 ترکیب کنند تا شعاع نگهداری دقیق کلاس، فیلد، و روش هر قانون را در ساخت اندازه‌گیری کنند.