التحسين لمؤلفي المكتبات

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

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

إذا كنت مطوّر تطبيقات وأردت معرفة كيفية تحسين تطبيق Android، يمكنك الاطّلاع على تفعيل تحسين التطبيق. ولمعرفة المكتبات المناسبة للاستخدام، يمكنك الاطّلاع على اختيار المكتبات بحكمة.

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

هناك نوعان مختلفان من قواعد الاحتفاظ التي يمكنك استخدامها في المكتبات:

  • يجب أن تحدّد قواعد الاحتفاظ بالمستهلك القواعد التي تحتفظ بأي شيء تعكسه المكتبة. إذا كانت المكتبة تستخدم الانعكاس أو JNI لاستدعاء الرمز الخاص بها أو الرمز الذي يحدّده تطبيق العميل، يجب أن تصف هذه القواعد الرمز الذي يجب الاحتفاظ به. يجب أن تحزم المكتبات قواعد الاحتفاظ بالمستهلك التي تستخدم التنسيق نفسه لقواعد الاحتفاظ بالتطبيق. يتم تجميع هذه القواعد في عناصر المكتبة (ملفات AAR أو JAR) ويتم استخدامها تلقائيًا أثناء تحسين تطبيق Android عند استخدام المكتبة. يتم الاحتفاظ بهذه القواعد في الملف المحدّد بالخاصية consumerProguardFiles في ملف build.gradle.kts (أو build.gradle). لمزيد من المعلومات، اطّلِع على كتابة قواعد الاحتفاظ بالمستهلك.
  • يتم تطبيق قواعد الاحتفاظ ببنية المكتبة عند إنشاء مكتبتك. ولا تحتاج إليها إلا إذا قررت تحسين مكتبتك جزئيًا في وقت الإنشاء. ويجب أن يمنعوا إزالة واجهة برمجة التطبيقات العامة للمكتبة، وإلا لن تكون واجهة برمجة التطبيقات العامة متوفّرة في توزيع المكتبة، ما يعني أنّه لن يتمكّن مطوّرو التطبيقات من استخدام المكتبة. يتم الاحتفاظ بهذه القواعد في الملف المحدّد باستخدام السمة proguardFiles في ملف build.gradle.kts (أو build.gradle). لمزيد من المعلومات، يُرجى الاطّلاع على تحسين إنشاء مكتبة AAR.

المتطلبات والإرشادات المتعلقة بالتحسين

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

الالتزام بمتطلبات التحسين

يُعدّ عدم الكفاءة في المكتبات من الأسباب الرئيسية لتضخّم التطبيقات، وإهدار الذاكرة، وبطء عمليات التشغيل، وأخطاء ANR (التطبيق لا يستجيب). ويجب أن تتجنّب المكتبات مخالفة المتطلبات التالية لتفادي خفض جودة التطبيق وتجربة المستخدم بشكل كبير.

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

    لا تُدرِج قواعد الاحتفاظ على مستوى الحزمة (مثل -keep class com.mylibrary.** {*; }) للحِزم في مكتبتك أو المكتبات الأخرى المُشار إليها. وتحدّ هذه القواعد من إمكانية تحسين هذه الحِزم في جميع التطبيقات التي تستخدم مكتبتك.

  • عدم استخدام قواعد عامة غير ملائمة: لا تستخدِم أبدًا خيارات عامة مثل -dontobfuscate أو -allowaccessmodification.

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

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

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

    تستخدم العديد من المكتبات الحديثة إنشاء الرموز البرمجية بدلاً من الانعكاس. راجِع KSP للحصول على نقطة دخول شائعة تستخدمها Room وDagger2 وغيرها الكثير.

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

اقتراحات إضافية

