أخبار المنتجات

ضبط قواعد الاحتفاظ ببيانات R8 وتحديد المشاكل فيها وحلّها

قراءة لمدة 7 دقائق
Ajesh Pai & Ben Weiss

في عملية تطوير تطبيقات Android الحديثة، يُعد توفير تطبيق صغير وسريع وآمن من التوقعات الأساسية للمستخدمين. الأداة الأساسية التي يستخدمها نظام تصميم Android لتحقيق ذلك هي أداة التحسين R8 ، وهي برنامج التجميع الذي يتعامل مع الرموز غير المستخدَمة وإزالة الموارد لتقليص الحجم، وإعادة تسمية الرموز أو إزالة البيانات غير الضرورية، وتحسين التطبيق.

يُعدّ تفعيل R8 خطوة مهمة في إعداد تطبيق للإصدار، ولكنّه يتطلّب من المطوّرين تقديم إرشادات في شكل "قواعد الحفاظ على البيانات".

بعد قراءة هذه المقالة، يمكنك مشاهدة فيديو "أسبوع تسليط الضوء على الأداء" حول تفعيل أداة تحسين R8 وتصحيح أخطائها وحلّ مشاكلها على YouTube.

 

 

أهمية قواعد الاحتفاظ بالبيانات

تنشأ الحاجة إلى كتابة قواعد Keep Rules من تعارض أساسي: R8 هي أداة تحليل ثابت، ولكن تطبيقات Android تعتمد غالبًا على أنماط التنفيذ الديناميكي، مثل الانعكاس أو عمليات الاستدعاء داخل الرمز البرمجي الأصلي وخارجه باستخدام JNI (واجهة Java الأصلية).

ينشئ R8 رسمًا بيانيًا للرمز المستخدَم من خلال تحليل عمليات الاستدعاء المباشرة. عند الوصول إلى الرمز بطريقة ديناميكية، لا يمكن للتحليل الثابت في R8 توقّع ذلك، وسيتم تحديد هذا الرمز على أنّه غير مستخدَم وإزالته، ما يؤدي إلى حدوث أعطال أثناء وقت التشغيل.

قاعدة الاحتفاظ هي تعليمات صريحة لمترجم R8، وتنص على ما يلي: "هذا الصنف أو الطريقة أو الحقل المحدّد هو نقطة دخول سيتم الوصول إليها بشكل ديناميكي في وقت التشغيل. ويجب الاحتفاظ بها حتى إذا لم تتمكّن من العثور على إشارة مباشرة إليها".

يمكنك الاطّلاع على الدليل الرسمي لمعرفة المزيد من التفاصيل حول قواعد الاحتفاظ.

مكان كتابة "قواعد الاحتفاظ"

تتم كتابة قواعد الاحتفاظ المخصّصة لأحد التطبيقات في ملف نصي. وفقًا للاصطلاح، يُطلق على هذا الملف الاسم proguard-rules.pro ويقع في جذر وحدة التطبيق أو المكتبة. يتم بعد ذلك تحديد هذا الملف في نوع التصميم release من ملف build.gradle.kts في الوحدة.

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

استخدام الملف التلقائي الصحيح

تستورد الطريقة getDefaultProguardFile مجموعة تلقائية من القواعد التي توفّرها حزمة تطوير البرامج (SDK) لنظام التشغيل Android. عند استخدام ملف غير صحيح، قد لا يتم تحسين تطبيقك. احرص على استخدام proguard-android-optimize.txt. يوفّر هذا الملف "قواعد الاحتفاظ" التلقائية لمكوّنات Android العادية ويتيح تحسينات R8 على الرموز البرمجية. لا يوفّر الإصدار القديم من proguard-android.txt سوى قواعد Keep، ولكنّه لا يفعّل تحسينات R8.

progaurd.png

بما أنّ هذه المشكلة تؤثر بشكل كبير في الأداء، سنبدأ بتنبيه المطوّرين بشأن استخدام الملف غير الصحيح، وذلك بدءًا من الإصدار 3 من تحديثات Narwhal في "استوديو Android". واعتبارًا من الإصدار 9.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android، لن نتيح استخدام ملف proguard-android.txt القديم. لذا، احرص على الترقية إلى الإصدار المحسّن.

كيفية كتابة "قواعد Keep"

تتألف قاعدة الاحتفاظ من ثلاثة أجزاء رئيسية:

  1. أحد الخيارات مثل -keep أو -keepclassmembers
  2. المعدّلات الاختيارية، مثل allowshrinking
  3. مواصفات فئة تحدد الرمز المطلوب مطابقته

للاطّلاع على البنية الكاملة والأمثلة، يُرجى الرجوع إلى الإرشادات حول إضافة قواعد الاحتفاظ.

