بهینه‌سازی برای نویسندگان کتابخانه

به‌عنوان نویسنده کتابخانه، باید مطمئن شوید که توسعه‌دهندگان برنامه می‌توانند به‌راحتی کتابخانه شما را در برنامه خود بگنجانند و درعین‌حال تجربه کاربر نهایی با کیفیت بالا را حفظ کنند. این یعنی کتابخانه شما باید با بهینه‌سازی Android (R8) سازگار باشد، بدون اینکه به راه‌اندازی اضافی ازسوی توسعه‌دهنده نیاز داشته باشد، یا اینکه در سند ذکر شود که کتابخانه ممکن است برای استفاده در Android مناسب نباشد. بسیار مهم است که کتابخانه‌هایی که برای استفاده در Android درنظر گرفته شده‌اند نباید مانع بهینه‌سازی‌های مهم برنامه شوند و باید از الزامات بهینه‌سازی اضافی پیروی کنند.

این مستندات توسعه‌دهندگان کتابخانه‌های منتشرشده را هدف قرار می‌دهد، اما ممکن است برای توسعه‌دهندگان واحدهای کتابخانه داخلی در یک برنامه بزرگ و واحدی نیز مفید باشد.

اگر توسعه‌دهنده برنامه هستید و می‌خواهید با بهینه‌سازی برنامه Android آشنا شوید، فعال کردن بهینه‌سازی برنامه را ببینید. برای آشنایی با اینکه کدام کتابخانه‌ها برای استفاده مناسب هستند، کتابخانه‌ها را عاقلانه انتخاب کنید را ببینید.

آشنایی با انواع قانون نگهداری داده

دو نوع متمایز از قانون نگهداری وجود دارد که می‌توانید در کتابخانه‌ها داشته باشید:

  • قوانین نگهداری مصرف‌کننده باید قوانینی را مشخص کند که هر آنچه را که کتابخانه منعکس می‌کند حفظ کند. اگر کتابخانه‌ای از بازتاب یا JNI برای فراخوانی کد خود یا کد تعریف‌شده توسط برنامه مشتری استفاده کند، این قوانین باید توضیح دهند که چه کدی باید حفظ شود. کتابخانه‌ها باید قوانین نگهداری مصرف‌کننده را بسته‌بندی کنند، که از همان قالب قوانین نگهداری برنامه استفاده می‌کنند. این قوانین در آرتیفکت‌های کتابخانه (AAR یا JAR) دسته‌بندی می‌شوند و هنگام استفاده از کتابخانه، به‌طور خودکار درطول بهینه‌سازی برنامه Android مصرف می‌شوند. این قوانین در فایلی که با ویژگی consumerProguardFiles در فایل build.gradle.kts (یا build.gradle) شما مشخص شده است نگهداری می‌شوند. برای کسب اطلاعات بیشتر، به نوشتن قوانین نگهداری مصرف‌کننده مراجعه کنید.
  • قوانین حفظ ساخت کتابخانه هنگام ساخت کتابخانه شما اعمال می‌شوند. این فایل‌ها فقط درصورتی لازم هستند که تصمیم بگیرید کتابخانه‌تان را در زمان ساخت به‌طور جزئی بهینه‌سازی کنید. آن‌ها باید از برداشته شدن میانای برنامه‌سازی کاربردی عمومی کتابخانه جلوگیری کنند، درغیراین‌صورت میانای برنامه‌سازی کاربردی عمومی در توزیع کتابخانه وجود نخواهد داشت، یعنی توسعه‌دهندگان برنامه نمی‌توانند از کتابخانه استفاده کنند. این قوانین در فایلی که با ویژگی proguardFiles در فایل build.gradle.kts (یا build.gradle) شما مشخص شده است نگهداری می‌شوند. برای کسب اطلاعات بیشتر، بهینه‌سازی ساخت کتابخانه AAR را ببینید.

الزامات و دستورالعمل‌های بهینه‌سازی

