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

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

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

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

استفاده از کدژن به جای بازتاب

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

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

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

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

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

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

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

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

انواع قوانین نگهداری در کتابخانه‌ها

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

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

قوانین مربوط به حفظ مشتری را بنویسید

جدا از بهترین شیوه‌های کلی قانون «نگهداری» ، موارد زیر توصیه‌هایی ویژه برای نویسندگان کتابخانه است.

  • از قوانین سراسری نامناسب استفاده نکنید—از قرار دادن تنظیمات سراسری مانند -dontobfuscate یا -allowaccessmodification در فایل قوانین نگهداری مصرف‌کننده کتابخانه خود خودداری کنید، زیرا آنها بر همه برنامه‌هایی که از کتابخانه شما استفاده می‌کنند تأثیر می‌گذارند.
  • قوانین keep در سطح بسته مانند -keep class com.mylibrary.** { *; } را لحاظ نکنید. چنین قوانینی بهینه‌سازی را در کل کتابخانه محدود می‌کنند و بر اندازه تمام برنامه‌هایی که از کتابخانه استفاده می‌کنند تأثیر می‌گذارند.
  • -repackageclasses در فایل قوانین نگهداری مصرف‌کننده کتابخانه خود استفاده نکنید. با این حال، برای بهینه‌سازی ساخت کتابخانه خود، می‌توانید -repackageclasses با نام بسته داخلی، مانند <your.library.package>.internal ، در فایل قوانین نگهداری ساخت کتابخانه خود استفاده کنید. این می‌تواند کتابخانه شما را حتی اگر برنامه‌هایی که از آن استفاده می‌کنند بهینه نشده باشند، کارآمدتر کند، اما به طور کلی ضروری نیست زیرا برنامه‌ها نیز باید بهینه شوند. برای جزئیات بیشتر در مورد بهینه‌سازی کتابخانه‌ها، به بهینه‌سازی برای نویسندگان کتابخانه مراجعه کنید.
  • هر ویژگی مورد نیاز برای عملکرد کتابخانه خود را در فایل‌های 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
    • -overloadaggressively
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

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

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

کاتلین

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 خروجی شما باندل می‌شوند.

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

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

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

اگر هنوز می‌خواهید کتابخانه خود را در زمان ساخت بهینه کنید، این کار توسط افزونه 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) انتخاب خواهد کرد.