إدارة ذاكرة تطبيقك

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

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

تقليل حجم الرموز والموارد في تطبيقك

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

تقليل الحجم الإجمالي للتطبيق من خلال تفعيل R8

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

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

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

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

تمنع قواعد الحفاظ المكتوبة بشكل سيئ أداة R8 من تحسين أجزاء كبيرة من قاعدة الرموز البرمجية. تجنَّب قواعد الاحتفاظ الواسعة النطاق واتّبِع أفضل الممارسات التالية:

  • القواعد العامة التي يجب تجنُّبها:
    • -dontoptimize: يؤدي إلى إيقاف التحسين للتطبيق بأكمله، ما يؤدي إلى إنشاء ملفات تنفيذية أكبر وأبطأ.
    • -dontshrink: يمنع إزالة الرموز والموارد غير المستخدَمة.
    • -dontobfuscate: يمنع تصغير الأسماء، ما يؤدي إلى عدم الاستفادة من توفير الذاكرة (خاصةً في التطبيقات الكبيرة).
  • تجنُّب أحرف البدل على مستوى الحزمة: تؤدي القواعد العامة، مثل -keep class com.example.package.** { *; }، إلى إجبار R8 على الاحتفاظ بكل فئة وحقل وطريقة في تلك الحزمة. يؤدي ذلك إلى إيقاف قدرة R8 تمامًا على إزالة الرمز أو تحسينه أو تصغيره في تلك الحزمة.

  • استخدام ملف الإعداد التلقائي في R8: استخدِم proguard-android-optimize.txt دائمًا.

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

تقدّم أداة "محلّل إعدادات R8" إحصاءات حول إعدادات R8 وكيفية تأثير كل قاعدة إبقاء في تطبيقك. لمزيد من المعلومات حول كيفية تحديد القواعد التي تحظر التحسين، راجِع محلّل إعدادات R8.

توخَّ الحذر بشأن استخدام المكتبات الخارجية

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

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

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

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

استخدام Hilt أو Dagger 2 لتوفير التبعيات

يمكن أن تُبسّط أُطر عمل إدخال التبعية الرمز الذي تكتبه وتوفّر بيئة قابلة للتكيّف ومفيدة للاختبار والتغييرات الأخرى في الإعدادات.

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

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

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

تحديد الغرض من تحميل الصور

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

على سبيل المثال، تستخدم معظم الصور النقطية إعدادات ARGB_8888، ما يعني أنّ كل بكسل يتطلّب 4 بايت من الذاكرة، أي بايت واحد لكل من الأحمر والأخضر والأزرق وقناة ألفا (الشفافية). إذا كان لديك ملف JPEG بحجم 100 كيلوبايت وعرضته في عرض 1000×1000 بكسل، ستتطلّب الصورة النقطية 4 بايت لكل من هذه البكسلات البالغ عددها 1,000,000، ما يصل إلى 4 ميغابايت من الذاكرة.

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

مراقبة الذاكرة المتاحة واستخدام الذاكرة

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

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

تتوفّر أدوات أخرى يمكنك استخدامها لتشخيص مشاكل الذاكرة استنادًا إلى بيانات من المستخدمين الذين يشغّلون تطبيقك على الإصدار العلني:

تحرير الذاكرة استجابةً للأحداث

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

يجب أن يركّز تنفيذ onTrimMemory() حصريًا على الحدثَين TRIM_MEMORY_UI_HIDDEN وTRIM_MEMORY_BACKGROUND. (اعتبارًا من Android 14، لم يعُد النظام يرسل إشعارات بشأن الثوابت الأخرى القديمة. تم إيقاف استخدام هذه الثوابت نهائيًا في Android 15).

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

  • TRIM_MEMORY_BACKGROUND: تشير هذه الإشارة إلى أنّ عمليتك موجودة في الخلفية، وهي الآن مرشّحة للإنهاء من أجل تلبية احتياجات الذاكرة العامة للنظام. ولإطالة المدة التي تظل فيها عمليتك في حالة التخزين المؤقت وتقليل عدد عمليات بدء تشغيل التطبيق الباردة، عليك إيقاف أي موارد يمكن إعادة إنشائها بسهولة عندما يستأنف المستخدم جلسته.

تعرض عيّنة الرمز البرمجي هذه كيفية تنفيذ عملية الاسترجاع onTrimMemory() للاستجابة لأحداث مختلفة ذات صلة بالذاكرة:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

التحقّق من حجم الذاكرة التي تحتاج إليها

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

لتجنُّب نفاد الذاكرة، يمكنك طلب معلومات من النظام لتحديد مقدار مساحة الذاكرة المؤقتة المتوفّرة على الجهاز الحالي. يمكنك طلب هذا الرقم من النظام من خلال استدعاء getMemoryInfo(). تعرض هذه السمة عنصر ActivityManager.MemoryInfo يقدّم معلومات حول حالة الذاكرة الحالية للجهاز، بما في ذلك الذاكرة المتاحة وإجمالي الذاكرة وحدّ الذاكرة الأدنى، أي مستوى الذاكرة الذي يبدأ عنده النظام في إيقاف العمليات. يعرض العنصر ActivityManager.MemoryInfo أيضًا lowMemory، وهو قيمة منطقية بسيطة تخبرك ما إذا كانت مساحة الذاكرة في الجهاز منخفضة.

يوضّح مقتطف الرمز البرمجي التالي كيفية استخدام طريقة getMemoryInfo() في تطبيقك.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

رصد عمليات إيقاف التطبيقات بسبب انخفاض الذاكرة

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

تُعد عمليات إيقاف العمليات التي تعمل في المقدّمة من أهم المؤشرات في "مؤشرات Android الحيوية" لأنّها تُستخدَم كوكيل عالي الدقة لإدارة الذاكرة بشكل غير سليم. في حين أنّ أي معدّل LMK أعلى من% 1 يشير إلى ضرورة اتخاذ إجراء فوري، فإنّ المعدّل المنخفض ليس بالضرورة مؤشرًا على سلامة النظام. قد يشير انخفاض معدّل أعطال "إغلاق التطبيقات بسبب نقص الذاكرة" التي لاحظها المستخدمون إلى أنّ عملية LMK daemon توقف العمليات بشكل متكرّر أثناء تشغيلها في الخلفية، ما يؤدي إلى تدهور أداء "التشغيل السريع" وسلاسة تنفيذ المهام المتعددة. لذلك، ننصحك باتّباع أفضل الممارسات المتعلّقة بالذاكرة بغض النظر عن نتيجة LMK الحالية، وذلك لضمان الاستقرار على المدى الطويل وحالة الجهاز الجيدة.

استخدام ProfilingManager لتتبُّع مشاكل الذاكرة

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

هناك مشغّلان جديدان تم تقديمهما مع Android 17 وهما مفيدان بشكل خاص في رصد مشاكل الذاكرة:

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

لمزيد من المعلومات حول استخدام ProfilingManager لتسجيل عمليات التشغيل واسترجاعها آليًا، يُرجى الاطّلاع على مستندات إنشاء الملفات الشخصية المستندة إلى عمليات التشغيل.

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

استخدام بنى رموز برمجية أكثر فعالية من حيث استخدام الذاكرة

تستهلك بعض ميزات Android وفئات Java وبُنى الرموز البرمجية مقدارًا أكبر من الذاكرة مقارنةً بغيرها. يمكنك تقليل مقدار الذاكرة التي يستخدمها تطبيقك من خلال اختيار بدائل أكثر كفاءة في الرمز البرمجي.

استخدام الخدمات باعتدال

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

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

بشكل عام، تجنَّب استخدام الخدمات الدائمة بسبب المتطلبات المستمرة التي تفرضها على الذاكرة المتاحة. بدلاً من ذلك، ننصحك باستخدام بديل، مثل WorkManager. لمزيد من المعلومات حول كيفية استخدام WorkManager لجدولة العمليات التي تعمل في الخلفية، راجِع العمل المستمر.

استخدام حاويات البيانات المحسّنة

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

يتضمّن إطار عمل Android العديد من حاويات البيانات المحسَّنة، بما في ذلك SparseArray وSparseBooleanArray وLongSparseArray. على سبيل المثال، تكون فئات SparseArray أكثر فعالية لأنّها تتجنّب حاجة النظام إلى التحويل التلقائي للمفتاح وأحيانًا للقيمة، ما يؤدي إلى إنشاء عنصر أو عنصرين آخرين لكل إدخال.

يمكنك دائمًا التبديل إلى المصفوفات الأولية للحصول على بنية بيانات بسيطة إذا لزم الأمر.

يجب التعامل بحذر مع تجريدات الرموز

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

استخدام بروتوكولات lite protobufs للبيانات المتسلسلة

بروتوكولات المخزن المؤقت هي آلية قابلة للتوسيع ومصمَّمة من قِبل Google، وهي مستقلة عن اللغة والنظام الأساسي، وتُستخدم في تسلسل البيانات المنظَّمة، وهي تشبه XML، ولكنها أصغر حجمًا وأسرع وأبسط. إذا كنت تستخدم بروتوكولات المخزن المؤقت لبياناتك، استخدِم دائمًا بروتوكولات المخزن المؤقت الخفيفة في الرمز البرمجي من جهة العميل. وتنتج بروتوكولات المخزن المؤقت العادية رمزًا برمجيًا مطوَّلاً للغاية، ما يزيد من حجم الرمز البرمجي لتطبيقك في ذاكرة الوصول العشوائي (RAM) (راجِع إدارة حجم الرمز البرمجي لتطبيقك وتحسينه) ويساهم في زيادة حجم حِزمة APK.

لمزيد من المعلومات، يُرجى الاطّلاع على ملف readme الخاص بـ protobuf.

توخّي الحذر بشأن تسرّبات الذاكرة

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

تجنُّب الاستخدام الزائد للذاكرة

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

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

على سبيل المثال، يمكنك تخصيص عناصر مؤقتة متعددة ضمن حلقة for. أو قد تنشئ كائنات Paint أو Bitmap جديدة داخل الدالة onDraw() الخاصة بأحد العروض. في كلتا الحالتين، ينشئ التطبيق الكثير من العناصر بسرعة وبكميات كبيرة. ويمكن أن تستهلك هذه العمليات بسرعة كل الذاكرة المتاحة في الجيل الشاب، ما يؤدي إلى حدوث حدث لجمع البيانات غير الضرورية.

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

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

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

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

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