پیکربندی R8 در کتابخانه‌ها تأثیری سراسری بر اندازه دودویی نهایی و عملکرد برنامه مصرف‌کننده دارد. علاوه‌بر روال‌های مطلوب قانون نگهداری، نویسندگان کتابخانه باید از الزامات خاصی پیروی کنند و دستورالعمل‌های اضافی را درنظر بگیرند.

پایبندی به الزامات بهینه‌سازی

کارایی پایین در کتابخانه‌ها یکی از عوامل اصلی بزرگ شدن برنامه، هدر رفتن حافظه، راه‌اندازی‌های کند، و خطاهای ANR (برنامه پاسخ نمی‌دهد) است. کتابخانه‌ها باید از نقض الزامات زیر اجتناب کنند تا از کاهش قابل‌توجه کیفیت برنامه و تجربه کاربری جلوگیری شود.

  • بدون قوانین نگه داشتن گسترده یا در سطح بسته: کتابخانه شما نباید شامل قوانین نگه داشتن گسترده‌ای باشد که بیشتر کد موجود در کتابخانه شما یا در کتابخانه دیگری را نگه می‌دارد. قوانین نگهداری گسترده ممکن است خرابی‌ها را در کوتاه‌مدت حل کند، اما اندازه برنامه همه برنامه‌هایی را که از کتابخانه شما استفاده می‌کنند افزایش می‌دهد.

    قوانین نگهداری در سطح بسته (مثل -keep class com.mylibrary.** {*; }) را برای بسته‌های موجود در کتابخانه خودتان یا کتابخانه‌های مرجع دیگر اضافه نکنید. چنین قوانینی بهینه‌سازی این بسته‌ها را در همه برنامه‌هایی که از کتابخانه شما استفاده می‌کنند محدود می‌کند.

  • بدون قوانین جهانی نامناسب: هرگز از گزینه‌های جهانی مانند -dontobfuscate یا -allowaccessmodification استفاده نکنید.

  • استفاده از تولید کد به‌جای بازتاب درصورت امکان: درصورت امکان، از تولید کد (codegen) به‌جای بازتاب استفاده کنید. تولید کد و بازتاب هر دو رویکردهای رایجی برای اجتناب از کد متن استاندارد هنگام برنامه‌نویسی هستند، اما تولید کد با بهینه‌ساز برنامه مانند R8 سازگارتر است.

    با codegen، کد درطول فرایند ساخت تحلیل و اصلاح می‌شود. چون پس‌از زمان ترجمه هیچ تغییر عمده‌ای وجود ندارد، بهینه‌ساز می‌داند که درنهایت به چه کدی نیاز است و چه کدی را می‌توان با اطمینان حذف کرد.

    با بازتاب، کد در زمان اجرا تجزیه‌وتحلیل و دستکاری می‌شود. چون کد تا زمانی که اجرا نشود واقعاً نهایی نمی‌شود، بهینه‌ساز نمی‌داند کدام کد را می‌توان به‌طور ایمن برداشت. این نسخه احتمالاً کدی را که به‌صورت پویا ازطریق انعکاس در زمان اجرا استفاده می‌شود برمی‌دارد، که باعث ازکارافتادن برنامه برای کاربران می‌شود.

    بسیاری از کتابخانه‌های مدرن به‌جای بازتاب از codegen استفاده می‌کنند. برای نقطه ورود مشترکی که Room، Hilt، و بسیاری دیگر از آن استفاده می‌کنند، KSP را ببینید.

  • پشتیبانی از حالت کامل R8: وقتی حالت کامل R8 فعال است، کتابخانه‌تان نباید ازکار بیفتد. حالت کامل R8 حالت توصیه‌شده برای استفاده از R8 است و از زمان AGP 8.0 که در سال ۲۰۲۳ پایدار شد، حالت پیش‌فرض است. اگر کتابخانه‌تان تحت R8 ازکار می‌افتد، راه‌حل این است که نقطه ورودی JNI یا بازتاب خاص را شناسایی کنید و قانون هدف‌یابی‌شده‌ای اضافه کنید، نه اینکه کل بسته را نگه دارید.