أنماط مضادة لقاعدة الاحتفاظ

من المهم معرفة أفضل الممارسات، ولكن أيضًا معرفة الأنماط المضادة. غالبًا ما تنشأ هذه الأنماط المضادة من حالات سوء فهم أو اختصارات لتحديد المشاكل وحلّها، ويمكن أن تكون كارثية على أداء الإصدار المتاح للجميع.

الخيارات العامة

هذه العلامات هي مفاتيح تبديل عامة يجب عدم استخدامها مطلقًا في بنية الإصدار. وهي مخصّصة فقط لتصحيح الأخطاء المؤقتة من أجل عزل مشكلة معيّنة.

يؤدي استخدام -dontotptimize إلى إيقاف تحسينات الأداء في R8 بشكلٍ فعّال، ما يؤدي إلى بطء التطبيق.

عند استخدام -dontobfuscate، يتم إيقاف جميع عمليات إعادة التسمية، وعند استخدام -dontshrink، يتم إيقاف إزالة الرموز البرمجية غير النشطة. تؤدي كلتا القاعدتين العامّتين إلى زيادة حجم التطبيق.

تجنَّب استخدام هذه العلامات العامة في بيئة إنتاج كلما أمكن ذلك للحصول على تجربة مستخدم أفضل للتطبيق.

قواعد الاحتفاظ الواسعة النطاق بشكل مفرط

إنّ أسهل طريقة لإبطال مزايا R8 هي كتابة قواعد Keep واسعة النطاق. توجّه قواعد مثل القاعدة أدناه أداة التحسين R8 إلى عدم تصغير أي فئة في هذه الحزمة أو أي من حِزمها الفرعية، وعدم تشويشها وعدم تحسينها. يؤدي ذلك إلى إزالة مزايا R8 بالكامل من تلك الحزمة. جرِّب كتابة "قواعد الاحتفاظ" ضيقة ومحددة بدلاً من ذلك.
 

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

عامل النفي (!)

يبدو أنّ عامل النفي (!) هو طريقة فعّالة لاستبعاد حزمة من إحدى القواعد. لكنّ الأمر ليس بهذه البساطة. لِنأخذ المثال التالي:

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

قد تعتقد أنّ هذه القاعدة تعني "لا تحتفظ بالفئات فيcom.example.package"، ولكنّها تعني في الواقع "احتفظ بكل فئة وطريقة وسمة في التطبيق بأكمله غير موجودة في com.example.package". إذا كان ذلك مفاجئًا لك، من الأفضل التحقّق من أي عمليات نفي في إعدادات R8.

قواعد مكرّرة لمكوّنات Android

من الأخطاء الشائعة الأخرى إضافة قواعد Keep يدويًا إلى Activities أو Services أو BroadcastReceivers في تطبيقك. هذا الإجراء غير ضروري. يتضمّن ملف proguard-android-optimize.txt التلقائي القواعد ذات الصلة لكي تعمل هذه المكوّنات العادية في Android بدون أي إعدادات إضافية.

توفّر العديد من المكتبات أيضًا "قواعد الاحتفاظ" الخاصة بها. لذلك، لن تحتاج إلى كتابة قواعد خاصة بهذه الحالات. في حال حدوث مشكلة في "قواعد الاحتفاظ" من مكتبة تستخدمها، من الأفضل التواصل مع مؤلف المكتبة لمعرفة المشكلة.

أفضل الممارسات المتعلّقة بقاعدة Keep

بعد أن عرفت ما لا يجب فعله، لنتحدّث الآن عن أفضل الممارسات.

كتابة "قواعد Keep" ضيقة النطاق

يجب أن تكون "قواعد الاحتفاظ" الجيدة ضيقة ومحددة قدر الإمكان. ويجب أن تحتفظ هذه الأدوات بما هو ضروري فقط، ما يسمح لأداة R8 بتحسين كل ما عدا ذلك.
 

القاعدةالجودة

 

-keep class com.example.** { ; }

 

منخفض: يحتفظ بحزمة كاملة وحِزمها الفرعية

 

-keep class com.example.MyClass { ; }

 

منخفض: يحتفظ بفئة كاملة من المرجّح أنّها لا تزال واسعة جدًا
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
عالية: يتم الاحتفاظ بالطُرق والسمات ذات الصلة فقط من فئة معيّنة

استخدام الأسلاف المشتركين

بدلاً من كتابة قواعد Keep منفصلة لنماذج بيانات مختلفة متعددة، اكتب قاعدة واحدة تستهدف صنفًا أساسيًا أو واجهة مشتركة. تطلب القاعدة أدناه من R8 الاحتفاظ بأي عناصر من الفئات التي تنفّذ هذه الواجهة، وهي قابلة للتوسيع بشكل كبير.

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

