تصغير تطبيقك وتعتيمه وتحسينه

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

عند إنشاء مشروعك باستخدام الإصدار 3.4.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو الإصدارات الأحدث، لن يستخدم المكوّن الإضافي ProGuard لإجراء تحسين على رموز وقت التجميع. وبدلاً من ذلك، يعمل المكوّن الإضافي مع برنامج التحويل البرمجي R8 للتعامل مع مهام وقت التجميع التالية:

  • تقليص الرموز (أو اهتزاز الشجرة): يتم رصد الفئات والحقول والطرق والسمات غير المستخدمة وإزالتها بأمان من تطبيقك وتبعيات مكتبته (مما يجعله أداة قيّمة لتفادي الحد الأقصى لعدد المراجع المسموح به والبالغ 64 ألف). على سبيل المثال، إذا كنت تستخدم بعض واجهات برمجة التطبيقات (API) فقط ضمن مكتبة تابعة للمكتبة، يمكن لتقليص الرموز البرمجية تحديد رمز المكتبة الذي لا يستخدمه تطبيقك وإزالة هذا الرمز فقط من تطبيقك. لمعرفة المزيد من المعلومات، انتقِل إلى القسم حول كيفية تقليص الرمز.
  • تقليص الموارد: يؤدي هذا الإجراء إلى إزالة الموارد غير المستخدَمة من تطبيقك المجمّع، بما في ذلك الموارد غير المستخدَمة في تبعيات مكتبة تطبيقك. وهي تعمل جنبًا إلى جنب مع تقليص التعليمات البرمجية بحيث أنه بمجرد إزالة التعليمات البرمجية غير المستخدمة، يمكن أيضًا إزالة أي موارد لم تعد تتم الإشارة إليها بأمان. لمعرفة المزيد من المعلومات، انتقِل إلى القسم المتعلّق بكيفية تقليص الموارد.
  • التشويش: يختصر اسم الفئات والأعضاء، ما يؤدي إلى تقليل أحجام ملفات DEX. لمعرفة المزيد من المعلومات، انتقِل إلى القسم المتعلّق بكيفية تشويش الرموز البرمجية.
  • التحسين: يتم فحص الرمز وإعادة كتابته لخفض حجم ملفات DEX في تطبيقك. على سبيل المثال، إذا رصدت R8 عدم استخدام فرع else {} لعبارة if/else معيّنة، تزيل R8 الرمز للفرع else {}. للاطّلاع على مزيد من المعلومات، يمكنك الانتقال إلى القسم حول تحسين الرموز.

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

تفعيل التقليص والتشويش والتحسين

عند استخدام الإصدار 3.4.0 من "استوديو Android" أو الإصدار 3.4.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو الإصدارات الأحدث، يكون R8 هو المحول البرمجي التلقائي الذي يحوّل رمز بايت Java لمشروعك إلى تنسيق DEX الذي يتم تشغيله على نظام Android الأساسي. ومع ذلك، عند إنشاء مشروع جديد باستخدام "استوديو Android"، لا يتم تفعيل التقليص وإخفاء مفاتيح فك التشفير وتحسين الرموز تلقائيًا. ويرجع ذلك إلى أنّ تحسينات وقت التجميع هذه تزيد من وقت إنشاء مشروعك وقد تؤدي إلى ظهور أخطاء إذا لم تخصِّص الرمز البرمجي المطلوب الاحتفاظ به بشكل كافٍ.

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

Kotlin

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

            // 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"
            )
        }
    }
    ...
}

رائع

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.

المصدر الموقع الجغرافي الوصف
استوديو Android <module-dir>/proguard-rules.pro عند إنشاء وحدة جديدة باستخدام "استوديو Android"، ينشئ بيئة التطوير المتكاملة ملف proguard-rules.pro في الدليل الجذري لتلك الوحدة.

لا يطبق هذا الملف أي قواعد تلقائيًا. وبالتالي، يمكنك تضمين قواعد ProGuard الخاصة بك هنا، مثل قواعد Keep المخصّصة.

المكوّن الإضافي لنظام Gradle المتوافق مع Android يتم إنشاء هذا الإعداد باستخدام المكوّن الإضافي لنظام Gradle المتوافق مع Android في وقت التجميع. ينشئ المكوّن الإضافي لنظام Gradle المتوافق مع Android proguard-android-optimize.txt، الذي يتضمَّن قواعد مفيدة لمعظم مشاريع Android ويفعِّل تعليقات @Keep* توضيحية.

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