توصیه‌های بیشتر

علاوه‌بر الزامات بهینه‌سازی، موارد زیر توصیه‌های اضافی هستند.

  • از -repackageclasses در فایل قوانین نگهداری مصرف‌کننده کتابخانه‌تان استفاده نکنید. بااین‌حال، برای بهینه‌سازی ساخت کتابخانه، می‌توانید از -repackageclasses با نام بسته داخلی، مانند <your.library.package>.internal، در فایل قوانین حفظ ساخت کتابخانه استفاده کنید. این کار می‌تواند کارایی کتابخانه شما را در برنامه‌های بهینه‌نشده بهبود بخشد. بااین‌حال، معمولاً لازم نیست، زیرا برنامه‌ها نیز باید بهینه‌سازی شوند.
  • هر مشخصه‌ای را که برای عملکرد کتابخانه‌تان نیاز دارید در فایل‌های قوانین حفظ کتابخانه‌تان اعلام کنید، حتی اگر ممکن است با مشخصه‌های تعریف‌شده در proguard-android-optimize.txt هم‌پوشانی داشته باشد.
  • اگر به مشخصه‌های زیر در توزیع کتابخانه‌تان نیاز دارید، آن‌ها را در فایل قوانین حفظ ساخت کتابخانه‌تان نگه دارید و نه در فایل قوانین حفظ مصرف‌کننده کتابخانه‌تان:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • اگر از حاشیه‌نویسی‌ها در زمان اجرا استفاده می‌شود، نویسندگان کتابخانه باید مشخصه RuntimeVisibleAnnotations را در قوانین نگهداری مصرف‌کننده خود نگه دارند.
  • نویسندگان کتابخانه نباید از گزینه‌های سراسری زیر در قوانین نگهداری مصرف‌کننده خود استفاده کنند:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

وقتی بازتاب خوب است

اگر باید از بازتاب استفاده کنید، فقط باید در یکی از موارد زیر بازتاب دهید:

  • انواع هدف‌یابی‌شده خاص (پیاده‌سازهای میانای خاص یا زیرکلاس‌ها)
  • کد بااستفاده از حاشیه‌نویسی زمان اجرای خاص

استفاده از انعکاس به این روش هزینه زمان اجرا را محدود می‌کند و امکان نوشتن قوانین نگهداری مصرف‌کننده هدفمند را فراهم می‌کند.

این شکل خاص و هدفمند از بازتاب الگویی است که می‌توانید در هر دو چارچوب Android (برای مثال، هنگام ازهم بازکردن فعالیت‌ها، نماها، و عناصر طراحی) و کتابخانه‌های AndroidX (برای مثال، هنگام ساختن WorkManager ListenableWorkers یا RoomDatabases) ببینید. درمقابل، بازتاب باز Gson برای استفاده در برنامه‌های Android مناسب نیست.

تصورات غلط رایج

چند تصور غلط رایج ممکن است باعث شود R8 را به‌درستی پیکربندی نکنید. این موارد شامل موارد زیر است:

  • درک نادرست از بهینه‌سازی‌های R8: برخلاف تصور رایج، بهینه‌سازی‌های R8 فقط به مبهم‌سازی محدود نمی‌شود، بلکه شامل کوچک کردن کد و بهینه‌سازی‌های منطقی با تکنیک‌های درون‌خطی کردن روش و ادغام کلاس نیز می‌شود. برای اطلاعات بیشتر، نمای کلی بهینه‌سازی R8 را ببینید.

  • دور زدن بهینه‌سازی کتابخانه‌های مبهم: یک خطای رایج این است که کتابخانه‌ای را از بهینه‌سازی حذف کنید، زیرا کتابخانه هنگام کامپایل شدن به AAR (بایگانی Android) یا JAR (بایگانی Java) بهینه‌سازی یا مبهم شده است. بهینه‌سازی‌ها درطول زمان ساخت کتابخانه محدود است و برنامه شما نباید با افزودن کتابخانه به قانون نگه داشتن، بهینه‌سازی کتابخانه را غیرفعال کند. برای اطلاعات بیشتر، بهینه‌سازی ساخت کتابخانه AAR را ببینید.

  • درک نادرست از گزینه -keep قانون -keep مانع از اجرای هریک از مراحل بهینه‌سازی R8 می‌شود. برای اطلاعات بیشتر، انتخاب گزینه نگهداری مناسب را ببینید.

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