استخدام التعليقات التوضيحية لاستهداف صفوف متعددة

أنشئ تعليقًا توضيحيًا مخصّصًا (مثل @Serialize) واستخدِمه "لوضع علامة" على الفئات التي تحتاج إلى الاحتفاظ بحقولها. هذا نمط آخر واضح وتصريحي وقابل للتوسّع بشكل كبير. يمكنك إنشاء "قواعد Keep" للتعليقات التوضيحية الحالية من الأُطر التي تستخدمها أيضًا.

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

اختيار خيار Keep المناسب

يُعدّ خيار "الاحتفاظ" الجزء الأكثر أهمية في القاعدة. وقد يؤدي اختيار الخيار غير الصحيح إلى إيقاف التحسين بدون داعٍ.

خيار الاحتفاظالإجراءات التي ينفّذها
-keepيمنع إزالة الفئة والعناصر المذكورة في البيان أو إعادة تسميتها.
-keepclassmembersيمنع الأعضاء المحدّدين من إزالتهم أو إعادة تسميتهم، ولكنّه يسمح بإزالة الصف نفسه فقط في الصفوف التي لم تتم إزالتها بطريقة أخرى.
-keepclasseswithmembersمجموعة: يتم الاحتفاظ بالصف وأعضائه فقط إذا كان جميع الأعضاء المحدّدين حاضرين.

يمكنك الاطّلاع على مزيد من المعلومات حول خيار "الاحتفاظ" في مستندات خيارات الاحتفاظ.

السماح بالتحسين باستخدام "المعدِّلات"

تخفّف المعدِّلات، مثل allowshrinking وallowobfuscation، قاعدة -keep عامة، ما يمنح R8 إمكانية التحسين مرة أخرى. على سبيل المثال، إذا كانت مكتبة قديمة تجبرك على استخدام -keep في فئة بأكملها، قد تتمكّن من استعادة بعض التحسين من خلال السماح بالتصغير والتشويش:

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

إضافة خيارات عامة لتحسين الأداء بشكل إضافي

بالإضافة إلى "قواعد الاحتفاظ"، يمكنك إضافة علامات عامة إلى ملف إعدادات R8 لتشجيع المزيد من التحسين.

-repackageclasses هو خيار فعّال يوجّه R8 إلى نقل جميع الفئات التي تم تشويشها إلى حزمة واحدة. يؤدي ذلك إلى توفير مساحة كبيرة في ملف DEX من خلال إزالة سلاسل أسماء الحِزم المكرّرة.

تسمح -allowaccessmodification لبرنامج R8 بتوسيع نطاق الوصول (على سبيل المثال، من private إلى public) لتفعيل التضمين الأكثر فعالية. يتم تفعيل هذا الخيار تلقائيًا عند استخدام proguard-android-optimize.txt.

تحذير: يجب على مطوّري المكتبات عدم إضافة علامات التحسين العامة هذه إلى قواعد المستهلكين، لأنّه سيتم تطبيقها بشكل إلزامي على التطبيق بأكمله.

ولزيادة الوضوح، سنبدأ في الإصدار 9.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android بتجاهل علامات التحسين العامة من المكتبات تمامًا. 

أفضل الممارسات للمكتبات

تعتمد جميع تطبيقات Android على المكتبات بطريقة أو بأخرى. لنتحدّث الآن عن أفضل الممارسات المتعلّقة بالمكتبات.

لمطوّري المكتبات

إذا كانت مكتبتك تستخدم الانعكاس أو JNI، تقع على عاتقك مسؤولية توفير قواعد Keep اللازمة للمستهلكين. يتم وضع هذه القواعد في ملف consumer-rules.pro، ثم يتم تجميعها تلقائيًا داخل ملف AAR الخاص بالمكتبة.

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

لمستخدمي المكتبة

فلترة "قواعد Keep" التي تتضمّن مشاكل

إذا كان عليك استخدام مكتبة تتضمّن قواعد Keep Rules إشكالية، يمكنك فلترتها في ملف build.gradle.kts بدءًا من الإصدار 9.0 من AGP، ما يطلب من R8 تجاهل القواعد الواردة من اعتمادية معيّنة.

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

أفضل قاعدة احتفاظ هي عدم وجود قاعدة احتفاظ

