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

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

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

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

انواع قوانین keep را درک کنید

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

  • قوانین نگهداری مصرف‌کننده باید قوانینی را مشخص کنند که هر آنچه کتابخانه روی آن منعکس می‌شود را نگه دارند. اگر یک کتابخانه از reflection یا JNI برای فراخوانی کد خود یا کدی که توسط یک برنامه کلاینت تعریف شده است استفاده کند، این قوانین باید توصیف کنند که چه کدی باید نگه داشته شود. کتابخانه‌ها باید قوانین نگهداری مصرف‌کننده را که از همان قالب قوانین نگهداری برنامه استفاده می‌کنند، بسته‌بندی کنند. این قوانین در مصنوعات کتابخانه (AAR یا JAR) قرار می‌گیرند و هنگام استفاده از کتابخانه، به طور خودکار در طول بهینه‌سازی برنامه اندروید مصرف می‌شوند. این قوانین در فایلی که با ویژگی consumerProguardFiles در فایل build.gradle.kts (یا build.gradle ) شما مشخص شده است، نگهداری می‌شوند. برای کسب اطلاعات بیشتر، به بخش «نوشتن قوانین نگهداری مصرف‌کننده» مراجعه کنید.
  • قوانین نگهداری ساخت کتابخانه هنگام ساخت کتابخانه شما اعمال می‌شوند. آنها فقط در صورتی مورد نیاز هستند که تصمیم بگیرید کتابخانه خود را در زمان ساخت تا حدی بهینه کنید. آنها باید از حذف API عمومی کتابخانه جلوگیری کنند، در غیر این صورت API عمومی در توزیع کتابخانه وجود نخواهد داشت، به این معنی که توسعه‌دهندگان برنامه نمی‌توانند از کتابخانه استفاده کنند. این قوانین در فایلی که با ویژگی proguardFiles در فایل build.gradle.kts (یا build.gradle ) شما مشخص شده است، نگهداری می‌شوند. برای کسب اطلاعات بیشتر، به Optimize AAR library build مراجعه کنید.

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

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

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

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

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

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

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

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

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

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

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

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

توصیه‌های اضافی

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

  • -repackageclasses در فایل قوانین keep مربوط به مصرف‌کننده کتابخانه خود استفاده نکنید. با این حال، برای بهینه‌سازی ساخت کتابخانه خود، می‌توانید -repackageclasses به همراه یک نام بسته داخلی، مانند <your.library.package>.internal ، در فایل قوانین keep مربوط به ساخت کتابخانه خود استفاده کنید. این می‌تواند کارایی کتابخانه شما را در برنامه‌های بهینه‌سازی نشده بهبود بخشد. با این حال، معمولاً ضروری نیست، زیرا برنامه‌ها نیز باید بهینه شوند.
  • هر ویژگی مورد نیاز برای عملکرد کتابخانه خود را در فایل‌های keep rules کتابخانه خود اعلام کنید، حتی اگر ممکن است با ویژگی‌های تعریف شده در 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

وقتی تأمل اشکالی ندارد

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

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

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

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

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

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

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

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

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

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

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

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

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

کاتلین

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

گرووی

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

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

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

با بررسی اینکه قوانین بسته‌های JAR نهایی در دایرکتوری META-INF/proguard قرار دارند، صحت آنها را تأیید کنید.

بهینه‌سازی ساخت کتابخانه 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") استفاده می‌شوند تا مشخص کنند کدام بخش از کتابخانه شما باید در طول ساخت کتابخانه نگه داشته شود. حداقل، این API عمومی شماست.
  • در مقابل consumerProguardFiles در کتابخانه بسته‌بندی می‌شوند تا بر بهینه‌سازی‌هایی که بعداً، در طول ساخت برنامه‌ای که از کتابخانه شما استفاده می‌کند، رخ می‌دهد، تأثیر بگذارند.

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

اگر -repackageclasses ‎ در ساخت کتابخانه خود استفاده می‌کنید، کلاس‌ها را به یک زیربسته درون بسته کتابخانه خود دوباره بسته‌بندی کنید. برای مثال، به جای -repackageclasses 'com.example.mylibrary.internal' ‎ از -repackageclasses '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 استفاده شوند، اما شامل نسخه 8.2.0 نمی‌شوند .

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