برای تأیید اینکه کتابخانه شما هنگام جاسازی در برنامه مانع بهینه‌سازی بیش‌ازحد کد نمی‌شود، از نمونه کتابخانه یا برنامه آزمایش یکپارچه‌سازی استفاده کنید. R8 را فعال کنید و کیفیت کلی پیکربندی R8 برنامه را با «تحلیلگر پیکربندی R8» بررسی کنید.

اگر قوانین مصرف‌کننده شما برای محدود کردن بهینه‌سازی در بخش‌های بزرگی از کد کتابخانه شما نشان داده می‌شود، این نشان می‌دهد که قوانین مصرف‌کننده شما نیاز به پالایش دارد. قوانین مصرف‌کننده را محدود کنید و هر جنبه تحت‌تأثیر قرارگرفته کتابخانه‌تان را در برنامه‌ای که R8 در آن فعال است آزمایش کنید.

پیکربندی بسته‌بندی قانون

برای اطمینان از اینکه قوانین نگهداری مصرف‌کننده شما به‌درستی اعمال می‌شوند، باید آن‌ها را بسته به قالب کتابخانه خود به‌طور مناسب بسته‌بندی کنید.

اگر از Jetpack Keep برای اعلام بازتاب کتابخانه و استفاده از JNI استفاده می‌کنید، افزایه Gradle آن می‌تواند قوانین تولیدشده را به‌عنوان قوانین حفظ مصرف‌کننده برای شما بسته‌بندی کند.

کتابخانه‌های توصیه‌های اِعمال خودکار

برای افزودن قوانین مصرف‌کننده برای کتابخانه AAR، از گزینه consumerProguardFiles در اسکریپت ساخت ماژول کتابخانه Android استفاده کنید. برای اطلاعات بیشتر، راهنمای ایجاد کردن پیمانه‌های کتابخانه را ببینید.

کاتلین

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

شیک

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

کتابخانه‌های JAR

برای بسته‌بندی کردن قوانین با کتابخانه Kotlin یا Java که به‌صورت JAR ارسال می‌شود، فایل قوانین را در فهرست راهنمای META-INF/proguard/ JAR نهایی با هر نام فایلی قرار دهید. برای مثال، اگر کدتان در <libraryroot>/src/main/kotlin است، فایل قوانین مصرف‌کننده را در <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro قرار دهید و قوانین در مکان صحیح در JAR برونداد شما دسته‌بندی خواهند شد.

با بررسی اینکه قوانین در دایرکتوری META-INF/proguard قرار دارند، تأیید کنید که دسته‌های JAR نهایی قوانین را به‌درستی اعمال می‌کنند.

بهینه‌سازی ساخت کتابخانه AAR (پیشرفته)

به‌طور کلی، نیازی نیست که ساخت کتابخانه را مستقیماً بهینه‌سازی کنید زیرا بهینه‌سازی‌های ممکن در زمان ساخت کتابخانه بسیار محدود است. به‌عنوان توسعه‌دهنده کتابخانه، باید درباره مراحل مختلف بهینه‌سازی استدلال کنید و رفتار را هم در زمان ساخت کتابخانه و هم در زمان ساخت برنامه قبل‌از بهینه‌سازی آن کتابخانه حفظ کنید.

اگر همچنان می‌خواهید کتابخانه‌تان را در زمان ساخت بهینه‌سازی کنید، «افزایه Android Gradle» از این کار پشتیبانی می‌کند.

کاتلین

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

شیک

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

