بهینه‌سازی کردن کد و منابع پلاتفرم با 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 جدول‌های منبع .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_server classpath: واحدها (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، یا 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}) بی‌صدا می‌کند و حاشیه‌نویسی‌های Kotlin DebugMetadata را در ساخت‌های انتشار حذف می‌کند.
  • 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، و روش XML android: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:

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

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

  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
    

با R8، شعاع نگهداری و شمول قاعده را تجزیه‌وتحلیل کنید

گردآورنده منبع باز R8 شامل تجزیه‌وتحلیلگر شعاع نگهداری است (که در Android Studio و Gradle نیز به‌عنوان تجزیه‌وتحلیلگر پیکربندی R8 آشکار می‌شود) که تأثیر دقیق هر قانون نگهداری را درطول گردآوری اندازه‌گیری می‌کند. ‫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 می‌نویسد.

قوانین مصرف‌کننده کتابخانه را اعتبارسنجی کنید

وقتی 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 برای اندازه‌گیری شعاع نگهداری دقیق کلاس، فیلد، و روش هر قانون در ساختار ترکیب کنند.