بالإضافة إلى متطلبات التحسين، إليك بعض الاقتراحات الإضافية.

  • لا تستخدِم -repackageclasses في ملف قواعد الاحتفاظ بالمستهلكين في مكتبتك. ومع ذلك، لتحسين عملية إنشاء المكتبة، يمكنك استخدام -repackageclasses مع اسم حزمة داخلي، مثل <your.library.package>.internal، في ملف قواعد الاحتفاظ بالإنشاء الخاص بالمكتبة. ويمكن أن يساعد ذلك في تحسين كفاءة المكتبة في التطبيقات غير المحسَّنة. ومع ذلك، لا يكون ذلك ضروريًا بشكل عام، لأنّه يجب أيضًا تحسين التطبيقات.
  • عليك تعريف أي سمات تحتاج إليها لتعمل مكتبتك في ملفات قواعد الاحتفاظ بالمكتبة، حتى إذا كان هناك تداخل مع السمات المحدّدة في proguard-android-optimize.txt.
  • إذا كنت بحاجة إلى السمات التالية في توزيع مكتبتك، احتفِظ بها في ملف قواعد الاحتفاظ الخاص ببنية مكتبتك، وليس في ملف قواعد الاحتفاظ الخاص بالمستهلك في مكتبتك:
    • AnnotationDefault
    • EnclosingMethod
    • Exceptions
    • InnerClasses
    • RuntimeInvisibleAnnotations
    • RuntimeInvisibleParameterAnnotations
    • RuntimeInvisibleTypeAnnotations
    • RuntimeVisibleAnnotations
    • RuntimeVisibleParameterAnnotations
    • RuntimeVisibleTypeAnnotations
    • Signature
  • على مؤلفي المكتبات الاحتفاظ بالسمة RuntimeVisibleAnnotations في قواعد الاحتفاظ الخاصة بالمستهلكين إذا تم استخدام التعليقات التوضيحية في وقت التشغيل.
  • يجب ألا يستخدم مؤلفو المكتبات الخيارات العامة التالية في قواعد الاحتفاظ الخاصة بالمستهلكين:
    • -include
    • -basedirectory
    • -injars
    • -outjars
    • -libraryjars
    • -repackageclasses
    • -flattenpackagehierarchy
    • -allowaccessmodification
    • -renamesourcefileattribute
    • -ignorewarnings
    • -addconfigurationdebugging
    • -printconfiguration
    • -printmapping
    • -printusage
    • -printseeds
    • -applymapping
    • -obfuscationdictionary
    • -classobfuscationdictionary
    • -packageobfuscationdictionary

الحالات التي يكون فيها التفكير الذاتي مقبولاً

إذا كان عليك استخدام الانعكاس، يجب أن يكون ذلك في أحد الخيارين التاليين فقط:

  • أنواع مستهدَفة محدّدة (منفّذو واجهات أو فئات فرعية محدّدة)
  • الرمز الذي يستخدم تعليقًا توضيحيًا لوقت تشغيل معيّن

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

هذا النوع المحدّد والمستهدَف من الانعكاس هو نمط يمكنك ملاحظته في كل من إطار عمل Android (على سبيل المثال، عند تضخيم الأنشطة وطرق العرض والرسومات القابلة للرسم) ومكتبات AndroidX (على سبيل المثال، عند إنشاء WorkManager ListenableWorkers أو RoomDatabases). في المقابل، لا يكون الانعكاس المفتوح النهاية في Gson مناسبًا للاستخدام في تطبيقات Android.

الأفكار الخاطئة الشائعة

قد تؤدي بعض المفاهيم الخاطئة الشائعة إلى ضبط R8 بشكل غير صحيح، ومنها ما يلي:

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

  • تجاوز تحسين المكتبات التي تم تشويشها: من الأخطاء الشائعة استبعاد مكتبة من التحسين لأنّه تم تحسينها أو تشويشها عند تجميعها في ملف AAR (أرشيف Android) أو JAR (أرشيف Java). تكون التحسينات أثناء مدّة التصميم للمكتبة محدودة، ويجب ألا يوقف تطبيقك تحسين المكتبة من خلال تضمينها في قاعدة الاحتفاظ. لمزيد من المعلومات، يُرجى الاطّلاع على تحسين إنشاء مكتبة AAR.

  • الفهم غير الصحيح للخيار -keep: يمنع الخيار -keep تنفيذ أي من عمليات التحسين في R8. لمزيد من المعلومات، راجِع اختيار خيار الاحتفاظ المناسب.

ضبط حِزم القواعد

لضمان تطبيق قواعد الاحتفاظ بالمستهلكين بشكل صحيح، يجب تجميعها بشكل مناسب حسب تنسيق مكتبتك.

مكتبات AAR

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

Kotlin

android {
    defaultConfig {
        consumerProguardFiles("consumer-proguard-rules.pro")
    }
    ...
}

أنيق

android {
    defaultConfig {
        consumerProguardFiles 'consumer-proguard-rules.pro'
    }
    ...
}

مكتبات JAR

لتضمين قواعد مع مكتبة Kotlin أو Java يتم شحنها كملف JAR، ضَع ملف القواعد في الدليل META-INF/proguard/ ضمن ملف JAR النهائي، مع أي اسم ملف. على سبيل المثال، إذا كان الرمز البرمجي في <libraryroot>/src/main/kotlin، ضَع ملف قواعد خاصًا بالمستخدم في <libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro، وسيتم تضمين القواعد في الموقع الصحيح في ملف JAR الناتج.