ملاحظة: يشتمل مكوّن Android Gradle الإضافي على ملفات إضافية لقواعد ProGuard المحددة مسبقًا، ولكن يُنصح باستخدام proguard-android-optimize.txt.

تبعيات المكتبة مكتبات AAR: <library-dir>/proguard.txt

مكتبات JAR: <library-dir>/META-INF/proguard/

في حال نشر مكتبة AAR مع ملف قواعد ProGuard الخاص بها، وتضمين AAR كتبعية لوقت التجميع، تطبّق R8 قواعدها تلقائيًا عند تجميع مشروعك.

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

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

أداة حزمة مواد العرض 2 لنظام التشغيل Android (AAPT2) بعد إنشاء مشروعك باستخدام minifyEnabled true: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt ينشئ AAPT2 قواعد الاحتفاظ بالبيانات استنادًا إلى الإشارات إلى الفئات في ملف البيان والتنسيقات وموارد التطبيق الأخرى. على سبيل المثال، يتضمن AAPT2 قاعدة الاحتفاظ لكل نشاط تسجّله في ملف بيان التطبيق كنقطة دخول.
ملفات الإعداد المخصّصة عندما تنشئ وحدة جديدة باستخدام "استوديو Android" تلقائيًا، تنشئ بيئة التطوير المتكاملة <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"، تنشئ بيئة التطوير المتكاملة ملف <module-dir>/proguard-rules.pro لتضمين قواعدك الخاصة. يمكنك أيضًا تضمين قواعد إضافية من الملفات الأخرى عن طريق إضافتها إلى السمة proguardFiles في النص البرمجي لإنشاء الوحدة.

على سبيل المثال، يمكنك إضافة قواعد خاصة بكل صيغة إصدار من خلال إضافة سمة proguardFiles أخرى في مجموعة productFlavor المقابلة. يضيف ملف Gradle التالي flavor2-rules.pro إلى نكهة منتجات flavor2. والآن، يستخدم flavor2 قواعد ProGuard الثلاث كلها لأنّه يتم أيضًا تطبيق تلك القواعد من مجموعة release.

بالإضافة إلى ذلك، يمكنك إضافة السمة testProguardFiles التي تحدّد قائمة بملفات ProGuard المضمّنة في حزمة APK للاختبار فقط:

Kotlin

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 هذا الرمز عند تقليص حجم تطبيقك.

الشكل 1. في وقت التجميع، تنشئ R8 رسمًا بيانيًا بناءً على قواعد الاحتفاظ المدمجة لمشروعك لتحديد التعليمات البرمجية التي لا يمكن الوصول إليها.

وتحدِّد واجهة R8 نقاط الدخول من خلال قواعد -keep في ملفات إعداد R8 الخاصة بالمشروع. وهذا يعني أنّ القواعد تحدّد الصفوف التي يجب على R8 عدم تجاهلها عند تقليص حجم تطبيقك، وتعتبر R8 هذه الفئات كنقاط دخول ممكنة إلى تطبيقك. وينشئ مكوّن Android Gradle الإضافي وAAPT2 تلقائيًا قواعد الاحتفاظ بالبيانات التي تطلبها معظم مشاريع التطبيقات، مثل أنشطة التطبيق ومشاهداته وخدماته. ومع ذلك، إذا كنت بحاجة إلى تخصيص هذا السلوك التلقائي باستخدام قواعد Keep إضافية، يُرجى الاطّلاع على القسم المتعلّق بكيفية تخصيص الرمز البرمجي المطلوب الاحتفاظ به.

إذا كنت مهتمًا فقط بتقليل حجم موارد تطبيقك، انتقِل مباشرةً إلى القسم المتعلّق بكيفية تقليص مواردك.

تخصيص الرمز المطلوب الاحتفاظ به

