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

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

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

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

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

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

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

يمكن لنظام 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 حدًا أقصى لحجم الذاكرة المخصصة لكل منها التطبيق. يختلف الحدّ الأقصى الدقيق لحجم الذاكرة بين الأجهزة بناءً على مقدار ذاكرة الوصول العشوائي (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;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

تجنُّب اضطرابات الذاكرة

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

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

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

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

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

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

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

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

إزالة الموارد والمكتبات ذات الاستهلاك الكثيف للذاكرة

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

تقليل الحجم الإجمالي لحِزم APK

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

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

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

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

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

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

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

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

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

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

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