تأكَّد من أنّ حِزم JAR النهائية تتضمّن القواعد بشكل صحيح من خلال التحقّق من أنّ القواعد موجودة في الدليل META-INF/proguard.

تحسين إنشاء مكتبة AAR (ميزة متقدّمة)

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

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

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

Kotlin

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        configureEach {
            consumerProguardFiles("consumer-rules.pro")
        }
    }
}

أنيق

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                'proguard-rules.pro'
        }
        configureEach {
            consumerProguardFiles "consumer-rules.pro"
        }
    }
}

يُرجى العِلم أنّ سلوك proguardFiles يختلف كثيرًا عن سلوك consumerProguardFiles:

  • يتم استخدام proguardFiles في وقت الإنشاء، وغالبًا ما يتم استخدامها مع getDefaultProguardFile("proguard-android-optimize.txt")، لتحديد الجزء الذي يجب الاحتفاظ به من المكتبة أثناء إنشاء المكتبة. والحد الأدنى لذلك هو واجهة برمجة التطبيقات العامة.
  • في المقابل، يتم تجميع consumerProguardFiles في المكتبة للتأثير في التحسينات التي تحدث لاحقًا أثناء إنشاء تطبيق يستخدم مكتبتك.

على سبيل المثال، إذا كانت مكتبتك تستخدم الانعكاس لإنشاء فئات داخلية، قد تحتاج إلى تحديد قواعد الإبقاء في كل من proguardFiles وconsumerProguardFiles.

إذا كنت تستخدم -repackageclasses في إصدار مكتبتك، أعِد تجميع الفئات في حزمة فرعية داخل حزمة مكتبتك. على سبيل المثال، استخدِم -repackageclasses 'com.example.mylibrary.internal' بدلاً من -repackageclasses 'internal'.

استخدام إصدارات مختلفة من R8 (إعدادات متقدّمة)

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

لتحديد قواعد R8 المستهدَفة، عليك تضمينها في الدليل META-INF/com.android.tools داخل classes.jar الخاص بملف AAR أو في الدليل META-INF/com.android.tools الخاص بملف JAR.

In an AAR library:
    proguard.txt (legacy location, the file name must be "proguard.txt")
    classes.jar
    └── META-INF
        └── com.android.tools (location of targeted R8 rules)
            ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
            └── ... (more directories with the same name format)

In a JAR library:
    META-INF
    ├── proguard/<ProGuard-rule-files> (legacy location)
    └── com.android.tools (location of targeted R8 rules)
        ├── r8-from-<X>-upto-<Y>/<R8-rule-files>
        └── ... (more directories with the same name format)

في الدليل META-INF/com.android.tools، يمكن أن تتوفّر عدة أدلة فرعية بأسماء على شكل r8-from-<X>-upto-<Y> للإشارة إلى إصدارات R8 التي تم إنشاء القواعد لها. يمكن أن يحتوي كل دليل فرعي على ملف واحد أو أكثر يتضمّن قواعد R8، مع أي أسماء ملفات وامتدادات.

يُرجى العِلم أنّ الأجزاء -from-<X> و-upto-<Y> اختيارية، وأنّ الإصدار <Y> حصري، وأنّ نطاقات الإصدارات تكون عادةً متواصلة ولكن يمكن أن تتداخل أيضًا.

على سبيل المثال، r8 وr8-upto-8.0.0 وr8-from-8.0.0-upto-8.2.0 وr8-from-8.2.0 هي أسماء أدلة تمثّل مجموعة من قواعد R8 المستهدَفة. يمكن لأي إصدار من R8 استخدام القواعد ضمن الدليل r8. يمكن أن يستخدم R8 القواعد الواردة في الدليل r8-from-8.0.0-upto-8.2.0 من الإصدار 8.0.0 حتى الإصدار 8.2.0 باستثناء الإصدار 8.2.0.

يستخدم المكوّن الإضافي لنظام Gradle المتوافق مع Android هذه المعلومات لاختيار جميع القواعد التي يمكن استخدامها مع إصدار R8 الحالي. إذا لم تحدّد المكتبة قواعد R8 مستهدَفة، سيختار مكوّن إضافي لنظام Gradle المتوافق مع Android القواعد من المواقع الجغرافية القديمة (proguard.txt لملف AAR أو META-INF/proguard/<ProGuard-rule-files> لملف JAR).