في معظم الحالات، يكفي ملف قواعد ProGuard التلقائي (proguard-android- optimize.txt) لكي تزيل R8 الرمز غير المستخدَم فقط. ومع ذلك، يصعب على R8 تحليل بعض المواقف بشكل صحيح وقد تزيل الكود الذي يحتاجه تطبيقك بالفعل. تتضمن بعض الأمثلة على الحالات التي قد تزيل فيها التعليمات البرمجية بشكل غير صحيح ما يلي:

  • عندما يطلب تطبيقك إحدى الطرق من واجهة Java Native Interface (JNI)
  • عندما يبحث تطبيقك عن رمز برمجي في وقت التشغيل (مثلاً، عند الانعكاس)

من المفترض أن يكشف اختبار تطبيقك عن أي أخطاء ناتجة عن رمز تمت إزالته بشكلٍ غير صحيح، ولكن يمكنك أيضًا فحص الرمز الذي تمت إزالته من خلال إنشاء تقرير بالرمز الذي تمت إزالته.

لإصلاح الأخطاء وإجبار R8 على الاحتفاظ برمز معيّن، أضِف سطر -keep في ملف قواعد ProGuard. مثلاً:

-keep public class MyClass

يمكنك بدلاً من ذلك إضافة التعليق التوضيحي @Keep إلى الرمز البرمجي الذي تريد الاحتفاظ به. تؤدي إضافة @Keep إلى إحدى الصفوف إلى إبقاء الفئة كما هي. وتؤدي إضافتها في طريقة أو حقل إلى إبقاء الطريقة/الحقل (واسمه) بدون تغيير في اسم الفئة أيضًا. لاحظ أن هذا التعليق التوضيحي لا يتوفر إلا عند استخدام مكتبة التعليقات التوضيحية لنظام AndroidX وعند تضمين ملف قواعد ProGuard المدمج في المكوّن الإضافي Gradle المتوافق مع Android، كما هو موضّح في القسم المتعلق بكيفية تفعيل ميزة "التقليص".

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

إزالة المكتبات الأصلية

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

دعم الأعطال الأصلية

تبلِّغ Google Play Console عن الأعطال الأصلية ضِمن مؤشرات Android الحيوية. يمكنك اتّباع بضع خطوات لإنشاء ملف أصلي لتصحيح أخطاء الترميز في تطبيقك وتحميله. يفعِّل هذا الملف عمليات تتبُّع تسلسُل استدعاء الدوال البرمجية الأصلية (التي تتضمّن أسماء الفئات والدوال) في مؤشرات Android الحيوية لمساعدتك في تصحيح أخطاء تطبيقك في مرحلة الإنتاج. تختلف هذه الخطوات حسب إصدار المكوّن الإضافي لنظام Gradle المتوافق مع Android المستخدَم في مشروعك، وكذلك ناتج التصميم.

الإصدار 4.1 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو الإصدارات الأحدث

إذا كان مشروعك ينشئ مجموعة حزمات تطبيق Android، يمكنك تلقائيًا تضمين ملف تصحيح أخطاء الترميز الأصلي فيها. لتضمين هذا الملف في إصدارات الإصدارات، أضِف ما يلي إلى ملف build.gradle.kts الخاص بتطبيقك:

android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }

اختَر مستوى رمز تصحيح الأخطاء مما يلي:

  • يمكنك استخدام SYMBOL_TABLE للحصول على أسماء الوظائف في عمليات تتبُّع تسلسل استدعاء الدوال البرمجية المرمزة في Play Console. يتيح هذا المستوى استخدام ملفات tombstone.
  • يمكنك استخدام FULL للحصول على أسماء الدوال والملفات وأرقام الأسطر في عمليات تتبُّع تسلسل استدعاء الدوال البرمجية المرمزة على Play Console.

إذا كان مشروعك ينشئ حزمة APK، استخدِم إعداد إصدار build.gradle.kts الموضّح سابقًا لإنشاء الملف الأصلي لتصحيح أخطاء الترميز بشكل منفصل. حمِّل يدويًا الملف الأصلي لتصحيح أخطاء الترميز إلى Google Play Console. وكجزء من عملية التصميم، يعمل المكوّن الإضافي لنظام Gradle المتوافق مع Android على إخراج هذا الملف في موقع المشروع التالي:

app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip

الإصدار 4.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو الإصدارات الأقدم (وأنظمة التصميم الأخرى)

وكجزء من عملية التصميم، يحتفظ المكوّن الإضافي لنظام Gradle المتوافق مع Android بنسخة من المكتبات التي لم تتم إزالتها في دليل المشروع. تشبه بنية الدليل هذه ما يلي:

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
  1. اضغط محتويات هذا الدليل:

    cd app/build/intermediates/cmake/universal/release/obj
    zip -r symbols.zip .
    
  2. تحميل ملف symbols.zip يدويًا إلى Google Play Console