تتمثّل استراتيجية إعدادات R8 المثالية في إزالة الحاجة إلى كتابة قواعد الاحتفاظ تمامًا. يمكن تحقيق ذلك في العديد من التطبيقات من خلال اختيار مكتبات حديثة تعطي الأولوية لإنشاء الرموز على التفكير المجرد.باستخدام ميزة إنشاء الرموز البرمجية، يمكن لأداة التحسين تحديد الرموز البرمجية المستخدَمة فعليًا في وقت التشغيل والرموز البرمجية التي يمكن إزالتها بسهولة أكبر. عدم استخدام أي انعكاس ديناميكي يعني أيضًا عدم وجود نقاط دخول "مخفية"، وبالتالي لا حاجة إلى قواعد Keep. عند اختيار مكتبة جديدة، ننصحك دائمًا باختيار حلّ يستخدم إنشاء الرموز البرمجية بدلاً من الانعكاس.

لمزيد من المعلومات حول كيفية اختيار المكتبات، يُرجى الاطّلاع على مقالة اختيار المكتبة بحكمة.

تحديد المشاكل وحلّها في إعدادات R8

عندما تزيل أداة R8 رمزًا كان من المفترض الاحتفاظ به، أو إذا كان حجم حِزمة APK أكبر من المتوقّع، استخدِم هذه الأدوات لتشخيص المشكلة.

البحث عن قواعد Keep المكرّرة والعامة

بما أنّ R8 يدمج القواعد من عشرات المصادر، قد يصعب معرفة مجموعة القواعد "النهائية". تؤدي إضافة هذا الخيار إلى ملف proguard-rules.pro إلى إنشاء تقرير كامل:

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

يمكنك البحث في هذا الملف للعثور على قواعد مكرّرة أو تتبُّع قاعدة تتسبّب في مشاكل (مثل -dontoptimize) والرجوع إلى المكتبة المحدّدة التي تضمّنتها.

Ask R8: Why are you keeping this?

إذا كان صف تتوقّع إزالته لا يزال في تطبيقك، يمكن أن يوضّح لك R8 السبب. ما عليك سوى إضافة هذه القاعدة:

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

أثناء عملية الإنشاء، ستعرض أداة R8 سلسلة المراجع الدقيقة التي أدّت إلى الاحتفاظ بهذه الفئة، ما يتيح لك تتبُّع المرجع وتعديل قواعدك.

للحصول على دليل كامل، يُرجى الاطّلاع على قسم تحديد المشاكل في R8 وحلّها.

الخطوات التالية

‫R8 هي أداة فعّالة لتحسين أداء تطبيقات Android. وتعتمد فعاليته على فهم صحيح لطريقة عمله كمحرّك تحليل ثابت.

من خلال كتابة قواعد محدّدة على مستوى الأعضاء، والاستفادة من العناصر الرئيسية والتعليقات التوضيحية، واختيار خيارات الاحتفاظ المناسبة بعناية، يمكنك الاحتفاظ بما هو ضروري بالضبط. أما الممارسة الأكثر تقدّمًا فهي إلغاء الحاجة إلى القواعد تمامًا من خلال اختيار مكتبات حديثة مستندة إلى إنشاء الرموز البرمجية بدلاً من المكتبات السابقة المستندة إلى الانعكاس.

أثناء متابعة "أسبوع تسليط الضوء على الأداء"، احرص على مشاهدة فيديو "أسبوع تسليط الضوء" اليوم على YouTube ومواصلة تحدّي R8. استخدِم الهاشتاغ ‎ #optimizationEnabled لطرح أي أسئلة حول تفعيل R8 أو تحديد المشاكل وحلّها. نحن في الخدمة.

حان الوقت للاطّلاع على المزايا بنفسك.

ننصحك بتفعيل وضع R8 الكامل لتطبيقك اليوم.

  1. اتّبِع أدلة المطوّرين للبدء: تفعيل ميزة تحسين التطبيق.
  2. تحقَّق مما إذا كنت لا تزال تستخدم proguard-android.txt واستبدِله proguard-android-optimize.txt.
  3. بعد ذلك، قِس التأثير. لا تكتفِ بالشعور بالفرق، بل تأكَّد منه. يمكنك قياس تحسُّن الأداء من خلال تعديل الرمز من نموذج تطبيق Macrobenchmark على GitHub لقياس أوقات بدء التشغيل قبل وبعد التعديل.

نحن على ثقة بأنّك ستلاحظ تحسّنًا ملحوظًا في أداء تطبيقك.

يمكنك أيضًا استخدام الهاشتاغ ‎#AskAndroid لطرح أسئلتك. يتابع خبراء Google أسئلتك ويجيبون عنها طوال الأسبوع.

ترقَّبوا مشاركتنا غدًا، حيث سنتحدّث عن ميزة "التحسين المستند إلى الملف الشخصي" باستخدام "ملفات المرجع" و"ملفات بدء التشغيل"، وسنشارك معلومات حول كيفية تحسُّن أداء العرض في Compose خلال الإصدارات السابقة، بالإضافة إلى اعتبارات الأداء المتعلقة بالعمل في الخلفية.

تأليف:

متابعة القراءة