برای اینکه برنامه خود را تا حد امکان کوچک و سریع کنید، باید نسخه انتشار خود را با isMinifyEnabled = true
بهینه سازی و کوچک کنید.
انجام این کار کوچک کردن را فعال می کند که کد و منابع استفاده نشده را حذف می کند. مبهم سازی ، که نام کلاس ها و اعضای برنامه شما را کوتاه می کند. و بهینه سازی ، که استراتژی های تهاجمی تری را برای کاهش بیشتر اندازه و بهبود عملکرد برنامه شما اعمال می کند. این صفحه توضیح می دهد که R8 چگونه این وظایف زمان کامپایل را برای پروژه شما انجام می دهد و چگونه می توانید آنها را سفارشی کنید.
وقتی پروژه خود را با استفاده از افزونه Android Gradle نسخه 3.4.0 یا بالاتر میسازید، افزونه دیگر از ProGuard برای بهینهسازی کد در زمان کامپایل استفاده نمیکند. در عوض، این افزونه با کامپایلر R8 کار می کند تا وظایف زمان کامپایل زیر را انجام دهد:
- کوچک کردن کد (یا تکان دادن درخت): کلاسها، فیلدها، روشها و ویژگیهای استفاده نشده را از برنامه شما و وابستگیهای کتابخانهاش شناسایی و با خیال راحت حذف میکند (که آن را به ابزاری ارزشمند برای کار در حدود 64 هزار مرجع تبدیل میکند). برای مثال، اگر فقط از چند API وابسته به کتابخانه استفاده میکنید، کوچک کردن میتواند کد کتابخانهای را که برنامه شما از آن استفاده نمیکند شناسایی کند و فقط آن کد را از برنامه شما حذف کند. برای کسب اطلاعات بیشتر، به بخش نحوه کوچک کردن کد خود بروید.
- کاهش منابع: منابع استفاده نشده را از برنامه بسته بندی شده شما حذف می کند، از جمله منابع استفاده نشده در وابستگی های کتابخانه برنامه شما. این در ارتباط با کوچک کردن کد کار می کند به طوری که پس از حذف کدهای استفاده نشده، هر منبعی که دیگر به آن ارجاع داده نمی شود، می تواند با خیال راحت حذف شود. برای کسب اطلاعات بیشتر، به بخش نحوه کاهش منابع خود بروید.
- بهینه سازی: کد شما را بازرسی و بازنویسی می کند تا عملکرد زمان اجرا را بهبود بخشد و حجم فایل های DEX برنامه شما را کاهش دهد. این عملکرد زمان اجرا کد را تا 30 درصد بهبود می بخشد و به شدت راه اندازی و زمان بندی فریم را بهبود می بخشد. برای مثال، اگر R8 تشخیص دهد که شاخه
else {}
برای یک عبارت if/else داده شده هرگز گرفته نشده است، R8 کد شاخهelse {}
را حذف می کند. برای کسب اطلاعات بیشتر، به بخش بهینه سازی کد بروید. - مبهم سازی (یا کوچک سازی شناسه): نام کلاس ها و اعضا را کوتاه می کند که منجر به کاهش اندازه فایل DEX می شود. برای کسب اطلاعات بیشتر، به بخش نحوه مبهم کردن کد خود بروید.
هنگام ساخت نسخه انتشار برنامه خود، R8 می تواند پیکربندی شود تا وظایف زمان کامپایل که در بالا توضیح داده شد را برای شما انجام دهد. همچنین میتوانید وظایف خاصی را غیرفعال کنید یا رفتار R8 را از طریق فایلهای قوانین ProGuard سفارشی کنید. در واقع، R8 با تمام فایلهای قوانین ProGuard موجود شما کار میکند ، بنابراین بهروزرسانی پلاگین Android Gradle برای استفاده از R8 نباید شما را ملزم به تغییر قوانین موجود خود کند.
کوچک کردن، مبهم سازی و بهینه سازی را فعال کنید
هنگامی که از Android Studio 3.4 یا Android Gradle 3.4.0 و بالاتر استفاده می کنید، R8 کامپایلر پیش فرضی است که بایت کد جاوا پروژه شما را به فرمت DEX که روی پلتفرم اندروید اجرا می شود تبدیل می کند. با این حال، هنگامی که یک پروژه جدید با استفاده از Android Studio ایجاد می کنید، بهینه سازی کد، کوچک شدن، مبهم سازی و کد به طور پیش فرض فعال نیست. دلیلش این است که این بهینهسازیهای زمان کامپایل زمان ساخت پروژه شما را افزایش میدهند و در صورتی که کد مورد نظر را به اندازه کافی سفارشی نکنید، ممکن است اشکالاتی را ایجاد کنند.
بنابراین، بهتر است هنگام ساختن نسخه نهایی برنامه خود که قبل از انتشار آزمایش می کنید، این وظایف زمان کامپایل را فعال کنید. برای فعال کردن کوچک کردن، مبهم سازی و بهینه سازی، موارد زیر را در اسکریپت ساخت سطح پروژه خود بگنجانید.
کاتلین
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
شیار
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
فایل های پیکربندی R8
R8 از فایلهای قوانین ProGuard استفاده میکند تا رفتار پیشفرض خود را اصلاح کند و ساختار برنامهتان را بهتر بشناسد، مانند کلاسهایی که به عنوان نقاط ورودی به کد برنامه شما عمل میکنند. اگرچه میتوانید برخی از این فایلهای قوانین را تغییر دهید، برخی از قوانین ممکن است بهطور خودکار توسط ابزارهای زمان کامپایل مانند AAPT2 ایجاد شوند یا از وابستگیهای کتابخانه برنامه شما به ارث برده شوند. جدول زیر منابع فایل های قوانین ProGuard را که R8 استفاده می کند، توضیح می دهد.
منبع | مکان | توضیحات |
اندروید استودیو | <module-dir>/proguard-rules.pro | هنگامی که یک ماژول جدید با استفاده از Android Studio ایجاد می کنید، IDE یک فایل proguard-rules.pro را در فهرست اصلی آن ماژول ایجاد می کند.به طور پیش فرض، این فایل هیچ قانونی را اعمال نمی کند. بنابراین، قوانین ProGuard خود را در اینجا وارد کنید، مانند قوانین حفظ سفارشی خود. |
پلاگین اندروید Gradle | در زمان کامپایل توسط افزونه Android Gradle ایجاد شده است. | افزونه Android Gradle proguard-android-optimize.txt را ایجاد می کند که شامل قوانینی است که برای اکثر پروژه های Android مفید است و حاشیه نویسی @Keep* را فعال می کند.به طور پیش فرض، هنگام ایجاد یک ماژول جدید با استفاده از اندروید استودیو، اسکریپت ساخت سطح ماژول شامل این فایل قوانین در بیلد انتشار شما برای شما می شود. توجه: افزونه Android Gradle شامل فایلهای قوانین ProGuard از پیش تعریفشده اضافی است، اما توصیه میشود از |
وابستگی های کتابخانه | در یک کتابخانه AAR: در یک کتابخانه JAR: علاوه بر این مکانها، افزونه Android Gradle نسخه 3.6 یا بالاتر نیز از قوانین کوچک کردن هدفمند پشتیبانی میکند. | اگر یک کتابخانه AAR یا JAR با فایل قوانین خاص خود منتشر شود و شما آن کتابخانه را به عنوان یک وابستگی زمان کامپایل قرار دهید، R8 به طور خودکار آن قوانین را هنگام کامپایل پروژه شما اعمال می کند. علاوه بر قوانین معمولی ProGuard، افزونه Android Gradle نسخه 3.6 یا بالاتر نیز از قوانین کوچک کردن هدفمند پشتیبانی می کند. اینها قوانینی هستند که کوچک کننده های خاص (R8 یا ProGuard) و همچنین نسخه های کوچک کننده خاص را هدف قرار می دهند. استفاده از فایلهای قوانین که با کتابخانهها بستهبندی شدهاند، در صورتی مفید است که قوانین خاصی برای عملکرد صحیح کتابخانه مورد نیاز باشد - یعنی توسعهدهنده کتابخانه مراحل عیبیابی را برای شما انجام داده است. با این حال، باید توجه داشته باشید که از آنجایی که قوانین افزودنی هستند، برخی از قوانینی که وابستگی کتابخانه شامل آن می شود را نمی توان حذف کرد و ممکن است بر گردآوری قسمت های دیگر برنامه شما تأثیر بگذارد. به عنوان مثال، اگر یک کتابخانه شامل یک قانون برای غیرفعال کردن بهینه سازی کد باشد، آن قانون بهینه سازی را برای کل پروژه شما غیرفعال می کند. |
Android Asset Package Tool 2 (AAPT2) | پس از ساختن پروژه خود با minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt | AAPT2 بر اساس ارجاع به کلاسها در مانیفست، طرحبندیها و سایر منابع برنامه، قوانین حفظ را ایجاد میکند. به عنوان مثال، AAPT2 برای هر فعالیتی که در مانیفست برنامه خود به عنوان نقطه ورودی ثبت می کنید، یک قانون نگه داشتن دارد. |
فایل های پیکربندی سفارشی | بهطور پیشفرض، وقتی یک ماژول جدید با استفاده از Android Studio ایجاد میکنید، IDE <module-dir>/proguard-rules.pro را برای شما ایجاد میکند تا قوانین خود را اضافه کنید. | می توانید پیکربندی های اضافی اضافه کنید و R8 آنها را در زمان کامپایل اعمال می کند. |
هنگامی که ویژگی minifyEnabled
را روی true
تنظیم می کنید، R8 قوانین را از تمام منابع موجود لیست شده در بالا ترکیب می کند. این مهم است که هنگام عیبیابی با R8 به خاطر داشته باشید، زیرا وابستگیهای زمان کامپایل دیگر، مانند وابستگیهای کتابخانه، ممکن است تغییراتی را در رفتار R8 ایجاد کنند که شما از آن اطلاعی ندارید.
برای خروجی یک گزارش کامل از تمام قوانینی که R8 هنگام ساخت پروژه خود اعمال می کند، موارد زیر را در فایل proguard-rules.pro
ماژول خود قرار دهید:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
قوانین کوچک کردن هدفمند
افزونه Android Gradle نسخه 3.6 یا بالاتر از قوانین کتابخانههایی پشتیبانی میکند که کوچککنندههای خاص (R8 یا ProGuard) و همچنین نسخههای کوچککننده خاص را هدف قرار میدهند. این به توسعهدهندگان کتابخانه اجازه میدهد تا قوانین خود را برای کار بهینه در پروژههایی که از نسخههای کوچککننده جدید استفاده میکنند، تنظیم کنند، در حالی که اجازه میدهد قوانین موجود همچنان در پروژههایی با نسخههای کوچککننده قدیمیتر استفاده شوند.
برای تعیین قوانین کوچک کردن هدفمند، توسعه دهندگان کتابخانه باید آنها را در مکان های خاصی در داخل یک کتابخانه AAR یا JAR قرار دهند، همانطور که در زیر توضیح داده شده است.
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
این بدان معناست که قوانین کوچک کردن هدفمند در فهرست META-INF/com.android.tools
یک JAR یا در فهرست META-INF/com.android.tools
داخل classes.jar
یک AAR ذخیره میشوند.
در زیر آن دایرکتوری، میتوان چندین دایرکتوری با نامهایی به شکل r8-from-<X>-upto-<Y>
یا proguard-from-<X>-upto-<Y>
وجود داشته باشد تا مشخص شود کدام نسخههای کوچککننده قوانین داخل دایرکتوری ها برای آنها نوشته شده است. توجه داشته باشید که قسمت های -from-<X>
و -upto-<Y>
اختیاری هستند، نسخه <Y>
انحصاری است و محدوده های نسخه باید پیوسته باشند.
برای مثال، r8-upto-8.0.0
، r8-from-8.0.0-upto-8.2.0
و r8-from-8.2.0
مجموعه معتبری از قوانین کوچک کردن هدفمند را تشکیل می دهند. قوانین تحت پوشه r8-from-8.0.0-upto-8.2.0
توسط R8 از نسخه 8.0.0 تا نسخه 8.0.0 استفاده می شود، اما شامل نسخه 8.2.0 نمی شود .
با توجه به این اطلاعات، افزونه Android Gradle نسخه 3.6 یا بالاتر، قوانین را از فهرست های R8 منطبق انتخاب می کند. اگر یک کتابخانه قوانین کوچک کردن هدفمند را مشخص نکند، افزونه Android Gradle قوانین را از مکانهای قدیمی انتخاب میکند ( proguard.txt
برای AAR یا META-INF/proguard/<ProGuard-rules-file>
برای JAR).
توسعهدهندگان کتابخانه میتوانند برای حفظ سازگاری با افزونه Android Gradle قدیمیتر از 3.6 یا ابزارهای دیگر، قوانین هدفمند کوچک کردن یا قوانین ProGuard قدیمی را در کتابخانههای خود یا هر دو نوع را در کتابخانههای خود بگنجانند.
شامل تنظیمات اضافی
هنگامی که یک پروژه یا ماژول جدید با استفاده از Android Studio ایجاد می کنید، IDE یک فایل <module-dir>/proguard-rules.pro
را برای شما ایجاد می کند تا قوانین خود را در آن لحاظ کنید. همچنین میتوانید با افزودن آنها به ویژگی proguardFiles
در اسکریپت ساخت ماژول، قوانین اضافی را از فایلهای دیگر اضافه کنید.
به عنوان مثال، میتوانید با افزودن یک ویژگی proguardFiles
دیگر در بلوک productFlavor
مربوطه، قوانینی را اضافه کنید که مختص هر نوع ساخت هستند. فایل Gradle زیر flavor2-rules.pro
را به طعم محصول flavor2
اضافه می کند. اکنون، flavor2
از هر سه قانون ProGuard استفاده میکند، زیرا قوانین مربوط به بلوک release
نیز اعمال میشوند.
علاوه بر این، میتوانید ویژگی testProguardFiles
را اضافه کنید که فهرستی از فایلهای ProGuard را که فقط در APK آزمایشی گنجانده شدهاند را مشخص میکند:
کاتلین
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
شیار
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
کد خود را کوچک کنید
هنگامی که ویژگی minifyEnabled
روی true
تنظیم می کنید، کوچک شدن کد با R8 به طور پیش فرض فعال می شود.
کوچک کردن کد (همچنین به عنوان تکان دادن درخت شناخته می شود)، فرآیند حذف کدی است که R8 تشخیص می دهد در زمان اجرا لازم نیست. اگر برای مثال، برنامه شما دارای وابستگی های کتابخانه ای زیادی باشد اما تنها از بخش کوچکی از عملکرد آنها استفاده کند، این فرآیند می تواند اندازه برنامه شما را تا حد زیادی کاهش دهد.
برای کوچک کردن کد برنامه، R8 ابتدا تمام نقاط ورود به کد برنامه شما را بر اساس مجموعه فایلهای پیکربندی ترکیبی تعیین میکند. این نقاط ورودی شامل تمام کلاسهایی است که پلتفرم Android ممکن است برای باز کردن فعالیتها یا خدمات برنامه شما از آنها استفاده کند. با شروع از هر نقطه ورودی، R8 کد برنامه شما را بررسی می کند تا نموداری از همه روش ها، متغیرهای عضو و سایر کلاس هایی که برنامه شما ممکن است در زمان اجرا به آنها دسترسی داشته باشد بسازد. کدی که به آن نمودار متصل نیست غیرقابل دسترس تلقی می شود و ممکن است از برنامه حذف شود.
شکل 1 یک برنامه با وابستگی به کتابخانه زمان اجرا را نشان می دهد. در حین بررسی کد برنامه، R8 تشخیص می دهد که متدهای foo()
, faz()
و bar()
از نقطه ورودی MainActivity.class
قابل دسترسی هستند. با این حال، کلاس OkayApi.class
یا متد baz()
هرگز توسط برنامه شما در زمان اجرا استفاده نمی شود و R8 آن کد را هنگام کوچک کردن برنامه شما حذف می کند.
R8 نقاط ورودی را از طریق قوانین -keep
در فایل های پیکربندی R8 پروژه تعیین می کند. به این معنی که قوانین حفظ، کلاسهایی را مشخص میکنند که R8 نباید هنگام کوچک کردن برنامهتان آنها را کنار بگذارد و R8 آن کلاسها را به عنوان نقاط ورود احتمالی به برنامه شما در نظر میگیرد. افزونه Android Gradle و AAPT2 بهطور خودکار قوانین نگهداری را ایجاد میکنند که اکثر پروژههای برنامه برای شما مورد نیاز هستند، مانند فعالیتها، بازدیدها و خدمات برنامهتان. با این حال، اگر میخواهید این رفتار پیشفرض را با قوانین نگهداری اضافی سفارشی کنید، بخش نحوه سفارشیسازی کد نگهداری را بخوانید.
اگر در عوض فقط به کاهش حجم منابع برنامه خود علاقه دارید، به بخش نحوه کوچک کردن منابع خود بروید.
توجه داشته باشید که اگر یک پروژه کتابخانه کوچک شود، برنامهای که به آن کتابخانه وابسته است شامل کلاسهای کتابخانه کوچک شده است. اگر کلاسهایی در APK کتابخانه وجود ندارد، ممکن است لازم باشد قوانین نگهداری کتابخانه را تنظیم کنید. اگر در حال ساخت و انتشار یک کتابخانه با فرمت AAR هستید، فایلهای JAR محلی که کتابخانه شما به آن وابسته است، در فایل AAR کوچک نمیشوند .
کدی را که باید حفظ شود سفارشی کنید
برای اکثر مواقع، فایل قوانین پیشفرض ProGuard ( proguard-android-optimize.txt
) برای R8 کافی است تا فقط کدهای استفاده نشده را حذف کند. با این حال، تجزیه و تحلیل درست برخی از موقعیتها برای R8 دشوار است و ممکن است کدهایی را که برنامه شما واقعاً به آن نیاز دارد حذف کند. چند نمونه از مواردی که ممکن است کد را به اشتباه حذف کند عبارتند از:
- وقتی برنامه شما روشی را از رابط بومی جاوا (JNI) فراخوانی میکند.
- وقتی برنامه شما کد را در زمان اجرا جستجو می کند (مانند بازتاب)
آزمایش برنامه شما باید خطاهای ناشی از کد حذف نامناسب را نشان دهد، اما همچنین میتوانید کد حذف شده را با ایجاد گزارش کد حذف شده بررسی کنید.
برای رفع خطاها و مجبور کردن R8 به نگه داشتن کد خاص، یک خط -keep
را در فایل قوانین ProGuard اضافه کنید. به عنوان مثال:
-keep public class MyClass
از طرف دیگر، میتوانید حاشیهنویسی @Keep
به کدی که میخواهید نگه دارید اضافه کنید. افزودن @Keep
به یک کلاس کل کلاس را همانطور که هست نگه می دارد. افزودن آن به یک متد یا فیلد، متد/فیلد (و نام آن) و همچنین نام کلاس را دست نخورده نگه میدارد. توجه داشته باشید که این حاشیهنویسی فقط در صورت استفاده از کتابخانه Annotations AndroidX و زمانی که فایل قوانین ProGuard را که با افزونه Android Gradle بستهبندی شده است، در دسترس قرار میدهید، همانطور که در بخش نحوه فعال کردن کوچک کردن توضیح داده شده است.
هنگام استفاده از گزینه -keep
باید ملاحظات زیادی را رعایت کنید. برای اطلاعات بیشتر در مورد سفارشی کردن فایل قوانین خود، راهنمای ProGuard را بخوانید. بخش عیبیابی سایر مشکلات رایجی را که ممکن است هنگام حذف کدتان با آن مواجه شوید، تشریح میکند.
کتابخانه های بومی را حذف کنید
بهطور پیشفرض، کتابخانههای کد بومی در نسخههای انتشار برنامه شما حذف میشوند. این حذف شامل حذف جدول نمادها و اشکال زدایی اطلاعات موجود در کتابخانه های بومی مورد استفاده برنامه شما است. حذف کتابخانه های کد بومی منجر به صرفه جویی قابل توجهی در اندازه می شود. با این حال، تشخیص خرابی در کنسول Google Play به دلیل اطلاعات از دست رفته (مانند نام کلاس و عملکرد) غیرممکن است.
پشتیبانی از خرابی بومی
کنسول Google Play خرابی های بومی تحت حیاتی اندروید را گزارش می کند. با چند مرحله، می توانید یک فایل نمادهای اشکال زدایی بومی برای برنامه خود ایجاد و آپلود کنید. این فایل ردیابی پشته تصادف بومی نمادین (شامل نام کلاس و توابع) را در حیاتی اندروید فعال میکند تا به شما کمک کند برنامه خود را در مرحله تولید اشکالزدایی کنید. این مراحل بسته به نسخه پلاگین Android Gradle مورد استفاده در پروژه شما و خروجی ساخت پروژه شما متفاوت است.
پلاگین Android Gradle نسخه 4.1 یا بالاتر
اگر پروژه شما یک Android App Bundle می سازد، می توانید به طور خودکار فایل نمادهای اشکال زدایی بومی را در آن قرار دهید. برای گنجاندن این فایل در نسخههای انتشار، موارد زیر را به فایل build.gradle.kts
برنامه خود اضافه کنید:
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
سطح نماد اشکال زدایی را از موارد زیر انتخاب کنید:
- از
SYMBOL_TABLE
برای دریافت نام توابع در ردپای نمادین پشته کنسول Play استفاده کنید. این سطح از سنگ قبرها پشتیبانی می کند. -
FULL
برای دریافت نام توابع، فایلها و شماره خطوط در ردیابی پشته نمادین کنسول Play استفاده کنید.
اگر پروژه شما یک APK میسازد، از تنظیمات ساخت build.gradle.kts
که قبلاً نشان داده شده بود استفاده کنید تا فایل نمادهای اشکال زدایی بومی را جداگانه ایجاد کنید. فایل نمادهای اشکال زدایی بومی را به صورت دستی در کنسول Google Play آپلود کنید . به عنوان بخشی از فرآیند ساخت، افزونه Android Gradle این فایل را در محل پروژه زیر خروجی میدهد:
app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip
پلاگین Android Gradle نسخه 4.0 یا بالاتر (و سایر سیستم های ساخت)
به عنوان بخشی از فرآیند ساخت، پلاگین Android Gradle یک کپی از کتابخانههای جدا نشده را در فهرست پروژه نگه میدارد. این ساختار دایرکتوری شبیه به زیر است:
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
محتویات این دایرکتوری را فشرده کنید:
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
فایل
symbols.zip
را به صورت دستی در کنسول Google Play آپلود کنید .
منابع خود را کوچک کنید
کوچک کردن منابع فقط در رابطه با کوچک کردن کد کار می کند. پس از حذف کدهای کوچککننده، همه کدهای استفاده نشده را حذف میکند، کوچککننده منابع میتواند شناسایی کند که برنامه هنوز از چه منابعی استفاده میکند. این امر مخصوصاً زمانی صادق است که شما کتابخانههای کدی را که شامل منابع هستند اضافه میکنید—شما باید کد کتابخانه استفادهنشده را حذف کنید تا منابع کتابخانه بیارجاع شوند و در نتیجه توسط کوچککننده منبع قابل حذف باشند.
برای فعال کردن کوچک شدن منابع، ویژگی shrinkResources
را در اسکریپت ساخت خود روی true
تنظیم کنید (در کنار minifyEnabled
برای کوچک کردن کد). به عنوان مثال:
کاتلین
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
شیار
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
اگر قبلاً برنامه خود را با استفاده از minifyEnabled
برای کوچک کردن کد نساختهاید، قبل از فعال کردن shrinkResources
آن را امتحان کنید، زیرا ممکن است لازم باشد فایل proguard-rules.pro
خود را ویرایش کنید تا کلاسها یا روشهایی را که ایجاد یا فراخوانی میشوند به صورت پویا قبل از شروع حذف نگه دارید. منابع
شخصی سازی کنید که کدام منابع را نگه دارید
اگر منابع خاصی وجود دارد که میخواهید آن را نگه دارید یا کنار بگذارید، یک فایل XML در پروژه خود با تگ <resources>
ایجاد کنید و هر منبع را در ویژگی tools:keep
و هر منبع را در ویژگی tools:discard
مشخص کنید. هر دو ویژگی فهرستی از نام منابع را میپذیرند که با کاما جدا شدهاند. می توانید از کاراکتر ستاره به عنوان کارت وحشی استفاده کنید.
به عنوان مثال:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
این فایل را در منابع پروژه خود ذخیره کنید، به عنوان مثال، در res/raw/my.package.keep.xml
. بیلد این فایل را در برنامه شما بسته بندی نمی کند.
توجه: حتما از یک نام منحصر به فرد برای فایل keep
استفاده کنید. وقتی کتابخانههای مختلف با هم مرتبط میشوند، قوانین نگهداری آنها در غیر این صورت با هم تضاد میکنند، و باعث ایجاد مشکلات بالقوه با قوانین نادیده گرفته شده یا منابع نگهداری غیر ضروری میشود.
مشخص کردن منابعی که باید حذف شوند ممکن است احمقانه به نظر برسد، اما این می تواند هنگام استفاده از انواع ساخت مفید باشد. به عنوان مثال، ممکن است تمام منابع خود را در دایرکتوری مشترک پروژه قرار دهید، سپس یک فایل my.package.build.variant.keep.xml
متفاوت برای هر نوع ساخت ایجاد کنید، زمانی که می دانید به نظر می رسد یک منبع داده شده در کد استفاده می شود (و بنابراین توسط شرینکر حذف نمی شود) اما می دانید که در واقع برای نوع ساخت داده شده استفاده نخواهد شد. همچنین این امکان وجود دارد که ابزارهای ساخت به اشتباه منبع مورد نیاز را شناسایی کرده باشند، که این امکان وجود دارد زیرا کامپایلر شناسه های منبع را به صورت درون خطی اضافه می کند و سپس تحلیلگر منبع ممکن است تفاوت بین یک منبع ارجاع شده واقعی و یک مقدار صحیح در کد را که اتفاق می افتد نداند. ارزش یکسانی دارند
بررسی های مرجع دقیق را فعال کنید
به طور معمول، کوچک کننده منبع می تواند به دقت تعیین کند که آیا از یک منبع استفاده شده است یا خیر. با این حال، اگر کد شما با Resources.getIdentifier()
تماس بگیرد (یا اگر هر یک از کتابخانه های شما این کار را انجام دهد—کتابخانه AppCompat این کار را می کند)، به این معنی است که کد شما نام منابع را بر اساس رشته های تولید شده به صورت پویا جستجو می کند. وقتی این کار را انجام میدهید، کوچککننده منبع بهطور پیشفرض رفتار تدافعی دارد و همه منابع را با قالب نام منطبق بهعنوان بالقوه استفاده شده و غیرقابل حذف علامتگذاری میکند.
به عنوان مثال، کد زیر باعث می شود که تمام منابع با پیشوند img_
به عنوان استفاده شده علامت گذاری شوند.
کاتلین
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
جاوا
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
کوچککننده منبع همچنین تمام ثابتهای رشتهای در کد شما و همچنین منابع مختلف res/raw/
را بررسی میکند و به دنبال URLهای منبع در قالبی مشابه file:///android_res/drawable//ic_plus_anim_016.png
میگردد. اگر رشتههایی مانند این یا رشتههای دیگری را پیدا کند که به نظر میرسد میتوان از آنها برای ساخت نشانیهای اینترنتی مانند این استفاده کرد، آنها را حذف نمیکند.
اینها نمونه هایی از حالت انقباض ایمن هستند که به طور پیش فرض فعال است. با این حال، میتوانید این مدیریت «ایمنتر از متأسفانه» را خاموش کنید و مشخص کنید که کوچککننده منابع فقط منابعی را نگه میدارد که مطمئن است استفاده میشوند. برای انجام این کار، shrinkMode
در فایل keep.xml
به صورت strict
گیر کنید:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
اگر حالت انقباض دقیق را فعال کنید و کد شما همچنین به منابعی با رشته های تولید شده به صورت پویا ارجاع می دهد، همانطور که در بالا نشان داده شده است، باید به صورت دستی آن منابع را با استفاده از ویژگی tools:keep
نگه دارید.
منابع جایگزین استفاده نشده را حذف کنید
کوچککننده منابع Gradle فقط منابعی را حذف میکند که کد برنامه شما به آنها ارجاع نمیدهد، به این معنی که منابع جایگزین را برای پیکربندیهای مختلف دستگاه حذف نمیکند. در صورت لزوم، میتوانید از ویژگی resConfigs
افزونه Android Gradle برای حذف فایلهای منبع جایگزینی که برنامه شما به آن نیازی ندارد، استفاده کنید.
برای مثال، اگر از کتابخانهای استفاده میکنید که شامل منابع زبانی است (مانند AppCompat یا Google Play Services)، برنامه شما شامل تمام رشتههای زبان ترجمهشده برای پیامهای موجود در آن کتابخانهها میشود، چه بقیه برنامه شما به همان زبانها ترجمه شده باشد یا نه اگر میخواهید فقط زبانهایی را نگه دارید که برنامه شما به طور رسمی از آنها پشتیبانی میکند، میتوانید آن زبانها را با استفاده از ویژگی resConfig
مشخص کنید. هر منبعی برای زبان های مشخص نشده حذف می شود.
قطعه زیر نشان می دهد که چگونه منابع زبان خود را فقط به انگلیسی و فرانسوی محدود کنید:
کاتلین
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
شیار
android { defaultConfig { ... resConfigs "en", "fr" } }
هنگام انتشار یک برنامه با استفاده از قالب Android App Bundle، به طور پیشفرض فقط زبانهای پیکربندی شده در دستگاه کاربر هنگام نصب برنامه دانلود میشوند. به طور مشابه، تنها منابعی که با تراکم صفحه دستگاه مطابقت دارند و کتابخانه های بومی مطابق با ABI دستگاه در دانلود گنجانده شده است. برای اطلاعات بیشتر به پیکربندی Android App Bundle مراجعه کنید.
برای برنامههای قدیمی که با APK منتشر میشوند (که قبل از آگوست 2021 ایجاد شدهاند)، میتوانید با ساختن چندین APK که هر کدام پیکربندی دستگاه متفاوتی را هدف قرار میدهند، تراکم صفحه یا منابع ABI را در APK خود تنظیم کنید.
ادغام منابع تکراری
بهطور پیشفرض، Gradle منابعی با نام یکسان را ادغام میکند. این رفتار توسط ویژگی shrinkResources
کنترل نمیشود و نمیتوان آن را غیرفعال کرد، زیرا لازم است وقتی چندین منبع با نامی که کد شما جستجو میشود مطابقت دارند از خطا جلوگیری شود.
ادغام منابع تنها زمانی اتفاق می افتد که دو یا چند فایل نام، نوع و واجد شرایط منبع یکسانی را به اشتراک بگذارند. Gradle انتخاب می کند که کدام فایل را بهترین انتخاب در بین موارد تکراری می داند (بر اساس ترتیب اولویت که در زیر توضیح داده شده است) و فقط آن یک منبع را برای توزیع در آرتیفکت نهایی به AAPT ارسال می کند.
Gradle به دنبال منابع تکراری در مکانهای زیر میگردد:
- منابع اصلی، مرتبط با مجموعه منبع اصلی، معمولاً در
src/main/res/
قرار دارند. - پوشش های مختلف، از نوع ساخت و طعم های ساخت.
- وابستگی های پروژه کتابخانه
Gradle منابع تکراری را به ترتیب اولویت آبشاری زیر ادغام می کند:
وابستگی ها → اصلی → طعم ساخت → نوع ساخت
به عنوان مثال، اگر یک منبع تکراری هم در منابع اصلی و هم در طعم ساخت ظاهر می شود، Gradle منبعی را در طعم ساخت انتخاب می کند.
اگر منابع یکسان در مجموعه منبع یکسان ظاهر شوند، Gradle نمی تواند آنها را ادغام کند و یک خطای ادغام منابع منتشر می کند. اگر چندین مجموعه منبع را در ویژگی sourceSet
فایل build.gradle.kts
خود تعریف کنید، ممکن است اتفاق بیفتد - برای مثال اگر هر دو src/main/res/
و src/main/res2/
دارای منابع یکسان باشند.
کد خود را مبهم کنید
هدف از مبهم سازی کاهش اندازه برنامه شما با کوتاه کردن نام کلاس ها، روش ها و فیلدهای برنامه شما است. مثال زیر نمونه ای از مبهم سازی با استفاده از R8 است:
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
در حالی که مبهم سازی کد را از برنامه شما حذف نمی کند، صرفه جویی در اندازه قابل توجهی را می توان در برنامه هایی با فایل های DEX مشاهده کرد که بسیاری از کلاس ها، روش ها و فیلدها را فهرست می کنند. با این حال، از آنجایی که مبهم سازی قسمتهای مختلف کد شما را تغییر نام میدهد، وظایف خاصی مانند بررسی ردپای پشته، به ابزارهای اضافی نیاز دارد. برای درک stacktrace خود پس از مبهم کردن، بخش نحوه رمزگشایی یک stack trace مبهم را بخوانید.
بهعلاوه، اگر کد شما متکی به نامگذاری قابل پیشبینی برای روشها و کلاسهای برنامهتان است—مثلاً هنگام استفاده از بازتاب، باید آن امضاها را بهعنوان نقاط ورودی در نظر بگیرید و قوانین حفظ را برای آنها مشخص کنید، همانطور که در بخش نحوه سفارشی کردن کد توضیح داده شد. نگه داشتن . این قوانین حفظ به R8 می گویند که نه تنها آن کد را در DEX نهایی برنامه شما نگه دارد، بلکه نام اصلی خود را نیز حفظ کند.
رمزگشایی یک رد پشته مبهم
بعد از اینکه R8 کد شما را مبهم کرد، درک ردیابی پشته دشوار است (اگر غیرممکن نباشد) زیرا ممکن است نام کلاس ها و متدها تغییر کرده باشند. برای به دست آوردن ردیابی پشته اصلی باید ردیابی پشته را دوباره دنبال کنید.
بهینه سازی کد
به منظور بهینه سازی برنامه شما حتی بیشتر، R8 کد شما را در سطح عمیق تری بازرسی می کند تا کدهای استفاده نشده بیشتری را حذف کند یا، در صورت امکان، کد شما را بازنویسی کند تا کمتر پرمخاطب باشد. در زیر چند نمونه از این بهینه سازی ها آورده شده است:
- اگر کد شما هرگز شاخه
else {}
را برای یک عبارت if/else نمی گیرد، R8 ممکن است کد شعبهelse {}
را حذف کند. - اگر کد شما یک روش را فقط در چند مکان فراخوانی میکند، R8 ممکن است روش را حذف کرده و آن را در چند سایت تماس وارد کند.
- اگر R8 تشخیص دهد که یک کلاس فقط یک زیر کلاس منحصر به فرد دارد، و خود کلاس نمونه سازی نشده است (به عنوان مثال، یک کلاس پایه انتزاعی فقط توسط یک کلاس پیاده سازی مشخص استفاده می شود)، آنگاه R8 می تواند دو کلاس را ترکیب کرده و یک کلاس را از برنامه حذف کند. .
- برای کسب اطلاعات بیشتر، پست های وبلاگ بهینه سازی R8 توسط جیک وارتون را بخوانید.
R8 به شما اجازه نمی دهد که بهینه سازی های گسسته را غیرفعال یا فعال کنید، یا رفتار یک بهینه سازی را تغییر دهید. در واقع، R8 قوانین ProGuard را که سعی در اصلاح بهینهسازیهای پیشفرض دارند، مانند -optimizations
و -optimizationpasses
را نادیده میگیرد. این محدودیت مهم است زیرا با ادامه بهبود R8، حفظ یک رفتار استاندارد برای بهینهسازی به تیم اندروید استودیو کمک میکند تا به راحتی هر مشکلی را که ممکن است با آن مواجه شوید عیبیابی و حل کند.
توجه داشته باشید که فعال کردن بهینه سازی، ردیابی پشته را برای برنامه شما تغییر می دهد. برای مثال، درونسازی، فریمهای پشته را حذف میکند. برای یادگیری نحوه به دست آوردن ردیابی پشته اصلی به بخش ردیابی مجدد مراجعه کنید.
تاثیر بر عملکرد زمان اجرا
اگر کوچک کردن، مبهم سازی و بهینهسازی همگی فعال باشند، R8 عملکرد زمان اجرا کد (از جمله زمان راهاندازی و فریم در رشته رابط کاربر) را تا 30 درصد بهبود میبخشد. غیرفعال کردن هر یک از این موارد، مجموعه بهینهسازیهایی را که R8 استفاده میکند، به شدت محدود میکند.
اگر R8 فعال است، برای عملکرد راهاندازی بهتر نیز باید نمایههای راهاندازی ایجاد کنید .
بهینه سازی های تهاجمی تر را فعال کنید
R8 شامل مجموعه ای از بهینه سازی های اضافی است (که به آن "حالت کامل" گفته می شود) که باعث می شود رفتار متفاوتی با ProGuard داشته باشد. این بهینهسازیها بهطور پیشفرض از پلاگین Android Gradle نسخه 8.0.0 فعال هستند.
می توانید این بهینه سازی های اضافی را با قرار دادن موارد زیر در فایل gradle.properties
پروژه خود غیرفعال کنید:
android.enableR8.fullMode=false
از آنجایی که بهینهسازیهای اضافی باعث میشود R8 متفاوت از ProGuard رفتار کند، ممکن است از شما بخواهد که قوانین ProGuard اضافی را برای جلوگیری از مشکلات زمان اجرا در صورتی که از قوانین طراحیشده برای ProGuard استفاده میکنید، اضافه کنید. به عنوان مثال، بگویید که کد شما از طریق Java Reflection API به یک کلاس ارجاع می دهد. وقتی از "حالت کامل" استفاده نمیکنید ، R8 فرض میکند که شما قصد دارید اشیاء آن کلاس را در زمان اجرا بررسی و دستکاری کنید - حتی اگر کد شما واقعاً این کار را نمیکند - و به طور خودکار کلاس و مقدار اولیه استاتیک آن را نگه میدارد.
با این حال، هنگام استفاده از "حالت کامل"، R8 این فرض را نمیکند و اگر R8 ادعا کند که در غیر این صورت کد شما هرگز از کلاس در زمان اجرا استفاده نمیکند، کلاس را از DEX نهایی برنامه شما حذف میکند. یعنی اگر میخواهید کلاس و استاتیک اولیه آن را حفظ کنید، باید یک قانون keep را در فایل قوانین خود قرار دهید تا این کار را انجام دهید.
اگر هنگام استفاده از "حالت کامل" R8 با مشکلی مواجه شدید، برای راه حل احتمالی به صفحه پرسش و پاسخ R8 مراجعه کنید. اگر نمی توانید مشکل را حل کنید، لطفاً یک اشکال را گزارش کنید .
ردیابی مجدد stacktraces
کد پردازش شده توسط R8 به روشهای مختلفی تغییر میکند که میتواند درک ردپای پشته را سختتر کند، زیرا ردیابی پشته دقیقاً با کد منبع مطابقت ندارد. این می تواند در مورد تغییرات در شماره خطوط زمانی که اطلاعات اشکال زدایی نگهداری نمی شود. این می تواند به دلیل بهینه سازی هایی مانند inlining و outlining باشد. بزرگترین عامل مبهم سازی است که در آن حتی کلاس ها و متدها نیز نام ها را تغییر می دهند.
برای بازیابی پشته اصلی، R8 ابزار خط فرمان retrace را ارائه میکند که با بسته ابزار خط فرمان همراه است.
برای پشتیبانی از ردیابی مجدد ردیابی پشته برنامه خود، باید با افزودن قوانین زیر به فایل proguard-rules.pro
ماژول خود، اطمینان حاصل کنید که ساخت اطلاعات کافی برای ردیابی مجدد را حفظ می کند:
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
ویژگی LineNumberTable
اطلاعات موقعیتی را در روش هایی حفظ می کند که این موقعیت ها در ردیابی پشته چاپ می شوند. ویژگی SourceFile
تضمین میکند که تمام زمانهای اجرا بالقوه واقعاً اطلاعات موقعیتی را چاپ میکنند. دستور -renamesourcefileattribute
نام فایل منبع در stack traces را فقط به SourceFile
تنظیم می کند. نام فایل منبع اصلی واقعی هنگام ردیابی مجدد لازم نیست زیرا فایل نگاشت حاوی فایل منبع اصلی است.
R8 هر بار که اجرا میشود یک فایل mapping.txt
ایجاد میکند که حاوی اطلاعات مورد نیاز برای ترسیم ردپای پشتهها به دنبالههای پشته اصلی است. Android Studio فایل را در فهرست <module-name> /build/outputs/mapping/ <build-type> /
ذخیره می کند.
هنگام انتشار برنامه خود در Google Play، می توانید فایل mapping.txt
را برای هر نسخه از برنامه خود آپلود کنید. هنگام انتشار با استفاده از بسته نرم افزاری Android، این فایل به طور خودکار به عنوان بخشی از محتوای بسته برنامه گنجانده می شود. سپس Google Play ردیابی پشته های ورودی از مشکلات گزارش شده توسط کاربر را مجدداً ردیابی می کند تا بتوانید آنها را در Play Console مرور کنید. برای اطلاعات بیشتر، به مقاله مرکز راهنمایی درباره نحوه رفع ابهام از ردیابی پشته خرابی مراجعه کنید.
عیب یابی با R8
این بخش برخی از استراتژیها را برای عیبیابی مشکلات هنگام فعال کردن کوچک کردن، مبهمسازی و بهینهسازی با استفاده از R8 توضیح میدهد. اگر در زیر راهحلی برای مشکل خود پیدا نکردید، صفحه پرسشهای متداول R8 و راهنمای عیبیابی ProGuard را نیز بخوانید.
گزارشی از کد حذف شده (یا نگهداری شده) ایجاد کنید
برای کمک به عیب یابی برخی از مشکلات R8، دیدن گزارشی از تمام کدهایی که R8 از برنامه شما حذف کرده است، ممکن است مفید باشد. برای هر ماژولی که میخواهید این گزارش را برای آن ایجاد کنید، -printusage <output-dir>/usage.txt
را به فایل قوانین سفارشی خود اضافه کنید. وقتی R8 را فعال میکنید و برنامه خود را میسازید، R8 گزارشی با مسیر و نام فایلی که شما مشخص کردهاید را خروجی میدهد. گزارش کد حذف شده شبیه به زیر است:
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
اگر در عوض میخواهید گزارشی از نقاط ورودی که R8 از قوانین حفظ پروژه خود تعیین میکند، مشاهده کنید، -printseeds <output-dir>/seeds.txt
را در فایل قوانین سفارشی خود قرار دهید. وقتی R8 را فعال میکنید و برنامه خود را میسازید، R8 گزارشی با مسیر و نام فایلی که شما مشخص کردهاید را خروجی میدهد. گزارش نقاط ورودی نگهداری شده مشابه موارد زیر است:
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
عیب یابی کاهش منابع
وقتی منابع را کوچک می کنید، Build پنجره خلاصه ای از منابع حذف شده از برنامه را نشان می دهد. (ابتدا باید روی Toggle View کلیک کنید در سمت چپ پنجره برای نمایش خروجی متن دقیق از Gradle.) به عنوان مثال:
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
Gradle همچنین یک پرونده تشخیصی به نام resources.txt
در <module-name>/build/outputs/mapping/release/
(همان پوشه با پرونده های خروجی Proguard) ایجاد می کند. این پرونده شامل جزئیاتی از جمله منابع منابع دیگر منابع و منابع استفاده می شود.
به عنوان مثال ، برای یافتن اینکه چرا @drawable/ic_plus_anim_016
هنوز در برنامه شما است ، پرونده resources.txt
را باز کرده و نام پرونده را جستجو کنید. ممکن است متوجه شوید که از منبع دیگری به شرح زیر است:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
اکنون باید بدانید که چرا @drawable/add_schedule_fab_icon_anim
قابل دستیابی است - و اگر به سمت بالا جستجو کنید ، می فهمید که این منبع در زیر "منابع قابل دسترسی:" ذکر شده است: ". این بدان معنی است که یک مرجع کد به add_schedule_fab_icon_anim
(یعنی شناسه R.Drawable آن در کد قابل دسترسی پیدا شده است) وجود دارد.
اگر از بررسی دقیق استفاده نمی کنید ، اگر ثابت های رشته ای وجود داشته باشد که به نظر می رسد ممکن است برای ساخت نام منابع برای منابع دینامیکی استفاده شود ، می توان شناسه منابع را به صورت قابل دسترسی مشخص کرد. در این حالت ، اگر خروجی ساخت را برای نام منابع جستجو می کنید ، ممکن است پیامی مانند این را پیدا کنید:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
اگر یکی از این رشته ها را می بینید و مطمئن هستید که از رشته برای بارگیری منابع داده شده به صورت پویا استفاده نمی شود ، می توانید از tools:discard
برای اطلاع رسانی به سیستم ساخت برای حذف آن ، همانطور که در بخش در مورد چگونگی توضیح توضیح داده شده است. از منابع برای نگهداری استفاده کنید .
برای اینکه برنامه خود را تا حد امکان کوچک و سریع انجام دهید ، باید ساخت و ساز انتشار خود را با isMinifyEnabled = true
بهینه و کوچک کنید.
انجام این کار باعث کوچک شدن می شود ، که کد و منابع بلااستفاده را حذف می کند. مبهم ، که نام کلاس ها و اعضای برنامه شما را کوتاه می کند. و بهینه سازی ، که استراتژی های تهاجمی تری برای کاهش بیشتر اندازه و بهبود عملکرد برنامه شما اعمال می شود. در این صفحه نحوه عملکرد R8 این وظایف زمان کامپایل برای پروژه شما و نحوه تنظیم آنها را شرح می دهد.
هنگامی که پروژه خود را با استفاده از افزونه Android Gradle 3.4.0 یا بالاتر می سازید ، این افزونه دیگر از Proguard برای انجام بهینه سازی کد زمان کامپایل استفاده نمی کند. در عوض ، این افزونه با کامپایلر R8 کار می کند تا وظایف زمان کامپایل زیر را انجام دهد:
- کوچک شدن کد (یا لرزش درخت): کلاس ها ، زمینه ها ، روش ها و ویژگی های استفاده نشده از برنامه و وابستگی های کتابخانه ای آن را تشخیص داده و با خیال راحت از بین می برد (آن را به یک ابزار ارزشمند برای کار در حدود حد مرجع 64K تبدیل می کند). به عنوان مثال ، اگر فقط از چند API وابسته به کتابخانه استفاده می کنید ، کوچک شدن می تواند کد کتابخانه ای را که برنامه شما از آن استفاده نمی کند شناسایی کرده و فقط آن کد را از برنامه خود حذف کنید. برای کسب اطلاعات بیشتر ، در مورد نحوه کوچک کردن کد خود به بخش بروید.
- کاهش منابع: منابع بلااستفاده را از برنامه بسته بندی شده خود ، از جمله منابع استفاده نشده در وابستگی های کتابخانه برنامه خود حذف می کند. این در ارتباط با کوچک کردن کد کار می کند به طوری که پس از حذف کدهای استفاده نشده، هر منبعی که دیگر به آن ارجاع داده نمی شود، می تواند با خیال راحت حذف شود. برای کسب اطلاعات بیشتر ، به بخش چگونگی کوچک کردن منابع خود بروید.
- بهینه سازی: برای بهبود عملکرد زمان اجرا و کاهش بیشتر اندازه پرونده های DEX برنامه خود ، کد خود را بازرسی و بازنویسی می کند. این عملکرد زمان اجرا کد را تا 30 ٪ بهبود می بخشد و به طرز چشمگیری راه اندازی و زمان بندی فریم را بهبود می بخشد. به عنوان مثال ، اگر R8 تشخیص دهد که شعبه
else {}
برای بیانیه IF/Else هرگز گرفته نشده است ، R8 کد را برای شاخهelse {}
حذف می کند. برای کسب اطلاعات بیشتر ، به بخش بهینه سازی کد بروید. - انسداد (یا استخراج شناسه): نام کلاس ها و اعضا را کوتاه می کند ، که منجر به کاهش اندازه پرونده DEX می شود. برای کسب اطلاعات بیشتر ، به بخش مربوط به نحوه انسداد کد خود بروید.
هنگام ساخت نسخه انتشار برنامه خود ، R8 را می توان پیکربندی کرد تا وظایف زمان کامپایل را که در بالا برای شما توضیح داده شده است ، انجام دهد. همچنین می توانید وظایف خاصی را غیرفعال کنید یا رفتار R8 را از طریق پرونده های Proguard Rules تنظیم کنید. در حقیقت ، R8 با تمام پرونده های موجود در Proguard Rules شما کار می کند ، بنابراین به روزرسانی افزونه Android Gradle برای استفاده از R8 نباید شما را ملزم به تغییر قوانین موجود کند.
کوچک کردن، مبهم سازی و بهینه سازی را فعال کنید
هنگامی که از Android Studio 3.4 یا Android Gradle Plugin 3.4.0 و بالاتر استفاده می کنید ، R8 کامپایلر پیش فرض است که Bytecode Java Project شما را به فرمت DEX تبدیل می کند که روی پلت فرم Android اجرا می شود. با این حال ، هنگامی که شما یک پروژه جدید با استفاده از Android Studio ایجاد می کنید ، کوچک شدن ، انسداد و بهینه سازی کد به طور پیش فرض فعال نمی شود. دلیل این امر این است که این بهینه سازی های زمان کامپایل باعث افزایش زمان ساخت پروژه شما می شوند و اگر به اندازه کافی کد را برای نگه داشتن کدام کد تنظیم نکنید ، ممکن است اشکالات را معرفی کند.
بنابراین ، بهتر است هنگام ساختن نسخه نهایی برنامه خود که قبل از انتشار آزمایش می کنید ، این وظایف زمان کامپایل را فعال کنید. برای فعال کردن کوچک شدن ، انسداد و بهینه سازی ، موارد زیر را در اسکریپت ساخت سطح پروژه خود درج کنید.
کاتلین
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
شیار
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
پرونده های پیکربندی R8
R8 از پرونده های قوانین Proguard برای تغییر رفتار پیش فرض خود و درک بهتر ساختار برنامه شما استفاده می کند ، مانند کلاس هایی که به عنوان نقاط ورود به کد برنامه شما هستند. اگرچه می توانید برخی از این پرونده ها را اصلاح کنید ، برخی از قوانین ممکن است به طور خودکار توسط ابزارهای کامپایل شده مانند AAPT2 تولید شوند یا از وابستگی های کتابخانه برنامه خود به ارث برده شوند. در جدول زیر منابع پرونده های Proguard قوانین که R8 از آن استفاده می کند ، شرح داده شده است.
منبع | مکان | توضیحات |
اندروید استودیو | <module-dir>/proguard-rules.pro | هنگامی که یک ماژول جدید را با استفاده از Android Studio ایجاد می کنید ، IDE یک پرونده proguard-rules.pro را در فهرست اصلی آن ماژول ایجاد می کند.به طور پیش فرض، این فایل هیچ قانونی را اعمال نمی کند. بنابراین ، قوانین proguard خود را در اینجا درج کنید ، مانند قوانین نگهدارنده سفارشی خود. |
افزونه Android Gradle | تولید شده توسط افزونه Android Gradle در زمان کامپایل. | افزونه Android Gradle proguard-android-optimize.txt را تولید می کند ، که شامل قوانینی است که برای اکثر پروژه های Android مفید است و حاشیه نویسی @Keep* می دارد.به طور پیش فرض ، هنگام ایجاد یک ماژول جدید با استفاده از Android Studio ، اسکریپت ساخت ماژول شامل این پرونده قوانین در ساخت شما برای شما است. توجه: افزونه Android Gradle شامل پرونده های قوانین Proguard از پیش تعریف شده اضافی است ، اما توصیه می شود از |
وابستگی های کتابخانه | در یک کتابخانه AAR: در یک کتابخانه شیشه ای: علاوه بر این مکان ها ، افزونه Android Gradle 3.6 یا بالاتر نیز از قوانین کوچک شده هدفمند پشتیبانی می کند. | اگر یک کتابخانه AAR یا JAR با پرونده قوانین خاص خود منتشر شود ، و شما آن کتابخانه را به عنوان یک وابستگی زمان کامپایل درج می کنید ، R8 به طور خودکار هنگام تهیه پروژه خود ، آن قوانین را اعمال می کند. علاوه بر قوانین متعارف Proguard ، افزونه Android Gradle 3.6 یا بالاتر نیز از قوانین کوچک شده هدفمند پشتیبانی می کند. اینها قوانینی هستند که کوچکتر از اندازه گیری های خاص (R8 یا Proguard) و همچنین نسخه های خاص کوچکتر را هدف قرار می دهند. استفاده از پرونده های قوانین که با کتابخانه ها بسته بندی شده اند ، در صورتی که قوانین خاصی برای عملکرد صحیح کتابخانه لازم باشد ، مفید است - یعنی ، توسعه دهنده کتابخانه مراحل عیب یابی را برای شما انجام داده است. با این حال ، شما باید توجه داشته باشید که ، زیرا قوانین افزودنی هستند ، برخی از قوانین مربوط به وابستگی به کتابخانه نمی توانند حذف شوند و ممکن است در تدوین سایر قسمت های برنامه شما تأثیر بگذارد. به عنوان مثال ، اگر یک کتابخانه شامل یک قانون برای غیرفعال کردن بهینه سازی کد باشد ، این قانون بهینه سازی را برای کل پروژه شما غیرفعال می کند. |
ابزار بسته بندی Android Asset 2 (AAPT2) | پس از ساختن پروژه خود با minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt | AAPT2 قوانین را بر اساس مراجعه به کلاسهای موجود در مانیفست ، چیدمان و سایر منابع برنامه شما ایجاد می کند. به عنوان مثال ، AAPT2 شامل یک قانون نگهدارنده برای هر فعالیتی است که در مانیفست برنامه خود به عنوان یک نقطه ورود ثبت می کنید. |
پرونده های پیکربندی سفارشی | به طور پیش فرض ، هنگامی که شما یک ماژول جدید را با استفاده از Android Studio ایجاد می کنید ، IDE <module-dir>/proguard-rules.pro را برای شما ایجاد می کند تا قوانین خود را اضافه کنید. | شما می توانید تنظیمات اضافی را درج کنید ، و R8 آنها را در زمان کامپایل اعمال می کند. |
هنگامی که ویژگی minifyEnabled
را روی true
تنظیم می کنید ، R8 قوانینی را از تمام منابع موجود ذکر شده در بالا ترکیب می کند. این مهم است که به یاد داشته باشید هنگام عیب یابی با R8 ، زیرا سایر وابستگی های زمان کامپایل ، مانند وابستگی های کتابخانه ، ممکن است تغییراتی در رفتار R8 ایجاد کند که شما از آن نمی دانید.
برای تهیه یک گزارش کامل از کلیه قوانینی که R8 هنگام ساخت پروژه خود اعمال می کند ، موارد زیر را در پرونده proguard-rules.pro
ماژول خود قرار دهید:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
قوانین کوچک شده هدفمند
Android Gradle Plugin 3.6 یا بالاتر از قوانین کتابخانه ها پشتیبانی می کند که کوچکتر از کوچکتر (R8 یا Proguard) و همچنین نسخه های خاص کوچکتر را هدف قرار می دهد. این امر به توسعه دهندگان كتابخانه اجازه می دهد تا قوانین خود را متناسب با پروژه هایی كه از نسخه های جدید شركت استفاده می كنند ، متناسب كنند ، در حالی كه اجازه می دهد قوانین موجود همچنان در پروژه هایی با نسخه های قدیمی تر استفاده شود.
برای مشخص کردن قوانین کوچک شده هدفمند ، توسعه دهندگان کتابخانه باید همانطور که در زیر شرح داده شده است ، آنها را در مکان های خاص داخل یک کتابخانه AAR یا JAR قرار دهند.
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
این بدان معناست که قوانین کوچک شده هدفمند در دایرکتوری یک شیشه یا در META-INF/com.android.tools
META-INF/com.android.tools
کلاس ها در classes.jar
ذخیره می شوند.
در زیر آن فهرست ، می توان چندین دایرکتوری با نام به شکل r8-from-<X>-upto-<Y>
یا proguard-from-<X>-upto-<Y>
وجود داشت تا نشان دهد که کدام نسخه ها کدام یک را کوچک می کنند قوانین موجود در دایرکتوری ها برای آن نوشته شده است. توجه داشته باشید که قطعات -from-<X>
و -upto-<Y>
اختیاری هستند ، نسخه <Y>
منحصر به فرد است و دامنه های نسخه باید مداوم باشند.
به عنوان مثال ، r8-upto-8.0.0
، r8-from-8.0.0-upto-8.2.0
و r8-from-8.2.0
مجموعه ای معتبر از قوانین کوچک شده هدفمند را تشکیل می دهند. قوانین تحت دایرکتوری r8-from-8.0.0-upto-8.2.0
توسط R8 از نسخه 8.0.0 تا اما شامل نسخه 8.2.0 استفاده نمی شود.
با توجه به این اطلاعات ، افزونه Android Gradle 3.6 یا بالاتر قوانین را از دایرکتوری های مطابق R8 انتخاب می کند. اگر یک کتابخانه قوانین کوچک شده هدفمند را مشخص نکند ، افزونه Android Gradle قوانین را از مکان های میراث انتخاب می کند ( proguard.txt
برای AAR یا META-INF/proguard/<ProGuard-rules-file>
برای یک شیشه).
توسعه دهندگان کتابخانه می توانند در صورت تمایل به حفظ سازگاری با افزونه Android Gradle قدیمی تر از 3.6 یا ابزارهای دیگر ، قوانین کوچک و یا قوانین Proguard را در کتابخانه های خود یا هر دو نوع درج کنند.
شامل تنظیمات اضافی
هنگامی که یک پروژه یا ماژول جدید را با استفاده از Android Studio ایجاد می کنید ، IDE یک فایل <module-dir>/proguard-rules.pro
را برای شما ایجاد می کند تا قوانین خود را درج کنید. همچنین می توانید با افزودن آنها به ویژگی proguardFiles
در اسکریپت ساخت ماژول خود ، قوانین اضافی را از سایر پرونده ها درج کنید.
به عنوان مثال ، می توانید با اضافه کردن یک ویژگی دیگر proguardFiles
در بلوک productFlavor
، قوانینی را که مخصوص هر نوع ساخت است ، اضافه کنید. پرونده Gradle زیر- flavor2-rules.pro
به طعم محصول flavor2
اضافه می کند. اکنون ، flavor2
از هر سه قانون Proguard استفاده می کند زیرا مواردی که از بلوک release
استفاده می شود نیز اعمال می شود.
علاوه بر این ، می توانید ویژگی testProguardFiles
را اضافه کنید ، که لیستی از پرونده های proguard را که فقط در APK تست گنجانده شده است ، مشخص می کند:
کاتلین
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
شیار
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
کد خود را کوچک کنید
کاهش کد با R8 به طور پیش فرض هنگامی که ویژگی minifyEnabled
روی true
تنظیم می کنید ، به طور پیش فرض فعال می شود.
کوچک شدن کد (همچنین به عنوان لرزش درخت نیز شناخته می شود) ، روند از بین بردن کدی است که R8 تعیین می کند در زمان اجرا لازم نیست. این فرایند می تواند اندازه برنامه شما را تا حد زیادی کاهش دهد اگر به عنوان مثال ، برنامه شما شامل بسیاری از وابستگی های کتابخانه ای باشد اما تنها از بخش کوچکی از عملکرد آنها استفاده می کند.
برای کوچک کردن کد برنامه خود ، R8 ابتدا تمام نقاط ورود به کد برنامه خود را بر اساس مجموعه ترکیبی از پرونده های پیکربندی تعیین می کند. این نقاط ورود شامل کلیه کلاسهایی است که ممکن است از پلت فرم Android برای باز کردن فعالیت ها یا خدمات برنامه شما استفاده کند. با شروع از هر نقطه ورود ، R8 کد برنامه شما را بازرسی می کند تا نموداری از کلیه روشها ، متغیرهای عضو و کلاسهای دیگری را که ممکن است برنامه شما در زمان اجرا به آن دسترسی پیدا کند ، بسازید. کدی که به آن نمودار متصل نیست ، غیرقابل دستیابی در نظر گرفته می شود و ممکن است از برنامه حذف شود.
شکل 1 برنامه ای را با وابستگی به کتابخانه زمان اجرا نشان می دهد. در حین بازرسی از کد برنامه ، R8 تعیین می کند که روش های foo()
، faz()
و bar()
از نقطه ورود MainActivity.class
قابل دسترسی هستند. با این حال ، کلاس OkayApi.class
یا روش baz()
هرگز توسط برنامه شما در زمان اجرا استفاده نمی شود ، و R8 هنگام کوچک کردن برنامه شما آن کد را حذف می کند.
R8 نقاط ورود را از طریق قوانین -keep
در پرونده های پیکربندی R8 پروژه تعیین می کند. یعنی قوانین را نگه دارید کلاس هایی را مشخص کنید که R8 هنگام کوچک کردن برنامه شما نباید از آن دور شود ، و R8 آن کلاس ها را به عنوان نقاط ورود ممکن در برنامه خود می داند. افزونه Android Gradle و AAPT2 به طور خودکار قوانینی را که توسط اکثر پروژه های برنامه برای شما لازم است ، مانند فعالیت ها ، نمایش ها و خدمات برنامه شما ، ایجاد می کنند. با این حال ، اگر شما نیاز به سفارشی کردن این رفتار پیش فرض با قوانین نگهدارنده اضافی دارید ، بخش را در مورد نحوه شخصی سازی کد برای نگه داشتن آن بخوانید.
اگر در عوض شما فقط به کاهش اندازه منابع برنامه خود علاقه دارید ، در مورد چگونگی کوچک کردن منابع خود به این بخش بروید.
توجه داشته باشید که اگر یک پروژه کتابخانه کوچک شود ، برنامه ای که به آن بستگی دارد شامل کلاس های کتابخانه کوچک است. اگر کلاسهای گمشده در APK کتابخانه وجود داشته باشد ، ممکن است لازم باشد قوانین نگهدارنده کتابخانه را تنظیم کنید. اگر در حال ساخت و انتشار کتابخانه با فرمت AAR هستید ، پرونده های JAR محلی که کتابخانه شما به آن بستگی دارد در پرونده AAR کوچک نشده است .
برای نگه داشتن کدام کد را سفارشی کنید
برای اکثر شرایط ، پرونده قوانین پیش فرض Proguard ( proguard-android-optimize.txt
) برای R8 کافی است تا فقط کد بلااستفاده را حذف کند. با این حال ، برخی از شرایط برای R8 برای تجزیه و تحلیل صحیح دشوار است و ممکن است کد شما را که در واقع به آن نیاز دارد حذف کند. برخی از نمونه هایی از زمان حذف نادرست کد شامل موارد زیر است:
- وقتی برنامه شما از رابط بومی جاوا (JNI) استفاده می کند
- وقتی برنامه شما در زمان اجرا کد به نظر می رسد (مانند بازتاب)
آزمایش برنامه شما باید خطاهای ناشی از کد نامناسب حذف شده را نشان دهد ، اما می توانید با تولید گزارش کد حذف شده ، چه کد حذف شده است.
برای رفع خطاها و مجبور کردن R8 برای حفظ کد خاص ، یک خط -keep
را در پرونده قوانین Proguard اضافه کنید. به عنوان مثال:
-keep public class MyClass
از طرف دیگر ، می توانید حاشیه نویسی @Keep
به کدی که می خواهید نگه دارید اضافه کنید. اضافه کردن @Keep
در کلاس ، کل کلاس را به صورت as-is نگه می دارد. اضافه کردن آن در یک روش یا فیلد ، روش/قسمت (و نام آن) و همچنین نام کلاس را دست نخورده نگه می دارد. توجه داشته باشید که این حاشیه نویسی فقط در هنگام استفاده از کتابخانه حاشیه نویسی Androidx در دسترس است و وقتی پرونده Proguard Rules را که با افزونه Android Gradle بسته بندی شده است ، درج می کنید ، همانطور که در بخش در مورد نحوه فعال سازی کوچک کردن توضیح داده شده است.
ملاحظات زیادی وجود دارد که باید هنگام استفاده از گزینه -keep
مطرح کنید. برای کسب اطلاعات بیشتر در مورد شخصی سازی پرونده قوانین خود ، دفترچه راهنمای Proguard را بخوانید. بخش عیب یابی سایر مشکلات متداول را که ممکن است هنگام محرومیت از آن با آنها روبرو شوید ، تشریح می کند.
کتابخانه های بومی
به طور پیش فرض ، كتابخانه های كد بومی در ساخت برنامه شما محروم می شوند. این سلب شامل حذف جدول نماد و اشکال زدایی اطلاعات موجود در هر کتابخانه بومی است که توسط برنامه شما استفاده می شود. سلب كتابخانه های كد بومی منجر به صرفه جویی در اندازه قابل توجه می شود. با این حال ، تشخیص تصادفات در کنسول Google Play به دلیل اطلاعات از دست رفته (مانند نام کلاس و عملکرد) غیرممکن است.
پشتیبانی از تصادف بومی
کنسول Google Play گزارش تصادفات بومی تحت Android Vitals را گزارش می کند. با چند مرحله ، می توانید یک پرونده نماد اشکال زدایی بومی را برای برنامه خود تولید و بارگذاری کنید. این فایل امکان آثار پشته بومی بومی (که شامل نام کلاس و عملکرد) در Android Vitals است را قادر می سازد تا به شما در تولید اشکال زدایی در تولید کمک کند. این مراحل بسته به نسخه افزونه Android Gradle که در پروژه شما و ساخت پروژه شما استفاده می شود متفاوت است.
افزونه Android Gradle نسخه 4.1 یا بالاتر
اگر پروژه شما بسته نرم افزاری برنامه Android را ایجاد کرده است ، می توانید به طور خودکار پرونده نمادهای اشکال زدایی بومی را در آن قرار دهید. برای گنجاندن این پرونده در ساختهای نسخه ، موارد زیر را به پرونده build.gradle.kts
برنامه خود اضافه کنید:
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
سطح نماد اشکال زدایی را از موارد زیر انتخاب کنید:
- برای بدست آوردن نام عملکرد در آثار نماد پشته کنسول Play از
SYMBOL_TABLE
استفاده کنید. این سطح از مقبره ها پشتیبانی می کند. - برای بدست آوردن نام عملکرد ، پرونده ها و شماره های خط در آثار نماد پشته کنسول Play
FULL
استفاده کنید.
اگر پروژه شما یک APK ایجاد کرده است ، از تنظیمات ساخت build.gradle.kts
استفاده کنید که قبلاً نشان داده شده است تا پرونده نمادهای اشکال زدایی بومی را به طور جداگانه تولید کنید. فایل نمادهای اشکال زدایی بومی را به صورت دستی در کنسول Google Play بارگذاری کنید . به عنوان بخشی از فرآیند ساخت ، افزونه Android Gradle این پرونده را در محل پروژه زیر قرار می دهد:
app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip
Android Gradle Plugin نسخه 4.0 یا قبل از آن (و سایر سیستم های ساخت)
به عنوان بخشی از فرآیند ساخت ، افزونه Android Gradle نسخه ای از کتابخانه های Unstripped را در یک فهرست پروژه نگه می دارد. این ساختار دایرکتوری مشابه موارد زیر است:
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
مطالب این فهرست را فشرده کنید:
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
فایل
symbols.zip
را به صورت دستی در کنسول Google Play بارگذاری کنید .
منابع خود را کوچک کنید
کوچک شدن منابع فقط در رابطه با کوچک شدن کد کار می کند. پس از اینکه کد کننده کد تمام کد بلااستفاده را حذف کرد ، Resource Shrinker می تواند مشخص کند که از چه منابعی که برنامه هنوز استفاده می کند ، می تواند مشخص کند. این امر به ویژه هنگامی که کتابخانه های کد را که شامل منابع است اضافه می کنید - باید کد کتابخانه استفاده نشده را حذف کنید تا منابع کتابخانه ای مورد بررسی قرار نگیرد و بنابراین توسط کوچک کننده منابع قابل جابجایی است.
برای فعال کردن کوچک شدن منابع ، ویژگی shrinkResources
را در اسکریپت ساخت خود true
کنید (در کنار minifyEnabled
برای کوچک شدن کد). به عنوان مثال:
کاتلین
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
شیار
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
اگر قبلاً برنامه خود را با استفاده از minifyEnabled
برای کوچک کردن کد ساخته اید ، پس از فعال کردن shrinkResources
، زیرا ممکن است شما نیاز به ویرایش پرونده های proguard-rules.pro
خود داشته باشید تا کلاس ها یا روشهایی را که ایجاد شده یا به صورت پویا قبل از شروع حذف استفاده می کنند ، نگه دارید. منابع
برای نگهداری از منابع شخصی را سفارشی کنید
اگر منابع خاصی وجود دارد که می خواهید آن را نگه دارید یا دور ریخته باشید ، یک فایل XML را در پروژه خود با یک برچسب <resources>
ایجاد کنید و هر منبع را برای نگه داشتن tools:keep
ویژگی و هر منبع را برای دور انداختن در tools:discard
. هر دو ویژگی لیستی از نام منابع را از هم جدا می کنند. می توانید از شخصیت ستاره به عنوان کارت وحشی استفاده کنید.
به عنوان مثال:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
این پرونده را در منابع پروژه خود ، به عنوان مثال ، در res/raw/my.package.keep.xml
ذخیره کنید. ساخت این پرونده را در برنامه شما بسته بندی نمی کند.
توجه: حتما از یک نام منحصر به فرد برای پرونده keep
استفاده کنید. هنگامی که کتابخانه های مختلف به هم پیوند می خورند ، قوانین نگهدارنده آنها در غیر این صورت با هم اختلاف دارد و باعث ایجاد مسائل احتمالی با قوانین نادیده گرفته شده یا منابع نگهدارنده نشده می شود.
مشخص کردن اینکه چه منابع برای دور انداختن ممکن است احمقانه به نظر برسد وقتی می توانید آنها را حذف کنید ، اما این می تواند هنگام استفاده از انواع ساخت مفید باشد. به عنوان مثال ، شما ممکن است تمام منابع خود را در دایرکتوری پروژه مشترک قرار دهید ، سپس یک فایل متفاوت my.package.build.variant.keep.xml
برای هر نوع ساخت ایجاد کنید وقتی می دانید که به نظر می رسد یک منبع خاص در کد استفاده می شود (و بنابراین توسط کوچکتر حذف نشده است) اما می دانید که در واقع برای نوع ساخت داده شده استفاده نمی شود. همچنین ممکن است که ابزارهای ساخت به طور نادرست یک منبع را در صورت لزوم شناسایی کنند ، که امکان پذیر است زیرا کامپایلر IDS Resource Inline را اضافه می کند و سپس آنالایزر منبع ممکن است تفاوت بین یک منبع واقعاً ارجاع شده و یک مقدار عدد صحیح را در کد که اتفاق می افتد ، بداند ارزش یکسانی دارند
بررسی های مرجع دقیق را فعال کنید
به طور معمول ، کوچکتر منبع می تواند به طور دقیق تعیین کند که آیا از یک منبع استفاده می شود یا خیر. با این حال ، اگر کد شما با Resources.getIdentifier()
(یا اگر هر یک از کتابخانه های شما این کار را انجام می دهند-کتابخانه AppCompat انجام می دهد) ، این بدان معنی است که کد شما به دنبال نام منابع است که بر اساس رشته های تولید شده پویا است. هنگامی که شما این کار را انجام می دهید ، Resource Shrinker به طور پیش فرض از نظر دفاعی رفتار می کند و تمام منابع را با فرمت نام تطبیق به عنوان بالقوه استفاده شده و برای حذف در دسترس نیست.
به عنوان مثال ، کد زیر باعث می شود تمام منابع با پیشوند img_
به صورت استفاده شده مشخص شود.
کاتلین
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
جاوا
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
Resource Shrinker همچنین تمام ثابت های رشته ای در کد شما و همچنین res/raw/
Resources مختلف را جستجو می کند ، و به دنبال URL های منبع در فرمی شبیه به file:///android_res/drawable//ic_plus_anim_016.png
. اگر رشته هایی مانند این یا سایر مواردی پیدا کند که به نظر می رسد می توانند برای ساخت URL هایی مانند این استفاده شوند ، آنها را از بین نمی برد.
اینها نمونه هایی از حالت کوچک شدن ایمن است که به طور پیش فرض فعال می شود. با این حال ، می توانید این کار "ایمن بهتر از ببخشید" را خاموش کنید و مشخص کنید که کوچکتر منابع فقط از منابع مورد استفاده در آن استفاده می شود. برای انجام این کار ، shrinkMode
در پرونده keep.xml
strict
تنظیم کنید ، به شرح زیر:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
اگر حالت کوچک شدن دقیق را فعال می کنید و کد شما همچنین منابع خود را با رشته های تولید شده پویا ، همانطور که در بالا نشان داده شده است ، ارجاع می دهد ، پس باید آن منابع را با استفاده از tools:keep
.
منابع جایگزین بلااستفاده را حذف کنید
Shrinker Resource Gradle فقط منابعی را که توسط کد برنامه شما ارجاع نمی شوند حذف می کند ، به این معنی که منابع جایگزین برای تنظیمات مختلف دستگاه را حذف نمی کند. در صورت لزوم ، می توانید از ویژگی های resConfigs
افزونه Android Gradle استفاده کنید تا پرونده های منبع جایگزین را که برنامه شما به آن احتیاج ندارد ، حذف کنید.
به عنوان مثال ، اگر از کتابخانه ای استفاده می کنید که شامل منابع زبانی باشد (مانند AppCompat یا Google Play Services) ، برنامه شما شامل تمام رشته های زبان ترجمه شده برای پیام های موجود در آن کتابخانه ها است که آیا بقیه برنامه شما به همان زبان ها ترجمه شده است یا نه اگر می خواهید فقط زبانهایی را که برنامه شما به طور رسمی از آن پشتیبانی می کند ، نگه دارید ، می توانید آن زبانها را با استفاده از ویژگی resConfig
مشخص کنید. هر منبع برای زبانهایی که مشخص نشده است حذف می شود.
قطعه زیر نشان می دهد که چگونه می توانید منابع زبان خود را فقط به انگلیسی و فرانسوی محدود کنید:
کاتلین
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
شیار
android { defaultConfig { ... resConfigs "en", "fr" } }
هنگام انتشار یک برنامه با استفاده از فرمت بسته برنامه Android ، به طور پیش فرض فقط زبانهای پیکربندی شده بر روی دستگاه کاربر هنگام نصب برنامه بارگیری می شوند. به طور مشابه ، فقط منابع مطابق با تراکم صفحه دستگاه و کتابخانه های بومی مطابق با ABI دستگاه در این بارگیری گنجانده شده است. برای اطلاعات بیشتر به پیکربندی بسته نرم افزاری برنامه Android مراجعه کنید.
برای برنامه های Legacy که با APK ها منتشر می شوند (قبل از آگوست 2021 ایجاد شده است) ، می توانید با ساخت چندین APK که هر یک از آنها پیکربندی دستگاه متفاوتی را هدف قرار می دهند ، چگالی صفحه یا منابع ABI را در APK خود قرار دهید.
منابع تکراری را ادغام کنید
به طور پیش فرض ، Gradle همچنین منابع یکسان نامگذاری شده را ادغام می کند ، مانند Drawables با همان نام که ممکن است در پوشه های مختلف منابع باشد. این رفتار توسط خاصیت shrinkResources
کنترل نمی شود و نمی تواند غیرفعال شود ، زیرا لازم است در صورت مطابقت با منابع متعدد با نام کد شما ، از خطاها جلوگیری شود.
ادغام منابع فقط در شرایطی اتفاق می افتد که دو یا چند پرونده یک نام ، نوع و صلاحیت منابع یکسان را به اشتراک بگذارند. Gradle انتخاب می کند که پرونده آن را بهترین انتخاب در بین نسخه های تکراری (بر اساس یک دستور اولویت که در زیر شرح داده شده است) انتخاب می کند و فقط یک منبع را برای توزیع در مصنوعات نهایی به AAPT منتقل می کند.
Gradle به دنبال منابع تکراری در مکان های زیر است:
- منابع اصلی ، مرتبط با مجموعه منبع اصلی ، به طور کلی در
src/main/res/
. - انواع مختلف ، از نوع ساخت و طعم های ساخت.
- وابستگی پروژه کتابخانه.
Gradle منابع کپی را در ترتیب اولویت آبشار زیر ادغام می کند:
وابستگی → اصلی → ساخت طعم → نوع ساخت
به عنوان مثال ، اگر یک منبع تکراری در هر دو منبع اصلی شما و عطر و طعم ساخت ظاهر شود ، Gradle یکی از آنها را در عطر و طعم ساخت انتخاب می کند.
اگر منابع یکسان در همان مجموعه منبع ظاهر شوند ، Gradle نمی تواند آنها را ادغام کند و خطای ادغام منبع را منتشر کند. این امر می تواند اتفاق بیفتد در صورتی که چندین مجموعه منبع را در ویژگی sourceSet
پرونده build.gradle.kts
خود تعریف کنید - برای مثال اگر src/main/res/
و src/main/res2/
حاوی منابع یکسان باشد.
کد خود را مبهوت کنید
هدف از انسجام ، کاهش اندازه برنامه شما با کوتاه کردن نام کلاس ها ، روش ها و زمینه های برنامه شما است. در زیر نمونه ای از انسداد با استفاده از R8 است:
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
در حالی که توقیف کد را از برنامه شما حذف نمی کند ، پس انداز اندازه قابل توجهی را می توان در برنامه هایی با پرونده های DEX مشاهده کرد که بسیاری از کلاس ها ، روش ها و زمینه ها را نمایه می کند. با این حال ، از آنجا که انسداد بخش های مختلفی از کد شما را تغییر نام می دهد ، برخی از کارهای خاص ، مانند بازرسی از آثار پشته ، به ابزارهای اضافی احتیاج دارند. برای درک StackTrace خود پس از انسجام ، بخش مربوط به نحوه رمزگشایی یک ردیابی پشته را بخوانید.
علاوه بر این ، اگر کد شما به نامگذاری قابل پیش بینی برای روش ها و کلاس های برنامه شما متکی است - به عنوان مثال ، هنگام استفاده از بازتاب ، شما باید با آن امضاها به عنوان نقاط ورود رفتار کنید و قوانین را برای آنها تعیین کنید ، همانطور که در بخش نحوه تنظیم کد به کدام کد توضیح داده شده است. نگه داشتن . این قوانین به R8 می گویند نه تنها این کد را در DEX نهایی برنامه شما نگه دارد بلکه نامگذاری اصلی آن را نیز حفظ می کند.
ردیابی پشته ای مبهم را رمزگشایی کنید
بعد از اینکه R8 کد شما را تحت تأثیر قرار می دهد ، درک یک ردیابی پشته دشوار است (اگر غیرممکن نباشد) زیرا ممکن است نام کلاس ها و روش ها تغییر کرده باشد. برای به دست آوردن ردیابی پشته اصلی ، باید ردیابی پشته را بازیابی کنید.
بهینه سازی کد
به منظور بهینه سازی برنامه خود حتی بیشتر ، R8 کد شما را در سطح عمیق تر بازرسی می کند تا کد بلااستفاده تر را حذف کند یا در صورت امکان ، کد خود را بازنویسی کنید تا آن را کمتر لفظ کنید. موارد زیر چند نمونه از چنین بهینه سازی ها است:
- اگر کد شما هرگز شعبه
else {}
را برای بیانیه IF/other دیگر نمی گیرد ، R8 ممکن است کد را برای شعبهelse {}
حذف کند. - اگر کد شما فقط در چند مکان به یک روش فراخوانی می کند ، R8 ممکن است روش را حذف کرده و آن را در چند سایت تماس قرار دهد.
- اگر R8 تعیین کند که یک کلاس فقط یک زیر کلاس منحصر به فرد دارد ، و خود کلاس فوری نمی شود (برای مثال ، یک کلاس پایه انتزاعی که فقط توسط یک کلاس اجرای بتن استفاده می شود) ، پس R8 می تواند دو کلاس را ترکیب کند و یک کلاس را از برنامه خارج کند .
- برای کسب اطلاعات بیشتر ، پست های وبلاگ بهینه سازی R8 توسط جیک وارتون را بخوانید.
R8 به شما امکان نمی دهد بهینه سازی های گسسته را غیرفعال یا فعال کنید ، یا رفتار یک بهینه سازی را اصلاح کنید. در حقیقت ، R8 هرگونه قوانین proguard را که سعی در اصلاح بهینه سازی های پیش فرض ، مانند -optimizations
و -optimizationpasses
دارند ، نادیده می گیرد. این محدودیت مهم است زیرا ، همانطور که R8 به پیشرفت خود ادامه می دهد ، حفظ یک رفتار استاندارد برای بهینه سازی به تیم استودیوی Android کمک می کند تا به راحتی هرگونه مشکلی را که ممکن است با آن روبرو شوید ، عیب یابی و حل کند.
توجه داشته باشید که فعال کردن بهینه سازی ، ردیابی پشته را برای برنامه شما تغییر می دهد. به عنوان مثال ، Inlining فریم های پشته را حذف می کند. برای یادگیری نحوه به دست آوردن آثار اصلی پشته ، به بخش Retracing مراجعه کنید.
تأثیر بر عملکرد زمان اجرا
در صورت کوچک شدن ، انسداد و بهینه سازی همه فعال هستند ، R8 عملکرد زمان اجرا کد (از جمله راه اندازی و زمان فریم را در موضوع UI) تا 30 ٪ بهبود می بخشد. غیرفعال کردن هر یک از این موارد ، مجموعه ای از بهینه سازی های R8 را به طرز چشمگیری محدود می کند.
اگر R8 فعال باشد ، باید پروفایل های راه اندازی را برای عملکرد حتی بهتر راه اندازی ایجاد کنید .
بهینه سازی های تهاجمی تر را فعال کنید
R8 شامل مجموعه ای از بهینه سازی های اضافی (به عنوان "حالت کامل" گفته می شود) که باعث می شود رفتار متفاوت از Proguard انجام شود. این بهینه سازی ها از زمان افزونه Android Gradle نسخه 8.0.0 به طور پیش فرض فعال می شوند.
شما می توانید این بهینه سازی های اضافی را با استفاده از موارد زیر در پرونده gradle.properties
پروژه خود غیرفعال کنید:
android.enableR8.fullMode=false
از آنجا که بهینه سازی های اضافی باعث می شود R8 متفاوت از Proguard رفتار کند ، ممکن است شما را ملزم به شامل قوانین اضافی Proguard کنید تا در صورت استفاده از قوانینی که برای Proguard طراحی شده است ، از مشکلات زمان اجرا جلوگیری کنید. به عنوان مثال ، بگویید که کد شما کلاس را از طریق API بازتاب جاوا ارجاع می دهد. هنگام استفاده از "حالت کامل" ، R8 فرض می کند که شما قصد دارید اشیاء آن کلاس را در زمان اجرا بررسی و دستکاری کنید - حتی اگر کد شما واقعاً اینگونه نباشد - و به طور خودکار کلاس و ابتکار عمل استاتیک آن را نگه می دارد.
با این حال ، هنگام استفاده از "حالت کامل" ، R8 این فرض را نمی کند و اگر R8 ادعا کند که کد شما در غیر این صورت هرگز از کلاس در زمان اجرا استفاده نمی کند ، کلاس را از DEX نهایی برنامه شما حذف می کند. یعنی اگر می خواهید کلاس و ابتدای استاتیک آن را نگه دارید ، برای انجام این کار باید یک قانون نگهدار را در پرونده قوانین خود قرار دهید.
اگر هنگام استفاده از "حالت کامل" R8 با هر مشکلی روبرو شدید ، برای یک راه حل احتمالی به صفحه R8 FAQ مراجعه کنید. اگر قادر به حل مسئله نیستید ، لطفاً یک اشکال گزارش دهید .
جمع آوری پشته ها
کد پردازش شده توسط R8 به روش های مختلفی تغییر می کند که می تواند آثار پشته را برای درک سخت تر کند زیرا آثار پشته دقیقاً با کد منبع مطابقت ندارند. این می تواند در مورد تغییر در شماره خط در هنگام نگهداری اطلاعات اشکال زدایی باشد. این می تواند به دلیل بهینه سازی مانند خط و ترسیم باشد. بزرگترین مشارکت کننده ، مبهم بودن است که حتی کلاس ها و روش ها اسامی را تغییر می دهند.
برای بازیابی ردیابی پشته اصلی ، R8 ابزار خط فرمان Retrace را ارائه می دهد ، که با بسته ابزار Command-Line همراه است.
برای پشتیبانی از عقب نشینی آثار پشته برنامه خود ، باید اطمینان حاصل کنید که ساخت و ساز با افزودن قوانین زیر به پرونده های proguard-rules.pro
ماژول خود ، اطلاعات کافی را برای بازیابی حفظ می کند:
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
ویژگی LineNumberTable
اطلاعات موقعیتی را در روش هایی حفظ می کند به گونه ای که این موقعیت ها در آثار پشته چاپ می شوند. ویژگی SourceFile
تضمین می کند که تمام زمان های بالقوه در واقع اطلاعات موقعیتی را چاپ می کنند. بخشنامه -renamesourcefileattribute
نام فایل منبع را در آثار پشته فقط به SourceFile
تنظیم می کند. نام واقعی پرونده منبع اصلی هنگام عقب نشینی لازم نیست زیرا پرونده نقشه برداری حاوی پرونده منبع اصلی است.
R8 هر بار که اجرا می شود ، یک فایل mapping.txt
ایجاد می کند ، که حاوی اطلاعات مورد نیاز برای نقشه برداری آثار پشته به آثار پشته اصلی است. Android Studio فایل را در <module-name> /build/outputs/mapping/ <build-type> /
دایرکتوری ذخیره می کند.
هنگام انتشار برنامه خود در Google Play ، می توانید فایل mapping.txt
را برای هر نسخه از برنامه خود بارگذاری کنید. When publishing using Android App Bundles this file is included automatically as part of the app bundle content. Then Google Play will retrace incoming stack traces from user-reported issues so you can review them in the Play Console. For more information, see the Help Center article about how to deobfuscate crash stack traces .
Troubleshoot with R8
This section describes some strategies for troubleshooting issues when enabling shrinking, obfuscation, and optimization using R8. If you do not find a solution to your issue below, also read the R8 FAQ page and ProGuard's troubleshooting guide .
Generate a report of removed (or kept) code
To help you troubleshoot certain R8 issues, it may be useful to see a report of all the code that R8 removed from your app. For each module for which you want to generate this report, add -printusage <output-dir>/usage.txt
to your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of removed code looks similar to the following:
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
If instead you want to see a report of the entry points that R8 determines from your project's keep rules , include -printseeds <output-dir>/seeds.txt
in your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of kept entry points looks similar to the following:
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
Troubleshoot resource shrinking
When you shrink resources, the Build window shows a summary of the resources that are removed from the app. (You need to first click Toggle view on the left side of the window to display detailed text output from Gradle.) For example:
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
Gradle also creates a diagnostic file named resources.txt
in <module-name>/build/outputs/mapping/release/
(the same folder as ProGuard's output files). This file includes details such as which resources reference other resources and which resources are used or removed.
For example, to find out why @drawable/ic_plus_anim_016
is still in your app, open the resources.txt
file and search for that file name. You might find that it's referenced from another resource, as follows:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
You now need to know why @drawable/add_schedule_fab_icon_anim
is reachable—and if you search upwards you'll find that resource is listed under "The root reachable resources are:". This means there is a code reference to add_schedule_fab_icon_anim
(that is, its R.drawable ID was found in the reachable code).
If you are not using strict checking, resource IDs can be marked as reachable if there are string constants that look like they might be used to construct resource names for dynamically loaded resources. In that case, if you search the build output for the resource name, you might find a message like this:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
If you see one of these strings and you are certain that the string is not being used to load the given resource dynamically, you can use the tools:discard
attribute to inform the build system to remove it, as described in the section about how to customize which resources to keep .
To make your app as small and fast as possible, you should optimize and minify your release build with isMinifyEnabled = true
.
Doing so enables shrinking , which removes unused code and resources; obfuscation , which shortens the names of your app's classes and members; and optimization , which applies more aggressive strategies to further reduce the size and improve the performance of your app. This page describes how R8 performs these compile-time tasks for your project and how you can customize them.
When you build your project using Android Gradle plugin 3.4.0 or higher, the plugin no longer uses ProGuard to perform compile-time code optimization. Instead, the plugin works with the R8 compiler to handle the following compile-time tasks:
- Code shrinking (or tree-shaking): detects and safely removes unused classes, fields, methods, and attributes from your app and its library dependencies (making it a valuable tool for working around the 64k reference limit ). For example, if you use only a few APIs of a library dependency, shrinking can identify library code that your app is not using and remove only that code from your app. To learn more, go to the section about how to shrink your code .
- Resource shrinking: removes unused resources from your packaged app, including unused resources in your app's library dependencies. این در ارتباط با کوچک کردن کد کار می کند به طوری که پس از حذف کدهای استفاده نشده، هر منبعی که دیگر به آن ارجاع داده نمی شود، می تواند با خیال راحت حذف شود. To learn more, go to the section about how to shrink your resources .
- Optimization: inspects and rewrites your code to improve runtime performance and further reduce the size of your app's DEX files. This improves runtime performance of code by up to 30%, drastically improving startup and frame timing. For example, if R8 detects that the
else {}
branch for a given if/else statement is never taken, R8 removes the code for theelse {}
branch. To learn more, go to the section about code optimization . - Obfuscation (or identifier minification): shortens the name of classes and members, which results in reduced DEX file sizes. To learn more, go to the section about how to obfuscate your code .
When building the release version of your app, R8 can be configured to perform the compile-time tasks described above for you. You can also disable certain tasks or customize R8's behavior through ProGuard rules files. In fact, R8 works with all of your existing ProGuard rules files , so updating the Android Gradle plugin to use R8 should not require you to change your existing rules.
کوچک کردن، مبهم سازی و بهینه سازی را فعال کنید
When you use Android Studio 3.4 or Android Gradle plugin 3.4.0 and higher, R8 is the default compiler that converts your project's Java bytecode into the DEX format that runs on the Android platform. However, when you create a new project using Android Studio, shrinking, obfuscation, and code optimization is not enabled by default. That's because these compile-time optimizations increase the build time of your project and might introduce bugs if you do not sufficiently customize which code to keep .
So, it's best to enable these compile-time tasks when building the final version of your app that you test prior to publishing. To enable shrinking, obfuscation, and optimization, include the following in your project-level build script.
کاتلین
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
شیار
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
R8 configuration files
R8 uses ProGuard rules files to modify its default behavior and better understand your app's structure, such as the classes that serve as entry points into your app's code. Although you can modify some of these rules files, some rules may be generated automatically by compile-time tools, such as AAPT2, or inherited from your app's library dependencies. The table below describes the sources of ProGuard rules files that R8 uses.
منبع | مکان | توضیحات |
اندروید استودیو | <module-dir>/proguard-rules.pro | When you create a new module using Android Studio, the IDE creates a proguard-rules.pro file in the root directory of that module.به طور پیش فرض، این فایل هیچ قانونی را اعمال نمی کند. So, include your own ProGuard rules here, such as your custom keep rules . |
Android Gradle plugin | Generated by the Android Gradle plugin at compile time. | The Android Gradle plugin generates proguard-android-optimize.txt , which includes rules that are useful to most Android projects and enables @Keep* annotations .By default, when creating a new module using Android Studio, the module-level build script includes this rules file in your release build for you. Note: The Android Gradle plugin includes additional predefined ProGuard rules files, but it is recommended that you use |
Library dependencies | In an AAR library: In a JAR library: In addition to these locations, Android Gradle plugin 3.6 or higher also supports targeted shrink rules . | If an AAR or JAR library is published with its own rules file, and you include that library as a compile-time dependency, R8 automatically applies those rules when compiling your project. In addition to conventional ProGuard rules, Android Gradle plugin 3.6 or higher also supports targeted shrink rules . These are rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. Using rules files that are packaged with libraries is useful if certain rules are required for the library to function properly—that is, the library developer has performed the troubleshooting steps for you. However, you should be aware that, because the rules are additive , certain rules that a library dependency includes cannot be removed and might impact the compilation of other parts of your app. For example, if a library includes a rule to disable code optimizations, that rule disables optimizations for your entire project. |
Android Asset Package Tool 2 (AAPT2) | After building your project with minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt | AAPT2 generates keep rules based on references to classes in your app's manifest, layouts, and other app resources. For example, AAPT2 includes a keep rule for each Activity that you register in your app's manifest as an entry point. |
Custom configuration files | By default, when you create a new module using Android Studio, the IDE creates <module-dir>/proguard-rules.pro for you to add your own rules. | You can include additional configurations , and R8 applies them at compile-time. |
When you set the minifyEnabled
property to true
, R8 combines rules from all the available sources listed above. This is important to remember when you troubleshoot with R8 , because other compile-time dependencies, such as library dependencies, may introduce changes to the R8 behavior that you do not know about.
To output a full report of all the rules that R8 applies when building your project, include the following in your module's proguard-rules.pro
file:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
Targeted shrink rules
Android Gradle plugin 3.6 or higher supports libraries' rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. This allows library developers to tailor their rules to work optimally in projects that use new shrinker versions, while allowing existing rules to continue to be used in projects with older shrinker versions.
To specify targeted shrink rules, library developers will need to include them at specific locations inside an AAR or JAR library, as described below.
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
That means targeted shrink rules are stored in the META-INF/com.android.tools
directory of a JAR or in the META-INF/com.android.tools
directory inside classes.jar
of an AAR.
Under that directory, there can be multiple directories with names in the form of r8-from-<X>-upto-<Y>
or proguard-from-<X>-upto-<Y>
to indicate which versions of which shrinker the rules inside the directories are written for. Note that the -from-<X>
and -upto-<Y>
parts are optional, the <Y>
version is exclusive , and the version ranges must be continuous.
For example, r8-upto-8.0.0
, r8-from-8.0.0-upto-8.2.0
, and r8-from-8.2.0
form a valid set of targeted shrink rules. The rules under the r8-from-8.0.0-upto-8.2.0
directory will be used by R8 from version 8.0.0 up to but not including version 8.2.0.
Given that information, Android Gradle plugin 3.6 or higher will select the rules from the matching R8 directories. If a library does not specify targeted shrink rules, the Android Gradle plugin will select the rules from the legacy locations ( proguard.txt
for an AAR or META-INF/proguard/<ProGuard-rules-file>
for a JAR).
Library developers can choose to include either targeted shrink rules or legacy ProGuard rules in their libraries, or both types if they want to maintain compatibility with Android Gradle plugin older than 3.6 or other tools.
Include additional configurations
When you create a new project or module using Android Studio, the IDE creates a <module-dir>/proguard-rules.pro
file for you to include your own rules. You can also include additional rules from other files by adding them to the proguardFiles
property in your module's build script.
For example, you can add rules that are specific to each build variant by adding another proguardFiles
property in the corresponding productFlavor
block. The following Gradle file adds flavor2-rules.pro
to the flavor2
product flavor. Now, flavor2
uses all three ProGuard rules because those from the release
block are also applied.
Additionally, you can add the testProguardFiles
property, which specifies a list of ProGuard files that are included in the test APK only:
کاتلین
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
شیار
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
Shrink your code
Code shrinking with R8 is enabled by default when you set the minifyEnabled
property to true
.
Code shrinking (also known as tree shaking), is the process of removing code that R8 determines is not required at runtime. This process can greatly reduce your app's size if, for example, your app includes many library dependencies but utilizes only a small part of their functionality.
To shrink your app's code, R8 first determines all entry points into your app's code based on the combined set of configuration files . These entry points include all classes that the Android platform may use to open your app's Activities or services. Starting from each entry point, R8 inspects your app's code to build a graph of all methods, member variables, and other classes that your app might access at runtime. Code that is not connected to that graph is considered unreachable and may be removed from the app.
Figure 1 shows an app with a runtime library dependency. While inspecting the app's code, R8 determines that methods foo()
, faz()
, and bar()
are reachable from the MainActivity.class
entry point. However, class OkayApi.class
or its method baz()
is never used by your app at runtime, and R8 removes that code when shrinking your app.
R8 determines entry points through -keep
rules in the project's R8 configuration files . That is, keep rules specify classes that R8 should not discard when shrinking your app, and R8 considers those classes as possible entry points into your app. The Android Gradle plugin and AAPT2 automatically generate keep rules that are required by most app projects for you, such as your app's activities, views, and services. However, if you need to customize this default behavior with additional keep rules, read the section about how to customize which code to keep .
If instead you are interested only in reducing the size of your app's resources, skip to the section about how to shrink your resources .
Note that if a library project is shrunk, an app that depends on that library includes shrunk library classes. You might need to adjust library keep rules if there are missing classes in the library APK. If you're building and publishing a library in AAR format, local JAR files that your library depends on aren't shrunk in the AAR file.
Customize which code to keep
For most situations, the default ProGuard rules file ( proguard-android-optimize.txt
) is sufficient for R8 to remove only the unused code. However, some situations are difficult for R8 to analyze correctly and it might remove code your app actually needs. Some examples of when it might incorrectly remove code include:
- When your app calls a method from the Java Native Interface (JNI)
- When your app looks up code at runtime (such as with reflection)
Testing your app should reveal any errors caused by inappropriately removed code, but you can also inspect what code was removed by generating a report of removed code .
To fix errors and force R8 to keep certain code, add a -keep
line in the ProGuard rules file. به عنوان مثال:
-keep public class MyClass
Alternatively, you can add the @Keep
annotation to the code you want to keep. Adding @Keep
on a class keeps the entire class as-is. Adding it on a method or field will keep the method/field (and its name) as well as the class name intact. Note that this annotation is available only when using the AndroidX Annotations Library and when you include the ProGuard rules file that is packaged with the Android Gradle plugin, as described in the section about how to enable shrinking .
There are many considerations you should make when using the -keep
option; for more information about customizing your rules file, read the ProGuard Manual . The Troubleshooting section outlines other common problems you might encounter when your code gets stripped away.
Strip native libraries
By default, native code libraries are stripped in release builds of your app. This stripping consists of removing the symbol table and debugging information contained in any native libraries used by your app. Stripping native code libraries results in significant size savings; however, it's impossible to diagnose crashes on the Google Play Console due to the missing information (such as class and function names).
Native crash support
The Google Play Console reports native crashes under Android vitals . With a few steps, you can generate and upload a native debug symbols file for your app. This file enables symbolicated native crash stack traces (that include class and function names) in Android vitals to help you debug your app in production. These steps vary depending on the version of the Android Gradle plugin used in your project and the build output of your project.
Android Gradle plugin version 4.1 or later
If your project builds an Android App Bundle, you can automatically include the native debug symbols file in it. To include this file in release builds, add the following to your app's build.gradle.kts
file:
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
Select the debug symbol level from the following:
- Use
SYMBOL_TABLE
to get function names in the Play Console's symbolicated stack traces. This level supports tombstones . - Use
FULL
to get function names, files, and line numbers in the Play Console's symbolicated stack traces.
If your project builds an APK, use the build.gradle.kts
build setting shown earlier to generate the native debug symbols file separately. Manually upload the native debug symbols file to the Google Play Console. As part of the build process, the Android Gradle plugin outputs this file in the following project location:
app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip
Android Gradle plugin version 4.0 or earlier (and other build systems)
As part of the build process, the Android Gradle plugin keeps a copy of the unstripped libraries in a project directory. This directory structure is similar to the following:
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
Zip up the contents of this directory:
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
Manually upload the
symbols.zip
file to the Google Play Console.
Shrink your resources
Resource shrinking works only in conjunction with code shrinking. After the code shrinker removes all unused code, the resource shrinker can identify which resources the app still uses. This is especially true when you add code libraries that include resources—you must remove unused library code so the library resources become unreferenced and, thus, removable by the resource shrinker.
To enable resource shrinking, set the shrinkResources
property to true
in your build script (alongside minifyEnabled
for code shrinking). به عنوان مثال:
کاتلین
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
شیار
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
If you haven't already built your app using minifyEnabled
for code shrinking, then try that before enabling shrinkResources
, because you might need to edit your proguard-rules.pro
file to keep classes or methods that are created or invoked dynamically before you start removing منابع
Customize which resources to keep
If there are specific resources you wish to keep or discard, create an XML file in your project with a <resources>
tag and specify each resource to keep in the tools:keep
attribute and each resource to discard in the tools:discard
attribute. Both attributes accept a comma-separated list of resource names. You can use the asterisk character as a wild card.
به عنوان مثال:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
Save this file in your project resources, for example, at res/raw/my.package.keep.xml
. The build does not package this file into your app.
Note: Make sure to use a unique name for the keep
file. When different libraries get linked together their keep rules would conflict otherwise, causing potential issues with ignored rules or unneeded kept resources.
Specifying which resources to discard might seem silly when you could instead delete them, but this can be useful when using build variants. For example, you might put all your resources into the common project directory, then create a different my.package.build.variant.keep.xml
file for each build variant when you know that a given resource appears to be used in code (and therefore not removed by the shrinker) but you know it actually won't be used for the given build variant. It's also possible that the build tools incorrectly identified a resource as needed, which is possible because the compiler adds the resource IDs inline and then the resource analyzer might not know the difference between a genuinely referenced resource and an integer value in the code that happens to ارزش یکسانی دارند
Enable strict reference checks
Normally, the resource shrinker can accurately determine whether a resource is used. However, if your code makes a call to Resources.getIdentifier()
(or if any of your libraries do that—the AppCompat library does), that means your code is looking up resource names based on dynamically-generated strings. When you do this, the resource shrinker behaves defensively by default and marks all resources with a matching name format as potentially used and unavailable for removal.
For example, the following code causes all resources with the img_
prefix to be marked as used.
کاتلین
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
جاوا
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
The resource shrinker also looks through all the string constants in your code, as well as various res/raw/
resources, looking for resource URLs in a format similar to file:///android_res/drawable//ic_plus_anim_016.png
. If it finds strings like this or others that look like they could be used to construct URLs like this, it doesn't remove them.
These are examples of the safe shrinking mode that is enabled by default. You can, however, turn off this "better safe than sorry" handling, and specify that the resource shrinker keep only resources that it's certain are used. To do this, set shrinkMode
to strict
in the keep.xml
file, as follows:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
If you do enable strict shrinking mode and your code also references resources with dynamically-generated strings, as shown above, then you must manually keep those resources using the tools:keep
attribute.
Remove unused alternative resources
The Gradle resource shrinker removes only resources that are not referenced by your app code, which means it will not remove alternative resources for different device configurations. If necessary, you can use the Android Gradle plugin's resConfigs
property to remove alternative resource files that your app does not need.
For example, if you are using a library that includes language resources (such as AppCompat or Google Play Services), then your app includes all translated language strings for the messages in those libraries whether the rest of your app is translated to the same languages or نه If you'd like to keep only the languages that your app officially supports, you can specify those languages using the resConfig
property. Any resources for languages not specified are removed.
The following snippet shows how to limit your language resources to just English and French:
کاتلین
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
شیار
android { defaultConfig { ... resConfigs "en", "fr" } }
When releasing an app using the Android App Bundle format, by default only languages configured on a user's device are downloaded when installing the app. Similarly, only resources matching the device's screen density, and native libraries matching the device's ABI are included in the download. For more information refer to the Android App Bundle configuration .
For legacy apps releasing with APKs (created before August 2021), you can customize which screen density or ABI resources to include in your APK by building multiple APKs that each target a different device configuration.
Merge duplicate resources
By default, Gradle also merges identically named resources, such as drawables with the same name that might be in different resource folders. This behavior is not controlled by the shrinkResources
property and cannot be disabled, because it is necessary to avoid errors when multiple resources match the name your code is looking up.
Resource merging occurs only when two or more files share an identical resource name, type, and qualifier. Gradle selects which file it considers to be the best choice among the duplicates (based on a priority order described below) and passes only that one resource to the AAPT for distribution in the final artifact.
Gradle looks for duplicate resources in the following locations:
- The main resources, associated with the main source set, generally located in
src/main/res/
. - The variant overlays, from the build type and build flavors.
- The library project dependencies.
Gradle merges duplicate resources in the following cascading priority order:
Dependencies → Main → Build flavor → Build type
For example, if a duplicate resource appears in both your main resources and a build flavor, Gradle selects the one in the build flavor.
If identical resources appear in the same source set, Gradle cannot merge them and emits a resource merge error. This can happen if you define multiple source sets in the sourceSet
property of your build.gradle.kts
file—for example if both src/main/res/
and src/main/res2/
contain identical resources.
Obfuscate your code
The purpose of obfuscation is to reduce your app size by shortening the names of your app's classes, methods, and fields. The following is an example of obfuscation using R8:
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
While obfuscation does not remove code from your app, significant size savings can be seen in apps with DEX files that index many classes, methods, and fields. However, as obfuscation renames different parts of your code, certain tasks, such as inspecting stack traces, require additional tools. To understand your stacktrace after obfuscation, read the section about how to decode an obfuscated stack trace .
Additionally, if your code relies on predictable naming for your app's methods and classes—when using reflection, for example, you should treat those signatures as entry points and specify keep rules for them, as described in the section about how to customize which code to نگه داشتن . Those keep rules tell R8 to not only keep that code in your app's final DEX but also retain its original naming.
Decode an obfuscated stack trace
After R8 obfuscates your code, understanding a stack trace is difficult (if not impossible) because names of classes and methods might have been changed. To obtain the original stack trace you should retrace the stack trace .
بهینه سازی کد
In order to optimize your app even further, R8 inspects your code at a deeper level to remove more unused code or, where possible, rewrite your code to make it less verbose. The following are a few examples of such optimizations:
- If your code never takes the
else {}
branch for a given if/else statement, R8 might remove the code for theelse {}
branch. - If your code calls a method in only a few places, R8 might remove the method and inline it at the few call sites.
- If R8 determines that a class has only one unique subclass, and the class itself is not instantiated (for example, an abstract base class only used by one concrete implementation class), then R8 can combine the two classes and remove a class from the app .
- To learn more, read the R8 optimization blog posts by Jake Wharton.
R8 does not allow you to disable or enable discrete optimizations, or modify the behavior of an optimization. In fact, R8 ignores any ProGuard rules that attempt to modify default optimizations, such as -optimizations
and -optimizationpasses
. This restriction is important because, as R8 continues to improve, maintaining a standard behavior for optimizations helps the Android Studio team easily troubleshoot and resolve any issues that you might encounter.
Note that enabling optimization will change the stack traces for your application. For example, inlining will remove stack frames. See the section on retracing to learn how to obtain the original stack traces.
Impact on runtime performance
If shrinking, obfuscation, and optimization are all enabled, R8 will improve runtime performance of code (including startup and frame time on the UI thread) by up to 30%. Disabling any of these drastically limits the set of optimizations R8 uses.
If R8 is enabled, you should also create Startup Profiles for even better startup performance.
Enable more aggressive optimizations
R8 includes a set of additional optimizations (referred to as "full mode") which makes it behave differently from ProGuard. These optimizations are enabled by default since Android Gradle plugin version 8.0.0 .
You can disable these additional optimizations by including the following in your project's gradle.properties
file:
android.enableR8.fullMode=false
Because the additional optimizations make R8 behave differently from ProGuard, they may require you to include additional ProGuard rules to avoid runtime issues if you're using rules designed for ProGuard. For example, say that your code references a class through the Java Reflection API. When not using "full mode," R8 assumes that you intend to examine and manipulate objects of that class at runtime—even if your code actually does not—and it automatically keeps the class and its static initializer.
However, when using "full mode", R8 does not make this assumption and, if R8 asserts that your code otherwise never uses the class at runtime, it removes the class from your app's final DEX. That is, if you want to keep the class and its static initializer, you need to include a keep rule in your rules file to do that.
If you encounter any issues while using R8's "full mode", refer to the R8 FAQ page for a possible solution. If you are unable to resolve the issue, please report a bug .
Retracing stacktraces
Code processed by R8 is changed in various ways that can make stack traces harder to understand because the stack traces won't exactly correspond to the source code. This can be the case for changes to the line numbers when debugging information is not kept. It can be due to optimizations such as inlining and outlining. The largest contributor is obfuscation where even the classes and methods will change names.
To recover the original stack trace, R8 provides the retrace command-line tool, which is bundled with the command-line tools package .
To support retracing of your application's stack traces, you should ensure the build retains sufficient information to retrace with by adding the following rules to your module's proguard-rules.pro
file:
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
The LineNumberTable
attribute retains positional information in methods such that those positions are printed in stack traces. The SourceFile
attribute ensures that all potential runtimes actually print the positional info. The -renamesourcefileattribute
directive sets the source file name in stack traces to just SourceFile
. The actual original source file name is not required when retracing because the mapping file contains the original source file.
R8 creates a mapping.txt
file each time it runs, which contains the information needed to map stack traces back to the original stack traces. Android Studio saves the file in the <module-name> /build/outputs/mapping/ <build-type> /
directory.
When publishing your app on Google Play, you can upload the mapping.txt
file for each version of your app. When publishing using Android App Bundles this file is included automatically as part of the app bundle content. Then Google Play will retrace incoming stack traces from user-reported issues so you can review them in the Play Console. For more information, see the Help Center article about how to deobfuscate crash stack traces .
Troubleshoot with R8
This section describes some strategies for troubleshooting issues when enabling shrinking, obfuscation, and optimization using R8. If you do not find a solution to your issue below, also read the R8 FAQ page and ProGuard's troubleshooting guide .
Generate a report of removed (or kept) code
To help you troubleshoot certain R8 issues, it may be useful to see a report of all the code that R8 removed from your app. For each module for which you want to generate this report, add -printusage <output-dir>/usage.txt
to your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of removed code looks similar to the following:
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
If instead you want to see a report of the entry points that R8 determines from your project's keep rules , include -printseeds <output-dir>/seeds.txt
in your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of kept entry points looks similar to the following:
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
Troubleshoot resource shrinking
When you shrink resources, the Build window shows a summary of the resources that are removed from the app. (You need to first click Toggle view on the left side of the window to display detailed text output from Gradle.) For example:
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
Gradle also creates a diagnostic file named resources.txt
in <module-name>/build/outputs/mapping/release/
(the same folder as ProGuard's output files). This file includes details such as which resources reference other resources and which resources are used or removed.
For example, to find out why @drawable/ic_plus_anim_016
is still in your app, open the resources.txt
file and search for that file name. You might find that it's referenced from another resource, as follows:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
You now need to know why @drawable/add_schedule_fab_icon_anim
is reachable—and if you search upwards you'll find that resource is listed under "The root reachable resources are:". This means there is a code reference to add_schedule_fab_icon_anim
(that is, its R.drawable ID was found in the reachable code).
If you are not using strict checking, resource IDs can be marked as reachable if there are string constants that look like they might be used to construct resource names for dynamically loaded resources. In that case, if you search the build output for the resource name, you might find a message like this:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
If you see one of these strings and you are certain that the string is not being used to load the given resource dynamically, you can use the tools:discard
attribute to inform the build system to remove it, as described in the section about how to customize which resources to keep .
To make your app as small and fast as possible, you should optimize and minify your release build with isMinifyEnabled = true
.
Doing so enables shrinking , which removes unused code and resources; obfuscation , which shortens the names of your app's classes and members; and optimization , which applies more aggressive strategies to further reduce the size and improve the performance of your app. This page describes how R8 performs these compile-time tasks for your project and how you can customize them.
When you build your project using Android Gradle plugin 3.4.0 or higher, the plugin no longer uses ProGuard to perform compile-time code optimization. Instead, the plugin works with the R8 compiler to handle the following compile-time tasks:
- Code shrinking (or tree-shaking): detects and safely removes unused classes, fields, methods, and attributes from your app and its library dependencies (making it a valuable tool for working around the 64k reference limit ). For example, if you use only a few APIs of a library dependency, shrinking can identify library code that your app is not using and remove only that code from your app. To learn more, go to the section about how to shrink your code .
- Resource shrinking: removes unused resources from your packaged app, including unused resources in your app's library dependencies. این در ارتباط با کوچک کردن کد کار می کند به طوری که پس از حذف کدهای استفاده نشده، هر منبعی که دیگر به آن ارجاع داده نمی شود، می تواند با خیال راحت حذف شود. To learn more, go to the section about how to shrink your resources .
- Optimization: inspects and rewrites your code to improve runtime performance and further reduce the size of your app's DEX files. This improves runtime performance of code by up to 30%, drastically improving startup and frame timing. For example, if R8 detects that the
else {}
branch for a given if/else statement is never taken, R8 removes the code for theelse {}
branch. To learn more, go to the section about code optimization . - Obfuscation (or identifier minification): shortens the name of classes and members, which results in reduced DEX file sizes. To learn more, go to the section about how to obfuscate your code .
When building the release version of your app, R8 can be configured to perform the compile-time tasks described above for you. You can also disable certain tasks or customize R8's behavior through ProGuard rules files. In fact, R8 works with all of your existing ProGuard rules files , so updating the Android Gradle plugin to use R8 should not require you to change your existing rules.
کوچک کردن، مبهم سازی و بهینه سازی را فعال کنید
When you use Android Studio 3.4 or Android Gradle plugin 3.4.0 and higher, R8 is the default compiler that converts your project's Java bytecode into the DEX format that runs on the Android platform. However, when you create a new project using Android Studio, shrinking, obfuscation, and code optimization is not enabled by default. That's because these compile-time optimizations increase the build time of your project and might introduce bugs if you do not sufficiently customize which code to keep .
So, it's best to enable these compile-time tasks when building the final version of your app that you test prior to publishing. To enable shrinking, obfuscation, and optimization, include the following in your project-level build script.
کاتلین
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
شیار
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. minifyEnabled true // Enables resource shrinking, which is performed by the // Android Gradle plugin. shrinkResources true // Includes the default ProGuard rules files that are packaged with // the Android Gradle plugin. To learn more, go to the section about // R8 configuration files. proguardFiles getDefaultProguardFile( 'proguard-android-optimize.txt'), 'proguard-rules.pro' } } ... }
R8 configuration files
R8 uses ProGuard rules files to modify its default behavior and better understand your app's structure, such as the classes that serve as entry points into your app's code. Although you can modify some of these rules files, some rules may be generated automatically by compile-time tools, such as AAPT2, or inherited from your app's library dependencies. The table below describes the sources of ProGuard rules files that R8 uses.
منبع | مکان | توضیحات |
اندروید استودیو | <module-dir>/proguard-rules.pro | When you create a new module using Android Studio, the IDE creates a proguard-rules.pro file in the root directory of that module.به طور پیش فرض، این فایل هیچ قانونی را اعمال نمی کند. So, include your own ProGuard rules here, such as your custom keep rules . |
Android Gradle plugin | Generated by the Android Gradle plugin at compile time. | The Android Gradle plugin generates proguard-android-optimize.txt , which includes rules that are useful to most Android projects and enables @Keep* annotations .By default, when creating a new module using Android Studio, the module-level build script includes this rules file in your release build for you. Note: The Android Gradle plugin includes additional predefined ProGuard rules files, but it is recommended that you use |
Library dependencies | In an AAR library: In a JAR library: In addition to these locations, Android Gradle plugin 3.6 or higher also supports targeted shrink rules . | If an AAR or JAR library is published with its own rules file, and you include that library as a compile-time dependency, R8 automatically applies those rules when compiling your project. In addition to conventional ProGuard rules, Android Gradle plugin 3.6 or higher also supports targeted shrink rules . These are rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. Using rules files that are packaged with libraries is useful if certain rules are required for the library to function properly—that is, the library developer has performed the troubleshooting steps for you. However, you should be aware that, because the rules are additive , certain rules that a library dependency includes cannot be removed and might impact the compilation of other parts of your app. For example, if a library includes a rule to disable code optimizations, that rule disables optimizations for your entire project. |
Android Asset Package Tool 2 (AAPT2) | After building your project with minifyEnabled true : <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt | AAPT2 generates keep rules based on references to classes in your app's manifest, layouts, and other app resources. For example, AAPT2 includes a keep rule for each Activity that you register in your app's manifest as an entry point. |
Custom configuration files | By default, when you create a new module using Android Studio, the IDE creates <module-dir>/proguard-rules.pro for you to add your own rules. | You can include additional configurations , and R8 applies them at compile-time. |
When you set the minifyEnabled
property to true
, R8 combines rules from all the available sources listed above. This is important to remember when you troubleshoot with R8 , because other compile-time dependencies, such as library dependencies, may introduce changes to the R8 behavior that you do not know about.
To output a full report of all the rules that R8 applies when building your project, include the following in your module's proguard-rules.pro
file:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
Targeted shrink rules
Android Gradle plugin 3.6 or higher supports libraries' rules that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. This allows library developers to tailor their rules to work optimally in projects that use new shrinker versions, while allowing existing rules to continue to be used in projects with older shrinker versions.
To specify targeted shrink rules, library developers will need to include them at specific locations inside an AAR or JAR library, as described below.
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
That means targeted shrink rules are stored in the META-INF/com.android.tools
directory of a JAR or in the META-INF/com.android.tools
directory inside classes.jar
of an AAR.
Under that directory, there can be multiple directories with names in the form of r8-from-<X>-upto-<Y>
or proguard-from-<X>-upto-<Y>
to indicate which versions of which shrinker the rules inside the directories are written for. Note that the -from-<X>
and -upto-<Y>
parts are optional, the <Y>
version is exclusive , and the version ranges must be continuous.
For example, r8-upto-8.0.0
, r8-from-8.0.0-upto-8.2.0
, and r8-from-8.2.0
form a valid set of targeted shrink rules. The rules under the r8-from-8.0.0-upto-8.2.0
directory will be used by R8 from version 8.0.0 up to but not including version 8.2.0.
Given that information, Android Gradle plugin 3.6 or higher will select the rules from the matching R8 directories. If a library does not specify targeted shrink rules, the Android Gradle plugin will select the rules from the legacy locations ( proguard.txt
for an AAR or META-INF/proguard/<ProGuard-rules-file>
for a JAR).
Library developers can choose to include either targeted shrink rules or legacy ProGuard rules in their libraries, or both types if they want to maintain compatibility with Android Gradle plugin older than 3.6 or other tools.
Include additional configurations
When you create a new project or module using Android Studio, the IDE creates a <module-dir>/proguard-rules.pro
file for you to include your own rules. You can also include additional rules from other files by adding them to the proguardFiles
property in your module's build script.
For example, you can add rules that are specific to each build variant by adding another proguardFiles
property in the corresponding productFlavor
block. The following Gradle file adds flavor2-rules.pro
to the flavor2
product flavor. Now, flavor2
uses all three ProGuard rules because those from the release
block are also applied.
Additionally, you can add the testProguardFiles
property, which specifies a list of ProGuard files that are included in the test APK only:
کاتلین
android { ... buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). "proguard-rules.pro" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
شیار
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // List additional ProGuard rules for the given build type here. By default, // Android Studio creates and includes an empty rules file for you (located // at the root directory of each module). 'proguard-rules.pro' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-proguard-rules.pro' } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
Shrink your code
Code shrinking with R8 is enabled by default when you set the minifyEnabled
property to true
.
Code shrinking (also known as tree shaking), is the process of removing code that R8 determines is not required at runtime. This process can greatly reduce your app's size if, for example, your app includes many library dependencies but utilizes only a small part of their functionality.
To shrink your app's code, R8 first determines all entry points into your app's code based on the combined set of configuration files . These entry points include all classes that the Android platform may use to open your app's Activities or services. Starting from each entry point, R8 inspects your app's code to build a graph of all methods, member variables, and other classes that your app might access at runtime. Code that is not connected to that graph is considered unreachable and may be removed from the app.
Figure 1 shows an app with a runtime library dependency. While inspecting the app's code, R8 determines that methods foo()
, faz()
, and bar()
are reachable from the MainActivity.class
entry point. However, class OkayApi.class
or its method baz()
is never used by your app at runtime, and R8 removes that code when shrinking your app.
R8 determines entry points through -keep
rules in the project's R8 configuration files . That is, keep rules specify classes that R8 should not discard when shrinking your app, and R8 considers those classes as possible entry points into your app. The Android Gradle plugin and AAPT2 automatically generate keep rules that are required by most app projects for you, such as your app's activities, views, and services. However, if you need to customize this default behavior with additional keep rules, read the section about how to customize which code to keep .
If instead you are interested only in reducing the size of your app's resources, skip to the section about how to shrink your resources .
Note that if a library project is shrunk, an app that depends on that library includes shrunk library classes. You might need to adjust library keep rules if there are missing classes in the library APK. If you're building and publishing a library in AAR format, local JAR files that your library depends on aren't shrunk in the AAR file.
Customize which code to keep
For most situations, the default ProGuard rules file ( proguard-android-optimize.txt
) is sufficient for R8 to remove only the unused code. However, some situations are difficult for R8 to analyze correctly and it might remove code your app actually needs. Some examples of when it might incorrectly remove code include:
- When your app calls a method from the Java Native Interface (JNI)
- When your app looks up code at runtime (such as with reflection)
Testing your app should reveal any errors caused by inappropriately removed code, but you can also inspect what code was removed by generating a report of removed code .
To fix errors and force R8 to keep certain code, add a -keep
line in the ProGuard rules file. به عنوان مثال:
-keep public class MyClass
Alternatively, you can add the @Keep
annotation to the code you want to keep. Adding @Keep
on a class keeps the entire class as-is. Adding it on a method or field will keep the method/field (and its name) as well as the class name intact. Note that this annotation is available only when using the AndroidX Annotations Library and when you include the ProGuard rules file that is packaged with the Android Gradle plugin, as described in the section about how to enable shrinking .
There are many considerations you should make when using the -keep
option; for more information about customizing your rules file, read the ProGuard Manual . The Troubleshooting section outlines other common problems you might encounter when your code gets stripped away.
Strip native libraries
By default, native code libraries are stripped in release builds of your app. This stripping consists of removing the symbol table and debugging information contained in any native libraries used by your app. Stripping native code libraries results in significant size savings; however, it's impossible to diagnose crashes on the Google Play Console due to the missing information (such as class and function names).
Native crash support
The Google Play Console reports native crashes under Android vitals . With a few steps, you can generate and upload a native debug symbols file for your app. This file enables symbolicated native crash stack traces (that include class and function names) in Android vitals to help you debug your app in production. These steps vary depending on the version of the Android Gradle plugin used in your project and the build output of your project.
Android Gradle plugin version 4.1 or later
If your project builds an Android App Bundle, you can automatically include the native debug symbols file in it. To include this file in release builds, add the following to your app's build.gradle.kts
file:
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
Select the debug symbol level from the following:
- Use
SYMBOL_TABLE
to get function names in the Play Console's symbolicated stack traces. This level supports tombstones . - Use
FULL
to get function names, files, and line numbers in the Play Console's symbolicated stack traces.
If your project builds an APK, use the build.gradle.kts
build setting shown earlier to generate the native debug symbols file separately. Manually upload the native debug symbols file to the Google Play Console. As part of the build process, the Android Gradle plugin outputs this file in the following project location:
app/build/outputs/native-debug-symbols/ variant-name /native-debug-symbols.zip
Android Gradle plugin version 4.0 or earlier (and other build systems)
As part of the build process, the Android Gradle plugin keeps a copy of the unstripped libraries in a project directory. This directory structure is similar to the following:
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
Zip up the contents of this directory:
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
Manually upload the
symbols.zip
file to the Google Play Console.
Shrink your resources
Resource shrinking works only in conjunction with code shrinking. After the code shrinker removes all unused code, the resource shrinker can identify which resources the app still uses. This is especially true when you add code libraries that include resources—you must remove unused library code so the library resources become unreferenced and, thus, removable by the resource shrinker.
To enable resource shrinking, set the shrinkResources
property to true
in your build script (alongside minifyEnabled
for code shrinking). به عنوان مثال:
کاتلین
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
شیار
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
If you haven't already built your app using minifyEnabled
for code shrinking, then try that before enabling shrinkResources
, because you might need to edit your proguard-rules.pro
file to keep classes or methods that are created or invoked dynamically before you start removing منابع
Customize which resources to keep
If there are specific resources you wish to keep or discard, create an XML file in your project with a <resources>
tag and specify each resource to keep in the tools:keep
attribute and each resource to discard in the tools:discard
attribute. Both attributes accept a comma-separated list of resource names. You can use the asterisk character as a wild card.
به عنوان مثال:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*" tools:discard="@layout/unused2" />
Save this file in your project resources, for example, at res/raw/my.package.keep.xml
. The build does not package this file into your app.
Note: Make sure to use a unique name for the keep
file. When different libraries get linked together their keep rules would conflict otherwise, causing potential issues with ignored rules or unneeded kept resources.
Specifying which resources to discard might seem silly when you could instead delete them, but this can be useful when using build variants. For example, you might put all your resources into the common project directory, then create a different my.package.build.variant.keep.xml
file for each build variant when you know that a given resource appears to be used in code (and therefore not removed by the shrinker) but you know it actually won't be used for the given build variant. It's also possible that the build tools incorrectly identified a resource as needed, which is possible because the compiler adds the resource IDs inline and then the resource analyzer might not know the difference between a genuinely referenced resource and an integer value in the code that happens to ارزش یکسانی دارند
Enable strict reference checks
Normally, the resource shrinker can accurately determine whether a resource is used. However, if your code makes a call to Resources.getIdentifier()
(or if any of your libraries do that—the AppCompat library does), that means your code is looking up resource names based on dynamically-generated strings. When you do this, the resource shrinker behaves defensively by default and marks all resources with a matching name format as potentially used and unavailable for removal.
For example, the following code causes all resources with the img_
prefix to be marked as used.
کاتلین
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
جاوا
String name = String.format("img_%1d", angle + 1); res = getResources().getIdentifier(name, "drawable", getPackageName());
The resource shrinker also looks through all the string constants in your code, as well as various res/raw/
resources, looking for resource URLs in a format similar to file:///android_res/drawable//ic_plus_anim_016.png
. If it finds strings like this or others that look like they could be used to construct URLs like this, it doesn't remove them.
These are examples of the safe shrinking mode that is enabled by default. You can, however, turn off this "better safe than sorry" handling, and specify that the resource shrinker keep only resources that it's certain are used. To do this, set shrinkMode
to strict
in the keep.xml
file, as follows:
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:tools="http://schemas.android.com/tools" tools:shrinkMode="strict" />
If you do enable strict shrinking mode and your code also references resources with dynamically-generated strings, as shown above, then you must manually keep those resources using the tools:keep
attribute.
Remove unused alternative resources
The Gradle resource shrinker removes only resources that are not referenced by your app code, which means it will not remove alternative resources for different device configurations. If necessary, you can use the Android Gradle plugin's resConfigs
property to remove alternative resource files that your app does not need.
For example, if you are using a library that includes language resources (such as AppCompat or Google Play Services), then your app includes all translated language strings for the messages in those libraries whether the rest of your app is translated to the same languages or نه If you'd like to keep only the languages that your app officially supports, you can specify those languages using the resConfig
property. Any resources for languages not specified are removed.
The following snippet shows how to limit your language resources to just English and French:
کاتلین
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
شیار
android { defaultConfig { ... resConfigs "en", "fr" } }
When releasing an app using the Android App Bundle format, by default only languages configured on a user's device are downloaded when installing the app. Similarly, only resources matching the device's screen density, and native libraries matching the device's ABI are included in the download. For more information refer to the Android App Bundle configuration .
For legacy apps releasing with APKs (created before August 2021), you can customize which screen density or ABI resources to include in your APK by building multiple APKs that each target a different device configuration.
Merge duplicate resources
By default, Gradle also merges identically named resources, such as drawables with the same name that might be in different resource folders. This behavior is not controlled by the shrinkResources
property and cannot be disabled, because it is necessary to avoid errors when multiple resources match the name your code is looking up.
Resource merging occurs only when two or more files share an identical resource name, type, and qualifier. Gradle selects which file it considers to be the best choice among the duplicates (based on a priority order described below) and passes only that one resource to the AAPT for distribution in the final artifact.
Gradle looks for duplicate resources in the following locations:
- The main resources, associated with the main source set, generally located in
src/main/res/
. - The variant overlays, from the build type and build flavors.
- The library project dependencies.
Gradle merges duplicate resources in the following cascading priority order:
Dependencies → Main → Build flavor → Build type
For example, if a duplicate resource appears in both your main resources and a build flavor, Gradle selects the one in the build flavor.
If identical resources appear in the same source set, Gradle cannot merge them and emits a resource merge error. This can happen if you define multiple source sets in the sourceSet
property of your build.gradle.kts
file—for example if both src/main/res/
and src/main/res2/
contain identical resources.
Obfuscate your code
The purpose of obfuscation is to reduce your app size by shortening the names of your app's classes, methods, and fields. The following is an example of obfuscation using R8:
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
While obfuscation does not remove code from your app, significant size savings can be seen in apps with DEX files that index many classes, methods, and fields. However, as obfuscation renames different parts of your code, certain tasks, such as inspecting stack traces, require additional tools. To understand your stacktrace after obfuscation, read the section about how to decode an obfuscated stack trace .
Additionally, if your code relies on predictable naming for your app's methods and classes—when using reflection, for example, you should treat those signatures as entry points and specify keep rules for them, as described in the section about how to customize which code to نگه داشتن . Those keep rules tell R8 to not only keep that code in your app's final DEX but also retain its original naming.
Decode an obfuscated stack trace
After R8 obfuscates your code, understanding a stack trace is difficult (if not impossible) because names of classes and methods might have been changed. To obtain the original stack trace you should retrace the stack trace .
بهینه سازی کد
In order to optimize your app even further, R8 inspects your code at a deeper level to remove more unused code or, where possible, rewrite your code to make it less verbose. The following are a few examples of such optimizations:
- If your code never takes the
else {}
branch for a given if/else statement, R8 might remove the code for theelse {}
branch. - If your code calls a method in only a few places, R8 might remove the method and inline it at the few call sites.
- If R8 determines that a class has only one unique subclass, and the class itself is not instantiated (for example, an abstract base class only used by one concrete implementation class), then R8 can combine the two classes and remove a class from the app .
- To learn more, read the R8 optimization blog posts by Jake Wharton.
R8 does not allow you to disable or enable discrete optimizations, or modify the behavior of an optimization. In fact, R8 ignores any ProGuard rules that attempt to modify default optimizations, such as -optimizations
and -optimizationpasses
. This restriction is important because, as R8 continues to improve, maintaining a standard behavior for optimizations helps the Android Studio team easily troubleshoot and resolve any issues that you might encounter.
Note that enabling optimization will change the stack traces for your application. For example, inlining will remove stack frames. See the section on retracing to learn how to obtain the original stack traces.
Impact on runtime performance
If shrinking, obfuscation, and optimization are all enabled, R8 will improve runtime performance of code (including startup and frame time on the UI thread) by up to 30%. Disabling any of these drastically limits the set of optimizations R8 uses.
If R8 is enabled, you should also create Startup Profiles for even better startup performance.
Enable more aggressive optimizations
R8 includes a set of additional optimizations (referred to as "full mode") which makes it behave differently from ProGuard. These optimizations are enabled by default since Android Gradle plugin version 8.0.0 .
You can disable these additional optimizations by including the following in your project's gradle.properties
file:
android.enableR8.fullMode=false
Because the additional optimizations make R8 behave differently from ProGuard, they may require you to include additional ProGuard rules to avoid runtime issues if you're using rules designed for ProGuard. For example, say that your code references a class through the Java Reflection API. When not using "full mode," R8 assumes that you intend to examine and manipulate objects of that class at runtime—even if your code actually does not—and it automatically keeps the class and its static initializer.
However, when using "full mode", R8 does not make this assumption and, if R8 asserts that your code otherwise never uses the class at runtime, it removes the class from your app's final DEX. That is, if you want to keep the class and its static initializer, you need to include a keep rule in your rules file to do that.
If you encounter any issues while using R8's "full mode", refer to the R8 FAQ page for a possible solution. If you are unable to resolve the issue, please report a bug .
Retracing stacktraces
Code processed by R8 is changed in various ways that can make stack traces harder to understand because the stack traces won't exactly correspond to the source code. This can be the case for changes to the line numbers when debugging information is not kept. It can be due to optimizations such as inlining and outlining. The largest contributor is obfuscation where even the classes and methods will change names.
To recover the original stack trace, R8 provides the retrace command-line tool, which is bundled with the command-line tools package .
To support retracing of your application's stack traces, you should ensure the build retains sufficient information to retrace with by adding the following rules to your module's proguard-rules.pro
file:
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
The LineNumberTable
attribute retains positional information in methods such that those positions are printed in stack traces. The SourceFile
attribute ensures that all potential runtimes actually print the positional info. The -renamesourcefileattribute
directive sets the source file name in stack traces to just SourceFile
. The actual original source file name is not required when retracing because the mapping file contains the original source file.
R8 creates a mapping.txt
file each time it runs, which contains the information needed to map stack traces back to the original stack traces. Android Studio saves the file in the <module-name> /build/outputs/mapping/ <build-type> /
directory.
When publishing your app on Google Play, you can upload the mapping.txt
file for each version of your app. When publishing using Android App Bundles this file is included automatically as part of the app bundle content. Then Google Play will retrace incoming stack traces from user-reported issues so you can review them in the Play Console. For more information, see the Help Center article about how to deobfuscate crash stack traces .
Troubleshoot with R8
This section describes some strategies for troubleshooting issues when enabling shrinking, obfuscation, and optimization using R8. If you do not find a solution to your issue below, also read the R8 FAQ page and ProGuard's troubleshooting guide .
Generate a report of removed (or kept) code
To help you troubleshoot certain R8 issues, it may be useful to see a report of all the code that R8 removed from your app. For each module for which you want to generate this report, add -printusage <output-dir>/usage.txt
to your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of removed code looks similar to the following:
androidx.drawerlayout.R$attr
androidx.vectordrawable.R
androidx.appcompat.app.AppCompatDelegateImpl
public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
public boolean hasWindowFeature(int)
public void setHandleNativeActionModesEnabled(boolean)
android.view.ViewGroup getSubDecor()
public void setLocalNightMode(int)
final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
private static final boolean DEBUG
private static final java.lang.String KEY_LOCAL_NIGHT_MODE
static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
...
If instead you want to see a report of the entry points that R8 determines from your project's keep rules , include -printseeds <output-dir>/seeds.txt
in your custom rules file. When you enable R8 and build your app, R8 outputs a report with the path and file name you specified. The report of kept entry points looks similar to the following:
com.example.myapplication.MainActivity
androidx.appcompat.R$layout: int abc_action_menu_item_layout
androidx.appcompat.R$attr: int activityChooserViewStyle
androidx.appcompat.R$styleable: int MenuItem_android_id
androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
androidx.lifecycle.FullLifecycleObserverAdapter
...
Troubleshoot resource shrinking
When you shrink resources, the Build window shows a summary of the resources that are removed from the app. (You need to first click Toggle view on the left side of the window to display detailed text output from Gradle.) For example:
:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
Gradle also creates a diagnostic file named resources.txt
in <module-name>/build/outputs/mapping/release/
(the same folder as ProGuard's output files). This file includes details such as which resources reference other resources and which resources are used or removed.
For example, to find out why @drawable/ic_plus_anim_016
is still in your app, open the resources.txt
file and search for that file name. You might find that it's referenced from another resource, as follows:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
You now need to know why @drawable/add_schedule_fab_icon_anim
is reachable—and if you search upwards you'll find that resource is listed under "The root reachable resources are:". This means there is a code reference to add_schedule_fab_icon_anim
(that is, its R.drawable ID was found in the reachable code).
If you are not using strict checking, resource IDs can be marked as reachable if there are string constants that look like they might be used to construct resource names for dynamically loaded resources. In that case, if you search the build output for the resource name, you might find a message like this:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
used because it format-string matches string pool constant ic_plus_anim_%1$d.
If you see one of these strings and you are certain that the string is not being used to load the given resource dynamically, you can use the tools:discard
attribute to inform the build system to remove it, as described in the section about how to customize which resources to keep .