تقليص الموارد

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

لتفعيل تقليص الموارد، اضبط السمة shrinkResources على true في النص البرمجي للإصدار (إلى جانب minifyEnabled لتقليص الرموز). مثلاً:

Kotlin

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/keep.xml. لا يقوم الإصدار بحزم هذا الملف في تطبيقك.

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

تفعيل عمليات التحقّق الصارمة من المراجع

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

على سبيل المثال، يؤدي الرمز التالي إلى وضع علامة على جميع الموارد التي تحمل البادئة img_ للإشارة إلى أنّها مُستخدَمة.

Kotlin

val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)

Java

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. إذا عثر على سلاسل مثل هذه أو غيرها من السلاسل التي يبدو أنها يمكن استخدامها لإنشاء عناوين URL مثل هذه، لن تزيلها.

في ما يلي أمثلة على وضع التصغير الآمن الذي يتم تفعيله تلقائيًا. ومع ذلك، يمكنك إيقاف طريقة المعالجة "أفضل من الأسف"، وتحديد أنّ برنامج تقليص الموارد لا يحتفظ إلا بالموارد التي يتم استخدامها بشكل مؤكّد. ولإجراء ذلك، اضبط السمة shrinkMode على strict في ملف keep.xml على النحو التالي:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

في حال تفعيل وضع التصغير المتشدد ويشير الرمز أيضًا إلى موارد ذات سلاسل تم إنشاؤها ديناميكيًا، كما هو موضّح أعلاه، عليك الاحتفاظ بهذه الموارد يدويًا باستخدام السمة tools:keep.

إزالة الموارد البديلة غير المستخدمة

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

على سبيل المثال، إذا كنت تستخدم مكتبة تتضمّن موارد لغوية (مثل AppCompat أو "خدمات Google Play")، سيتضمّن تطبيقك كل السلاسل اللغوية المترجَمة للرسائل الواردة في تلك المكتبات، سواء تمت ترجمة بقية تطبيقك إلى اللغات نفسها أم لا. إذا أردت الاحتفاظ فقط باللغات التي يوفّرها تطبيقك رسميًا، يمكنك تحديد تلك اللغات باستخدام السمة resConfig. وتتم إزالة أي موارد للغات غير محددة.

يوضح المقتطف التالي كيفية حصر موارد اللغة على اللغتين الإنجليزية والفرنسية فقط:

Kotlin

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

رائع

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

عند إصدار تطبيق باستخدام تنسيق "مجموعة حزمات تطبيق Android"، لا يتم تلقائيًا تنزيل سوى اللغات التي تم ضبطها على جهاز المستخدم عند تثبيت التطبيق. وبالمثل، لا يتم في عملية التنزيل سوى الموارد التي تطابق كثافة شاشة الجهاز والمكتبات الأصلية التي تتطابق مع واجهة التطبيق الثنائية (ABI) الخاصة بالجهاز. لمزيد من المعلومات، يُرجى الرجوع إلى ضبط مجموعة حزمات تطبيق Android.

بالنسبة إلى التطبيقات القديمة التي سيتم إصدارها باستخدام حِزم APK (تم إنشاؤها قبل آب (أغسطس) 2021)، يمكنك تخصيص كثافة الشاشة أو موارد واجهة التطبيق الثنائية (ABI) لتضمينها في حزمة APK من خلال إنشاء حِزم 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 التي تفهرس العديد من الفئات والطرق والحقول. ومع ذلك، ونظرًا لإعادة تسمية أجزاء مختلفة من الرمز البرمجي باستخدام أسلوب التشويش، تتطلب بعض المهام، مثل فحص عمليات تتبُّع تسلسل استدعاء الدوال البرمجية، أدوات إضافية. ولفهم تتبُّع تسلسل استدعاء الدوال البرمجية بعد إخفاء مفاتيح فك التشفير، يُرجى الاطّلاع على القسم المتعلق بكيفية فك ترميز عملية تتبُّع تسلسل استدعاء الدوال البرمجية التي تم تشويشها.

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