توجه داشته باشید که رفتار proguardFiles با consumerProguardFiles بسیار متفاوت است:

  • از proguardFiles در زمان ساخت استفاده می‌شود، اغلب همراه با getDefaultProguardFile("proguard-android-optimize.txt")، برای تعریف اینکه کدام بخش از کتابخانه شما باید درطول ساخت کتابخانه حفظ شود. حداقل، این میانای برنامه‌سازی کاربردی عمومی شما است.
  • ‫consumerProguardFiles درمقابل، در کتابخانه بسته‌بندی می‌شوند تا بر بهینه‌سازی‌هایی که بعداً، درطول ساخت برنامه‌ای که از کتابخانه شما استفاده می‌کند، انجام می‌شود تأثیر بگذارند.

برای مثال، اگر کتابخانه شما از انعکاس برای ساختن کلاس‌های داخلی استفاده می‌کند، ممکن است لازم باشد قوانین نگه داشتن را هم در proguardFiles و هم در consumerProguardFiles تعریف کنید.

اگر از -repackageclasses در ساخت کتابخانه‌تان استفاده می‌کنید، کلاس‌ها را در یک زیربسته درون بسته کتابخانه‌تان بسته‌بندی مجدد کنید. برای مثال، به‌جای -repackageclasses 'internal' از -repackageclasses 'com.example.mylibrary.internal' استفاده کنید.

پشتیبانی از نسخه‌های مختلف R8 (پیشرفته)

می‌توانید قوانین را برای هدف‌یابی نسخه‌های خاصی از R8 سفارشی‌سازی کنید. این کار باعث می‌شود کتابخانه شما در پروژه‌هایی که از نسخه‌های جدیدتر R8 استفاده می‌کنند به‌طور بهینه کار کند، و درعین‌حال اجازه می‌دهد قوانین موجود همچنان در پروژه‌هایی که از نسخه‌های قدیمی‌تر R8 استفاده می‌کنند استفاده شوند.

برای مشخص کردن قوانین هدفمند R8، باید آن‌ها را در META-INF/com.android.tools فهرست راهنمای داخل classes.jar فایل AAR یا در META-INF/com.android.tools فهرست راهنمای فایل JAR قرار دهید.

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

در فهرست راهنمای META-INF/com.android.tools، می‌تواند چندین زیرفهرست راهنما با نام‌هایی به شکل r8-from-<X>-upto-<Y> وجود داشته باشد تا نشان دهد قوانین برای کدام نسخه‌های R8 نوشته شده‌اند. هر زیرفهرست راهنما می‌تواند یک یا چند فایل حاوی قوانین R8 با هر نام و پسوند فایلی داشته باشد.

توجه داشته باشید که بخش‌های -from-<X> و -upto-<Y> اختیاری هستند، نسخه <Y> انحصاری است، و محدوده‌های نسخه معمولاً پیوسته هستند اما می‌توانند همپوشانی نیز داشته باشند.

برای مثال، r8،‏ r8-upto-8.0.0،‏ r8-from-8.0.0-upto-8.2.0، و r8-from-8.2.0 نام‌های دایرکتوری هستند که مجموعه‌ای از قوانین R8 هدف‌یابی‌شده را نشان می‌دهند. هر نسخه R8 می‌تواند از قوانین موجود در فهرست راهنمای r8 استفاده کند. قوانین موجود در دایرکتوری r8-from-8.0.0-upto-8.2.0 می‌تواند توسط R8 از نسخه 8.0.0 تا به‌جز نسخه 8.2.0 استفاده شود.

افزایه Android Gradle از این اطلاعات برای انتخاب همه قوانینی که می‌توانند توسط نسخه فعلی R8 استفاده شوند استفاده می‌کند. اگر کتابخانه‌ای قوانین هدفمند R8 را مشخص نکند، افزایه Android Gradle قوانین را از مکان‌های قدیمی انتخاب می‌کند (proguard.txt برای AAR یا META-INF/proguard/<ProGuard-rule-files> برای JAR).