ProGuard و R8 هما أدوات التعتيم والتصغير والتحسين لتطبيقات Android. ProGuard، الذي تم إنشاؤه في عام 2002، كان المعيار الفعلي لحماية كود Java لفترة طويلة. R8 هو خليفته، الذي طورته Google وتم دمجه في Android Gradle Plugin بدءًا من AGP 3.4. كلا الأداتين تقللان حجم APK، وتزيلان الكود الميت، وتصعّبان الهندسة العكسية. وفقًا لـ Android Developers، يقوم R8 ببناء التطبيق أسرع بـ 2–3 مرات من ProGuard مع جودة تعتيم مماثلة.
الخلاصة
ProGuard هي أداة مفتوحة المصدر (Apache 2.0) لتعتيم وتصغير وتحسين والتحقق المسبق من bytecode Java. طورها إريك لافورج في عام 2002 كجزء من مشروع SourceForge. يستقبل ProGuard فئات Java المجمعة (.class) أو أرشيفات JAR كمدخلات وينتج فئات معالجة بنفس التنسيق، ولكن بحجم أصغر وعناصر معاد تسميتها.
لفترة طويلة، كان ProGuard المعيار الوحيد لحماية تطبيقات Android من الهندسة العكسية. أوصت Google رسميًا باستخدامه في Android SDK ووفرت تكوينًا افتراضيًا في ملف proguard-android-optimize.txt داخل أدوات SDK. كان ProGuard يعمل كأداة منفصلة، يتم تشغيلها بعد تجميع كود Java إلى bytecode وقبل التعبئة في DEX.
يتكون ProGuard من أربع مراحل متتالية: shrink (إزالة الفئات غير المستخدمة)، optimize (تحسين bytecode — تضمين، إزالة الكود الميت)، obfuscate (إعادة تسمية الفئات والطرق والحقول إلى أسماء قصيرة)، preverify (التحقق من التوافق مع JVM). كل مرحلة يتم التحكم فيها بقواعد منفصلة من ملفات التكوين.
أثناء مرحلة التعتيم، يولد ProGuard ملف تعيين (mapping.txt) الذي يربط الأسماء الأصلية بالأسماء المعتمة. هذا الملف ضروري لفك تشفير سجلات الأعطال من إصدارات الإصدار باستخدام أداة retrace. بدون ملف التعيين، يتحول تتبع المكدس إلى مجموعة من الأحرف a()، b()، c() دون إمكانية استعادة السياق الأصلي.
| مرحلة ProGuard | الغرض | النتيجة |
|---|---|---|
| Shrink | تحليل رسم بياني للاستدعاء وإزالة الكود الميت | فئات أقل في APK |
| Optimize | تضمين الطرق، إزالة المعاملات غير المستخدمة | تنفيذ أسرع للكود |
| Obfuscate | إعادة تسمية الفئات والحقول والطرق | حماية من الهندسة العكسية |
| Preverify | إضافة سمات StackMap لـ JVM | التوافق مع Java 6+ |
R8 هي أداة تعتيم وتصغير من الجيل التالي من Google، تم تقديمها لأول مرة في Android Studio 3.3 (نوفمبر 2018) وأصبحت قياسية في AGP 3.4 (أغسطس 2019). على عكس ProGuard، فإن R8 جزء من مترجم D8/R8 الذي يحول bytecode Java إلى تنسيق DEX. يقوم R8 بتنفيذ جميع المراحل — التعتيم والتصغير والتحسين — في مسار واحد، دون تمرير ملفات وسيطة بين الأدوات.
طورت Google R8 بهدفين: تسريع البناء (كان ProGuard يعمل كأداة خارجية) وتوفير تكامل سلس مع مجموعة Android الحديثة (Desugar، Core Library Desugaring، D8). R8 مكتوب بلغة Kotlin و Java وهو جزء من مستودع R8/Desugar على AOSP (مشروع Android مفتوح المصدر).
ميزة مهمة لـ R8 هي التوافق العكسي الكامل مع قواعد ProGuard. ملفات .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"
)
}
}
}يوضح الكود تكوين بناء إصدار قياسي. العلم 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 وإعادة التعبئة (هجوم إعادة التغليف). عمليًا، 70% من المهام يتم حلها بإعادة التسمية وحدها — ولهذا يتم استخدام 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 — إزالة السجلات من الإصدار
# =====================
-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 يحفظ البيانات الوصفية في bytecode — التعليقات التوضيحية والتوقيعات والاستثناءات.
القاعدة -keep,allowobfuscation,allowshrinking لـ Retrofit تسمح لـ R8 بإعادة تسمية الواجهات ولكن ليس إزالتها. هذا ضروري لأن Retrofit يصل إلى الواجهات عبر الوكلاء الديناميكيين (java.lang.reflect.Proxy)، والإزالة ستؤدي إلى ClassNotFoundException في وقت التشغيل. وبالمثل، يستخدم Gson reflection للوصول إلى الحقول الموسومة بـ @SerializedName — بدون -keepclassmembers ستتم إزالة الحقول كغير مستخدمة.
التصغير (shrinking) هو عملية إزالة الكود والموارد غير المستخدمة من البناء النهائي. يقوم ProGuard و R8 بتحليل رسم بياني للاستدعاء بدءًا من نقاط الدخول (Activity، Service، BroadcastReceiver) وإزالة الفئات والطرق التي لا يمكن الوصول إليها عبر سلسلة الاستدعاءات. ShrinkResources هي مرحلة إضافية تزيل الموارد غير المستخدمة من res/ (تخطيط، قابل للرسم، سلسلة، لون).
التصغير يوفر أكبر فائدة في المشاريع الكبيرة التي تحتوي على مكتبات. سيناريو نموذجي: مشروع يستخدم 10% من الكود من مكتبة متصلة (مثل Google Play Services). بدون تصغير، يصل كل كود المكتبة إلى APK. مع التصغير، يزيل R8 70–90% من كود المكتبة، تاركًا فقط الفئات والطرق المستخدمة فعليًا. هذا يؤثر مباشرة على حجم 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 باسم ديناميكي. للحفاظ على هذه الموارد، أضف التوجيه -keepclassmembers class **.R$string { *; } إلى proguard-rules.pro — يمنع إزالة أي حقول من جميع فئات R$string.
| التوجيه | الغرض | مثال |
|---|---|---|
| -keep | يحافظ على الفئة وجميع أعضائها | -keep class com.example.api.** { *; } |
| -keepclassmembers | يحافظ على أعضاء الفئة فقط | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | يحافظ على البيانات الوصفية للـ bytecode | -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 (إريك لافورج) | |
| سنة الإصدار | 2002 | 2018 (مستقر في 2019) |
| الهندسة | 4 مراحل منفصلة (shrink → optimize → obfuscate → preverify) | مسار واحد: shrink + optimize + obfuscate في وقت واحد |
| التكامل في AGP | أداة خارجية، تُشغل بعد javac | مدمجة في مترجم D8 DEX |
| سرعة البناء | أبطأ بـ 2–3 مرات | أسرع بفضل المسار الواحد والتكامل الأصلي |
| دعم Kotlin | محدود (مشاكل مع inline، lambdas، coroutines) | كامل: coroutines، دوال inline، data class |
| ملف التعيين | mapping.txt (متوافق مع retrace) | mapping.txt (نفس التنسيق) |
| تخصيص التحسين | 60+ خيارًا -optimizationpasses، -optimizations | محدود: معظم التحسينات مفعلة افتراضيًا |
| حالة الدعم | تم استبداله بـ R8 (AGP 7.0+ لا يستخدمه) | تطوير نشط، جزء من AOSP |
R8 أكثر عدوانية من ProGuard في إزالة الكود الذي يعتبره ميتًا. هذا يؤدي إلى مواقف حيث يعمل بناء التصحيح ولكن بناء الإصدار يفشل مع 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 هو مفتاح التعتيم المستقر دون أخطاء في وقت التشغيل. أدناه عملية إعداد خطوة بخطوة لمشروع جديد أو لمشروع حيث يسبب التعتيم أخطاء.
ابدأ بتضمين ملف 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 ويتم ربطها تلقائيًا عبر قواعد المستهلك. تحقق من أن المكتبة توفر ملف proguard.txt داخل AAR — هذا يشير إلى أن القواعد قد تم أخذها في الاعتبار بالفعل.
قبل النشر، تأكد من اختبار بناء الإصدار على جهاز حقيقي أو محاكي. تظهر مشكلات التعتيم فقط في وقت التشغيل. تحقق من: التفويض (تسجيل الدخول/التسجيل)، تحميل البيانات من الشبكة، التنقل بين الشاشات، الكاميرا والمعرض، إشعارات الدفع، Deeplinks، WebView. يجب فك تشفير كل تعطل في بناء الإصدار باستخدام retrace مع ملف التعيين، وإضافة قواعد -keep المفقودة.
يتم إنشاء ملف التعيين في build/outputs/mapping/release/mapping.txt. يجب الحفاظ على هذا الملف: بدونه يستحيل فك تشفير سجلات الأعطال من Google Play Console. قم بتضمين mapping.txt في نظام التحكم في الإصدارات أو قم بتحميله كأحد مخرجات CI. تقبل Google Play Console ملف التعيين تلقائيًا عند رفع 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 بعد التعتيم بنسبة 20–50% اعتمادًا على عدد المكتبات المتصلة.
الأسئلة الشائعة
R8 هو خليفة ProGuard، طورته Google. يقوم R8 بالتعتيم والتصغير والتحسين في مسار واحد، ويعمل أسرع بـ 2–3 مرات من ProGuard ومدمج مباشرة في Android Gradle Plugin. يستخدم ProGuard أربع مراحل منفصلة ويتطلب تشغيلًا خارجيًا. بدءًا من AGP 7.0، لا يُستخدم ProGuard — يعمل R8 افتراضيًا.
نعم، R8 يستخدم نفس قواعد ProGuard (ملفات .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. ملف التعيين يحافظ على تطابق الأسماء الأصلية والمعتمة لفك تشفير سجلات الأعطال.
لفك تشفير تتبع المكدس، استخدم أداة retrace (جزء من ProGuard/R8 SDK). الأمر: retrace mapping.txt crash-stacktrace.txt. ملف التعيين موجود في build/outputs/mapping/release/mapping.txt. Google Play Console تدعم أيضًا رفع mapping.txt عند نشر AAB — يتم فك تشفير سجلات الأعطال تلقائيًا في لوحة التحكم. بدون ملف التعيين، سيحتوي تتبع المكدس فقط على أسماء معتمة مثل a.b.c()، وهو عديم الفائدة للتصحيح.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.