فك ترميز عملية تتبُّع تسلسل استدعاء الدوال البرمجية التي تتضمّن تشويشًا

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

تحسين الرمز

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

  • إذا كان الرمز الخاص بك لا يأخذ فرع else {} مطلقًا في عبارة if/else معيّنة، قد تزيل R8 الرمز لفرع else {}.
  • إذا استدعت التعليمة البرمجية طريقة في مكان واحد فقط، فقد تزيل R8 الطريقة وتضمّنها في موقع الاستدعاء الفردي.
  • إذا حددت R8 أن إحدى الفئات لها فئة فرعية فريدة واحدة فقط، ولم يتم إنشاء مثيل للفئة نفسها (على سبيل المثال، فئة أساسية مجردة لا تستخدمها سوى فئة تنفيذ ملموسة واحدة)، يمكن لـ R8 دمج الفئتين وإزالة فئة من التطبيق.
  • لمعرفة المزيد من المعلومات، اقرأ مشاركات مدونة تحسين R8 التي نشرها جيك وارتون.

لا تسمح لك واجهة R8 بتعطيل أو تفعيل تحسينات منفصلة أو تعديل سلوك التحسين. في الواقع، تتجاهل R8 أي قواعد ProGuard التي تحاول تعديل التحسينات التلقائية، مثل -optimizations و- optimizationpasses. وهذا التقييد مهمّ لأنّه مع استمرار تحسين R8، فإنّ الحفاظ على سلوك عادي من أجل التحسينات يساعد فريق "استوديو Android" في تحديد المشاكل وحلّها بسهولة وحلّ أي مشاكل قد تواجهك.

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

تفعيل تحسينات أكثر قوة

تتضمن الإصدار R8 مجموعة من التحسينات الإضافية (يشار إليها باسم "الوضع الكامل") والتي تجعلها تعمل بشكل مختلف عن ProGuard. ويتم تفعيل هذه التحسينات تلقائيًا منذ الإصدار 8.0.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android.

يمكنك إيقاف هذه التحسينات الإضافية من خلال تضمين ما يلي في ملف gradle.properties لمشروعك:

android.enableR8.fullMode=false

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

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

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

جارٍ سحب المكدسات

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

لاسترداد عملية تتبُّع تسلسل استدعاء الدوال البرمجية الأصلية، توفِّر R8 أداة سطر الأوامر retrace المضمَّنة في حزمة أدوات سطر الأوامر.

لإتاحة إعادة تتبُّع تسلسل استدعاء الدوال البرمجية لتطبيقك، عليك التأكّد من احتفاظ الإصدار بمعلومات كافية يمكن تتبُّعها من خلال إضافة القواعد التالية إلى ملف proguard-rules.pro في الوحدة:

-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile

تحتفظ السمة LineNumberTable بمعلومات موضعية الطرق، حيث تتم طباعة هذه المواضع في عمليات تتبع تسلسل استدعاء الدوال البرمجية. تضمن السمة SourceFile طباعة المعلومات الموضعية في جميع أوقات التشغيل المحتملة. يضبط التوجيه -renamesourcefileattribute اسم الملف المصدر في تتبعات تسلسل استدعاء الدوال البرمجية على SourceFile فقط. اسم ملف المصدر الأصلي غير مطلوب عند إعادة التتبع لأن ملف التعيين يحتوي على ملف المصدر الأصلي.

تنشئ R8 ملف mapping.txt في كل مرة يتم تشغيلها، وهي تحتوي على المعلومات اللازمة لتعيين عمليات تتبع تسلسل استدعاء الدوال البرمجية مرة أخرى إلى عمليات تتبع تسلسل استدعاء الدوال البرمجية الأصلية. يحفظ "استوديو Android" الملف في دليل <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
...

تحديد وحلّ المشاكل المتعلقة بتقليص الموارد

عند تقليص الموارد، تعرض نافذة إنشاء ملخصًا للموارد التي تمت إزالتها من التطبيق. (عليك أولاً النقر على تبديل العرض على الجانب الأيمن من النافذة لعرض إخراج نص مفصّل من Gradle.) مثلاً:

:android:shrinkDebugResources
Removed unused resources: Binary 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] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out]     &#64;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 لإبلاغ نظام الإصدار بإزالته، كما هو موضّح في القسم حول كيفية تخصيص الموارد المطلوب الاحتفاظ بها.