ProGuard و R8 — ابزارهای مبهمسازی، کوچکسازی و بهینهسازی برای برنامههای Android هستند. ProGuard که در سال 2002 ایجاد شد، مدت طولانی استاندارد دوفاکتو برای محافظت از کد Java بود. R8 — جانشین آن است که توسط Google توسعه یافته و از AGP 3.4 به بعد در Android Gradle Plugin تعبیه شده است. هر دو ابزار اندازه APK را کاهش میدهند، کد مرده را حذف میکنند و مهندسی معکوس را دشوارتر میسازند. طبق Android Developers، R8 در کیفیت مشابه مبهمسازی، ۲–۳ برابر سریعتر از ProGuard بیلد را انجام میدهد.
مهمترین نکات
ProGuard — یک ابزار متنباز (Apache 2.0) برای مبهمسازی، کوچکسازی، بهینهسازی و پیشتأیید بایتکد Java است. توسط اریک لافورژ در سال ۲۰۰۲ در چارچوب پروژه SourceForge توسعه یافت. ProGuard کلاسهای Java کامپایل شده (.class) یا بایگانیهای JAR را به عنوان ورودی دریافت میکند و کلاسهای پردازش شده با همان فرمت، اما با اندازه کوچکتر و عناصر تغییر نام یافته تحویل میدهد.
مدت طولانی ProGuard تنها استاندارد محافظت از برنامههای Android در برابر مهندسی معکوس بود. Google رسماً استفاده از آن را در Android SDK توصیه میکرد و پیکربندی پیشفرض را در فایل proguard-android-optimize.txt درون SDK tools ارائه میداد. ProGuard به عنوان یک ابزار مجزا پس از کامپایل کد Java به بایتکد و قبل از بستهبندی به DEX اجرا میشد.
ProGuard از چهار فاز متوالی تشکیل شده است: shrink (حذف کلاسهای استفاده نشده)، optimize (بهینهسازی بایتکد — درونخطی کردن، حذف کد مرده)، obfuscate (تغییر نام کلاسها، متدها و فیلدها به نامهای کوتاه)، preverify (بررسی سازگاری با JVM). هر فاز با قوانین جداگانه از فایلهای پیکربندی کنترل میشود.
در مرحله مبهمسازی، ProGuard یک فایل mapping (mapping.txt) تولید میکند که نامهای اصلی را به نامهای مبهمسازی شده نگاشت میکند. این فایل برای رمزگشایی لاگهای crash از بیلدهای release با ابزار retrace حیاتی است. بدون فایل mapping، stack trace به مجموعهای از حروف a()، b()، c() تبدیل میشود بدون امکان بازیابی زمینه اصلی.
| فاز ProGuard | هدف | نتیجه |
|---|---|---|
| Shrink | تحلیل گراف فراخوانی و حذف کد مرده | کاهش تعداد کلاسها در APK |
| Optimize | درونخطی کردن متدها، حذف پارامترهای استفاده نشده | تسریع اجرای کد |
| Obfuscate | تغییر نام کلاسها، فیلدها و متدها | محافظت در برابر مهندسی معکوس |
| Preverify | افزودن ویژگیهای StackMap برای JVM | سازگاری با Java 6+ |
R8 — ابزار مبهمسازی و کوچکسازی نسل بعدی از Google است که اولین بار در Android Studio 3.3 (نوامبر ۲۰۱۸) معرفی و در AGP 3.4 (اوت ۲۰۱۹) به استاندارد تبدیل شد. برخلاف ProGuard، R8 بخشی از کامپایلر D8/R8 است که بایتکد Java را به فرمت DEX تبدیل میکند. R8 تمام فازها — مبهمسازی، کوچکسازی و بهینهسازی — را در یک پاس و بدون انتقال فایلهای میانی بین ابزارها انجام میدهد.
Google R8 را با دو هدف توسعه داد: تسریع بیلد (ProGuard به عنوان ابزار خارجی کار میکرد) و تضمین یکپارچگی یکپارچه با پشته مدرن Android (Desugar، Core Library Desugaring، D8). R8 به زبانهای Kotlin و Java نوشته شده و بخشی از مخزن R8/Desugar در AOSP (Android Open Source Project) است.
مزیت مهم R8 — سازگاری کامل رو به عقب با ProGuard rules است. فایلهای .pro موجود بدون تغییر کار میکنند. R8 حتی از دستورات خاص ProGuard از جمله -whyareyoukeeping، -printconfiguration و -printmapping پشتیبانی میکند. این بدان معناست که انتقال از ProGuard به R8 به صورت شفاف انجام میشود: کافی است AGP را بهروزرسانی کنید.
// build.gradle.kts — فعالسازی R8 از طریق minifyEnabled
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// پیکربندی پایه از Android SDK
getDefaultProguardFile("proguard-android-optimize.txt"),
// قوانین سفارشی پروژه
"proguard-rules.pro"
)
}
}
}کد پیکربندی استاندارد بیلد release را نشان میدهد. پرچم isMinifyEnabled = true R8 را برای مبهمسازی و بهینهسازی فعال میکند. isShrinkResources = true علاوه بر این منابع استفاده نشده را حذف میکند. getDefaultProguardFile قوانین پایه را از SDK بارگذاری میکند و proguard-rules.pro شامل تنظیمات خاص پروژه است.
مبهمسازی — فرایند تبدیل کد مبدأ به فرمی است که تحلیل آن برای انسان دشوار است اما عملکرد کامل را حفظ میکند. در زمینه Android، مبهمسازی به معنای تغییر نام کلاسها، متدها و فیلدها به نامهای کوتاه و بیمعنی است: com.example.app.auth.LoginManager به a.a.a، متد authenticateUser به a، فیلد userToken به b تبدیل میشود.
فایلهای APK Android آرشیوهایی هستند که با هر بایگانیکنندهای (ZIP، 7z، WinRAR) قابل باز شدن هستند. بدون مبهمسازی، مهاجم نقشه کامل برنامه را دریافت میکند: نام بستهها، کلاسها، متدها و فیلدها. ابزارهایی مانند jadx یا Bytecode Viewer تقریباً کد Java اصلی را از فایلهای DEX در چند ثانیه بازسازی میکنند. مبهمسازی کد را غیرقابل نفوذ نمیکند اما آستانه ورود را به طور قابل توجهی افزایش میدهد: به جای نامهای معنیدار، خواننده a()، b()، c() را میبیند.
اهداف معمول مبهمسازی: محافظت از منطق تجاری (الگوریتمها، فرمولهای محاسبه)، دشوار کردن سرقت کلیدهای API و توکنها، جلوگیری از جایگزینی کلاسها از طریق reflection، محافظت در برابر پچ و تغییر APK (repackage attack). در عمل ۷۰٪ کارها دقیقاً با تغییر نام حل میشود — به همین دلیل ProGuard / R8 اجرا میشوند.
در زیر یک فایل معمولی proguard-rules.pro برای پروژه Android با Retrofit، Gson و Parcelable آورده شده است. قوانین -keep کلاسها و متدهای لازم برای کار کتابخانهها از طریق reflection را حفظ میکنند. بدون این قوانین، R8 کلاسهایی را که کتابخانه با نام رشتهای به آن دسترسی پیدا میکند حذف یا تغییر نام میدهد.
# =====================
# Retrofit — نگهداری رابطها
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — سریالسازی JSON
# =====================
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class *.serialization.** {
<fields>;
}
# =====================
# Parcelable — Creator
# =====================
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# =====================
# Logging — حذف لاگها از release
# =====================
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(String, int);
public static int v(...);
public static int d(...);
public static int i(...);
public static int w(...);
public static int e(...);
}
# =====================
# کلاسهای داده Kotlin — نگهداری سازندهها
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — نقطه ورود
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}هر دستور در فایل .pro یک وظیفه خاص را حل میکند. -keep از حذف یا تغییر نام کلاس به طور کامل جلوگیری میکند. -keepclassmembers فقط اعضای کلاس (فیلدها و متدها) را محافظت میکند اما اجازه حذف خود کلاس را در صورت عدم استفاده میدهد. -assumenosideeffects به R8 نشان میدهد که فراخوانی متد عوارض جانبی ندارد و میتوان آن را با خیال راحت حذف کرد. دستور -keepattributes فرادادهها را در بایتکد نگه میدارد — حاشیهنویسیها، امضاها، استثناها.
قانون -keep,allowobfuscation,allowshrinking برای Retrofit به R8 اجازه میدهد نام رابطها را تغییر دهد اما آنها را حذف نکند. این ضروری است زیرا Retrofit از طریق پروکسی پویا (java.lang.reflect.Proxy) به رابطها دسترسی پیدا میکند و حذف آنها منجر به ClassNotFoundException در زمان اجرا میشود. به طور مشابه، Gson از reflection برای دسترسی به فیلدهای دارای حاشیهنویسی @SerializedName استفاده میکند — بدون -keepclassmembers فیلدها به عنوان استفاده نشده حذف میشوند.
کوچکسازی (shrinking) — فرایند حذف کد و منابع استفاده نشده از بیلد نهایی است. ProGuard و R8 گراف فراخوانی را از نقاط ورود (Activity، Service، BroadcastReceiver) تحلیل میکنند و کلاسها و متدهایی را که از طریق زنجیره فراخوانی قابل دسترسی نیستند حذف میکنند. ShrinkResources — مرحله اضافی که منابع استفاده نشده را از res/ (layout، drawable، string، color) حذف میکند.
کوچکسازی بیشترین سود را در پروژههای بزرگ با کتابخانهها میدهد. تصویر معمول: پروژه از ۱۰٪ کد کتابخانه متصل (مثلاً Google Play Services) استفاده میکند. بدون کوچکسازی، تمام کد کتابخانه وارد APK میشود. با کوچکسازی، R8 ۷۰–۹۰٪ کد کتابخانهها را حذف میکند و فقط کلاسها و متدهای واقعاً استفاده شده را باقی میگذارد. این به طور مستقیم بر اندازه APK، زمان بارگذاری و مصرف حافظه تأثیر میگذارد.
مکانیسم ShrinkResources همراه با کوچکسازی کد کار میکند. پس از اینکه R8 مشخص کرد کدام کلاسها استفاده میشوند، کوچکسازی منابع ارجاعات به منابع از کد را تحلیل میکند: R.layout.main، R.drawable.icon، getString(R.string.title). تمام منابعی که ارجاع مستقیم یا غیرمستقیم ندارند از APK یا AAB نهایی حذف میشوند. برای این کار از فایل منبع resources.arsc و پوشههای res/ استفاده میشود.
نکته مهم: منابع میتوانند از طریق getIdentifier() یا Resources.getResourceName() با نام رشتهای و با دور زدن کلاس R فراخوانی شوند. در چنین مواردی R8 ارتباط مستقیم را نمیبیند و ممکن است منبعی را که در واقع استفاده میشود حذف کند. برای محافظت از چنین منابعی دستور -keep class **.R$* { *; } وجود دارد — همه شناسههای کلاس R را حفظ میکند.
<!-- مثال: منبعی که فقط از طریق getIdentifier() استفاده میشود -->
<string name="dynamic_title_welcome">خوش آمدید</string>
<string name="dynamic_title_share">اشتراکگذاری</string>
<!-- کد Kotlin که با رشته دسترسی پیدا میکند -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->در این مورد R8 ارجاع ایستا به dynamic_title_welcome را در کلاس R نمیبیند زیرا دسترسی از طریق getIdentifier با نام پویا انجام میشود. برای حفظ چنین منابعی، باید در proguard-rules.pro دستور -keepclassmembers class **.R$string { *; } را اضافه کرد — این دستور حذف هر فیلدی را از همه کلاسهای R$string ممنوع میکند.
| دستور | هدف | مثال |
|---|---|---|
| -keep | کلاس و تمام اعضای آن را حفظ میکند | -keep class com.example.api.** { *; } |
| -keepclassmembers | فقط اعضای کلاس را حفظ میکند | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | فراداده بایتکد را حفظ میکند | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | فراخوانیهای بدون عوارض جانبی را حذف میکند | -assumenosideeffects class Log { d(...); } |
| -dontwarn | هشدارها را نادیده میگیرد | -dontwarn com.example.legacy.** |
با وجود اینکه R8 جانشین ProGuard است، تفاوتهای اساسی در معماری، عملکرد و رفتار بین ابزارها وجود دارد. Google رسماً پشتیبانی از ProGuard را در Android Gradle Plugin از AGP 7.0 متوقف کرده است، با این حال ProGuard همچنان در پروژههایی که نیاز به رفتار بهینهسازی خاصی دارند که در R8 موجود نیست استفاده میشود.
| ویژگی | ProGuard | R8 |
|---|---|---|
| توسعهدهنده | GuardSquare (اریک لافورژ) | |
| سال عرضه | ۲۰۰۲ | ۲۰۱۸ (پایدار در ۲۰۱۹) |
| معماری | ۴ فاز جداگانه (shrink → optimize → obfuscate → preverify) | یک پاس: shrink + optimize + obfuscate همزمان |
| یکپارچگی در AGP | ابزار خارجی، پس از javac اجرا میشود | تعبیه شده در کامپایلر D8 DEX |
| سرعت بیلد | ۲–۳ برابر کندتر | سریعتر به دلیل یک پاس و یکپارچگی بومی |
| پشتیبانی Kotlin | محدود (مشکلات با inline، lambdas، coroutines) | کامل: coroutines، توابع inline، data class |
| فایل mapping | mapping.txt (سازگار با retrace) | mapping.txt (فرمت یکسان) |
| شخصیسازی بهینهسازی | ۶۰+ گزینه -optimizationpasses، -optimizations | محدود: بیشتر بهینهسازیها به طور پیشفرض فعال هستند |
| وضعیت پشتیبانی | جایگزین شده با R8 (AGP 7.0+ استفاده نمیکند) | توسعه فعال، بخشی از AOSP |
R8 تهاجمیتر از ProGuard کدی را که مرده میداند حذف میکند. این منجر به موقعیتهایی میشود که بیلد debug کار میکند اما release با ClassNotFoundException یا NoSuchMethodException از کار میافتد. موارد معمول: کتابخانههایی که از reflection با نام کلاس استفاده میکنند (Gson، Moshi، Retrofit، Room، Dagger)؛ فراخوانیهای ServiceLoader یا java.util.ServiceLoader؛ پروکسیهای پویا (java.lang.reflect.Proxy)؛ متدهای بومی (JNI). راه حل — اضافه کردن -keep برای همه کلاسهایی که از طریق reflection فراخوانی میشوند.
# مشکلات معمول reflection — R8 ارتباط ایستا را نمیبیند
# Room — DAO و مهاجرتها را نگهداری میکنیم
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — کامپوننتها را نگهداری میکنیم
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — متدهای بومی را تغییر نام نمیدهیم
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — کلاسهای Binding را نگهداری میکنیم
-keep class *.databinding.** { *; }اگر پس از اضافه کردن قوانین بیلد همچنان خراب میشود، از پرچم -printconfiguration full-config.txt در proguard-rules.pro استفاده کنید. R8 یک فایل پیکربندی کامل تولید میکند که نشان میدهد کدام قوانین اعمال شده و کدام کلاسها نگهداری میشوند. همچنین دستور -whyareyoukeeping class com.example.MyClass مفید است — دلیل تصمیم R8 برای نگهداری کلاس را نشان میدهد.
پیکربندی صحیح ProGuard rules — کلید عملکرد پایدار مبهمسازی بدون باگ در زمان اجرا است. در زیر فرایند گام به گام پیکربندی برای یک پروژه جدید یا پروژهای که در آن مبهمسازی باعث خطا میشود آورده شده است.
با اتصال فایل استاندارد Android SDK — proguard-android-optimize.txt شروع کنید. این فایل شامل قوانین کامپوننتهای پایه Android است: Activity، Service، BroadcastReceiver، ContentProvider، View، Fragment. این فایل در پوشه SDK قرار دارد: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. اگر از AGP استفاده میکنید، getDefaultProguardFile آن را به طور خودکار بارگذاری میکند.
هر کتابخانه محبوب قوانین ProGuard توصیه شده خود را دارد. Retrofit، OkHttp، Glide، Fresco، Coil، Room، Dagger/Hilt، Kotlin Coroutines — همه به قوانین -keep خاص نیاز دارند. معمولاً قوانین در کتابخانه AAR گنجانده شده و از طریق consumer guard rules به طور خودکار متصل میشوند. بررسی کنید که کتابخانه فایل proguard.txt را درون AAR ارائه میدهد — این نشانه آن است که قوانین قبلاً در نظر گرفته شدهاند.
قبل از انتشار، حتماً بیلد release را روی یک دستگاه واقعی یا شبیهساز تست کنید. مشکلات مبهمسازی فقط در زمان اجرا ظاهر میشوند. بررسی کنید: احراز هویت (ورود/ثبتنام)، بارگیری داده از شبکه، ناوبری بین صفحهها، دوربین و گالری، اعلانهای push، Deeplinks، WebView. هر crash در بیلد release باید با retrace و فایل mapping رمزگشایی شود و قوانین -keep缺失 اضافه شوند.
فایل mapping در build/outputs/mapping/release/mapping.txt تولید میشود. این فایل برای ذخیرهسازی الزامی است: بدون آن نمیتوان لاگهای crash را از Google Play Console رمزگشایی کرد. mapping.txt را در سیستم کنترل نسخه قرار دهید یا آن را به عنوان مصنوعات CI بارگذاری کنید. Google Play Console فایل mapping را هنگام بارگذاری AAB با uploading mapping.txt فعال به طور خودکار میپذیرد.
در زیر گردش کار کامل پیکربندی مبهمسازی در فایل proguard-rules.pro با توضیحات برای هر گروه از قوانین آورده شده است.
# ===========================================
# proguard-rules.pro — مثال کامل
# ===========================================
# --- تنظیمات عمومی ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- کامپوننتهای Android ---
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.Fragment
-keep public class * extends androidx.fragment.app.Fragment
-keep public class * extends android.view.View
# --- OkHttp / Retrofit ---
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions
# --- Gson / Moshi ---
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }
# --- Firebase ---
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }
# --- Kotlin Coroutines ---
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# --- سریالسازی ---
-keepclassmembers class * implements java.io.Serializable {
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# --- فقط R8: نگهداری اجباری ---
# (ProGuard این دستور را نادیده میگیرد)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}پس از پیکربندی، بیلد را اجرا کنید: ./gradlew assembleRelease. بررسی کنید که در build/outputs/mapping/release/ فایلها ایجاد شدهاند: mapping.txt (تناظر نامهای اصلی و مبهمسازی شده)، seeds.txt (کلاسهای نگهداری شده توسط قوانین -keep)، usage.txt (کلاسهای حذف شده در کوچکسازی). اندازه APK پس از مبهمسازی باید بسته به تعداد کتابخانههای متصل ۲۰–۵۰٪ کاهش یابد.
سوالات متداول
R8 — جانشین ProGuard است که توسط Google توسعه یافته. R8 مبهمسازی، کوچکسازی و بهینهسازی را در یک پاس انجام میدهد، ۲–۳ برابر سریعتر از ProGuard کار میکند و مستقیماً در Android Gradle Plugin یکپارچه شده است. ProGuard از چهار فاز جداگانه استفاده میکند و نیاز به اجرای خارجی دارد. از AGP 7.0 ProGuard استفاده نمیشود — به طور پیشفرض R8 کار میکند.
بله، R8 از همان ProGuard rules (فایلهای .pro) استفاده میکند. دستورات -keep، -keepclassmembers، -keepattributes، -assumenosideeffects به طور یکسان کار میکنند. قوانین پایه در proguard-android-optimize.txt از Android SDK وجود دارند و قوانین خاص کتابخانهها (Retrofit، Room، Gson) در proguard-rules.pro پروژه اضافه میشوند. بدون این قوانین، R8 میتواند کلاسهای لازم برای کار کتابخانهها از طریق reflection را حذف کند.
R8 به طور پیشفرض در Android Gradle Plugin از AGP 3.4 فعال است. برای فعالسازی کوچکسازی، isMinifyEnabled = true را در بلوک release buildType فایل build.gradle.kts تنظیم کنید. پرچم اضافی isShrinkResources = true حذف منابع استفاده نشده را فعال میکند. در gradle.properties میتوان R8 را از طریق android.enableR8=false به زور غیرفعال کرد، اما این توصیه نمیشود — R8 سریعتر و پایدارتر است.
مبهمسازی — تغییر نام کلاسها، متدها و فیلدها به نامهای کوتاه بیمعنی (a، b، c). کلاس com.example.app.auth.LoginManager به a.a.a، متد authenticateUser به a تبدیل میشود. این مهندسی معکوس برنامه را دشوار میکند اما بر منطق اجرا تأثیر نمیگذارد. ProGuard و R8 فقط عناصری را تغییر نام میدهند که توسط قوانین -keep محافظت نشدهاند. فایل mapping تناظر نامهای اصلی و مبهمسازی شده را برای رمزگشایی لاگهای crash حفظ میکند.
برای رمزگشایی stack trace از ابزار retrace (بخشی از ProGuard/R8 SDK) استفاده میشود. دستور: retrace mapping.txt crash-stacktrace.txt. فایل mapping در build/outputs/mapping/release/mapping.txt قرار دارد. Google Play Console نیز از بارگذاری mapping.txt هنگام انتشار AAB پشتیبانی میکند — لاگهای crash به طور خودکار در کنسول رمزگشایی میشوند. بدون فایل mapping، stack trace فقط شامل نامهای مبهمسازی شده a.b.c() خواهد بود که برای دیباگ بیفایده است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید