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

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

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

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

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

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

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

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

يمكنك تنفيذ استدعاء 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) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> {
                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
            }
        }
    }
}

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) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /*
                   Release any memory your app doesn't need to run.

                   The device is running low on memory while the app is running.
                   The event raised indicates the severity of the memory-related event.
                   If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system
                   begins stopping background processes.
                */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /*
                   Release as much memory as the process can.

                   The app is on the LRU list and the system is running low on memory.
                   The event raised indicates where the app sits within the LRU list.
                   If the event is TRIM_MEMORY_COMPLETE, the process is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

الاطّلاع على حجم الذاكرة التي تحتاجها

للسماح بعمليات متعدّدة قيد التشغيل، يضبط Android حدًا صارمًا لحجم الذاكرة المخصَّصة لكل تطبيق. ويختلف الحد الدقيق لحجم ذاكرة التخزين المؤقت باختلاف الأجهزة استنادًا إلى حجم ذاكرة الوصول العشوائي المُتاح على الجهاز بشكل عام. إذا وصل تطبيقك إلى سعة كومة الذاكرة المؤقتة وحاول تخصيص المزيد من الذاكرة، سيعرض النظام الخطأ 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;
}

استخدام تركيبات رموز أكثر كفاءة في الذاكرة

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

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

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

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

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

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

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

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

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

توخي الحذر بشأن تجريدات الرموز

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

استخدام النماذج الأولية المبسّطة للبيانات المتسلسلة

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

لمزيد من المعلومات، يُرجى الاطّلاع على protobufreadme.

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

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

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

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

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

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

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

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

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

إزالة الموارد والمكتبات التي تستهلك قدرًا كبيرًا من الذاكرة

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

تقليل الحجم الكلي لحزمة APK

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

لمزيد من المعلومات عن تقليل الحجم الكلي للتطبيق، راجِع تقليل حجم التطبيق.

استخدام Hilt أو Dagger 2 لإدخال التبعية

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

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

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

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

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

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

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

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