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

ولجعل تطبيقك صغيرًا وسريعًا قدر الإمكان، عليك تحسين بنية الإصدار وتصغيرها باستخدام isMinifyEnabled = true.

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

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

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

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

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

عند استخدام الإصدار 3.4.0 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو الإصدار 3.4.0 والإصدارات الأحدث من "استوديو 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

            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.

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

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

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

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

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

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

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

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

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

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

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

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

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

تخصيص الرمز الذي تريد الاحتفاظ به

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

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

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

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

-keep public class MyClass

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

هناك العديد من الاعتبارات التي يجب مراعاتها عند استخدام الخيار -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. وكجزء من عملية التصميم، يعمل المكوّن الإضافي Android Gradle على إخراج هذا الملف في موقع المشروع التالي:

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 مختلف لكل صيغة إصدار عندما تعلم أنّ موردًا معيّنًا يبدو مستخدَمًا في الرمز البرمجي (وبالتالي لا تتم إزالته من خلال أداة التخفيض)، ولكنك تدرك أنّه لن يتم استخدامه لصيغة الإصدار المحددة. من المحتمل أيضًا أن تكون أدوات التصميم قد حددت موردًا بشكل غير صحيح حسب الحاجة، وهذا أمر محتمل لأن أداة التجميع تضيف معرّفات الموارد بشكل مضمّن، ومن ثم قد لا تعرف أداة تحليل الموارد الفرق بين مورد تمت الإشارة إليه فعليًا وقيمة عدد صحيح في الرمز البرمجي، وبالتالي قد يكون له نفس القيمة.

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

عادة، يمكن ل تقليص الموارد تحديد ما إذا كان سيتم استخدام المورد بدقة. في المقابل، إذا يستدعي الرمز الخاص بك Resources.getIdentifier() (أو إذا كان ذلك في أي من مكتباتك، وستعمل مكتبة AppCompat 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 على تحسين أداء الرمز البرمجي في وقت التشغيل (بما في ذلك وقت بدء التشغيل ووقت عرض اللقطة في سلسلة واجهة المستخدم) بنسبة تصل إلى %30. ويؤدي إيقاف أي منها إلى تقييد مجموعة التحسينات التي تستخدمها R8 بشكل كبير.

إذا تم تفعيل R8، عليك أيضًا إنشاء ملفات شخصية لبدء التشغيل لتحسين أداء الشركات الناشئة.

تفعيل تحسينات أكثر صرامة

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

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

android.enableR8.fullMode=false

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

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

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

جارٍ تتبُّع عمليات تتبُّع تسلسل استدعاء الدوال البرمجية

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

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

لإتاحة تتبُّع عمليات تتبُّع تسلسُل استدعاء الدوال البرمجية في التطبيق، يجب التأكّد من احتفاظ الإصدار بمعلومات كافية يمكن تتبُّعها من خلال إضافة القواعد التالية إلى ملف 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] @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 لإبلاغ نظام التصميم بإزالته، كما هو موضّح في القسم حول كيفية تخصيص الموارد التي يجب الاحتفاظ بها.