توضّح هذه الصفحة كيفية تقليل استخدام الذاكرة بشكل استباقي داخل تطبيقك. للحصول على معلومات حول كيفية إدارة نظام التشغيل Android للذاكرة، راجِع نظرة عامة على إدارة الذاكرة.
ذاكرة الوصول العشوائي (RAM) هي مورد قيّم لأي بيئة لتطوير البرامج، وهي أكثر قيمة لنظام التشغيل على الأجهزة الجوّالة حيث تكون الذاكرة الفعلية محدودة في كثير من الأحيان. على الرغم من أنّ كلًا من وقت تشغيل Android (ART) والجهاز الافتراضي Dalvik يجريان عملية جمع البيانات غير الضرورية بشكل روتيني، لا يعني ذلك أنّه يمكنك تجاهل وقت ومكان تخصيص تطبيقك للذاكرة وتحريرها. ومع ذلك، عليك تجنُّب حدوث تسرُّبات الذاكرة، والتي تحدث عادةً بسبب الاحتفاظ بمراجع الكائنات في متغيرات الأعضاء الثابتة، وإصدار أي كائنات Reference في الوقت المناسب كما هو محدّد من خلال عمليات معاودة الاتصال بدورة الحياة.
تقليل حجم الرموز والموارد في تطبيقك
يمكن أن تستهلك بعض الموارد والمكتبات داخل الرمز البرمجي الذاكرة بدون أن تدرك ذلك. يمكن أن يؤثّر الحجم الإجمالي لتطبيقك، بما في ذلك المكتبات الخارجية أو الموارد المضمّنة، في مقدار الذاكرة التي يستهلكها تطبيقك. يمكنك تحسين استهلاك الذاكرة في تطبيقك من خلال إزالة المكوّنات والموارد والمكتبات المكرّرة أو غير الضرورية أو المفرطة من الرمز البرمجي.
تقليل الحجم الإجمالي للتطبيق من خلال تفعيل R8
يشكّل الرمز البرمجي للتطبيق المجمَّع جزءًا نشطًا من مساحة الذاكرة المستخدَمة في وقت التشغيل. يجب تحميل كل فئة وطريقة واعتماد على مكتبة وثابت سلسلة إلى ذاكرة الوصول العشوائي (RAM) عند التشغيل. وكلما زاد حجم قاعدة الرموز البرمجية المجمَّعة، زادت سعة ذاكرة الوصول العشوائي (RAM) الفعلية التي يحتاجها تطبيقك.
يمكنك استخدام R8 لتقليل استهلاك الذاكرة في تطبيقك. على الرغم من أنّ الرمز R8 معروف تقليديًا بتقليل حجم حِزم APK، إلا أنّه يؤثّر بشكل مباشر وإيجابي في ذاكرة الوصول العشوائي (RAM) أثناء التشغيل. تحلّل أداة R8 الرمز الثانوي لتطبيقك من أجل إزالة الرموز غير النشطة ودمج الفئات المكرّرة والأساليب المضمّنة وتقليل حجم المعرّفات. ومن خلال تحميل عدد أقل من الرموز البايتية المترجمة من حزمة APK إلى ذاكرة الوصول العشوائي، يتم تقليل مساحة الذاكرة الأساسية الإجمالية التي يشغلها التطبيق. بالإضافة إلى ذلك، يؤدي تصغير أسماء الفئات والأساليب والحقول إلى معرّفات أقصر إلى تقليل الحمل الزائد لذاكرة الوصول العشوائي بشكل مباشر. تؤدي عمليات التحسين، مثل دمج الفئات وتضمين الدوال البرمجية على نطاق واسع، إلى استبدال عمليات البحث وعمليات التخصيص المكلفة في وقت التشغيل، ما يؤدي إلى تحسين الذاكرة المخصّصة في الكومة وفي المكدّس.
فهم قواعد الاحتفاظ
قواعد الاحتفاظ هي تعليمات إعدادات تخبر R8 بأجزاء الرمز البرمجي التي يجب الاحتفاظ بها أثناء التحسين، ما يمنعها من إزالة الرمز البرمجي الذي يعتمد عليه تطبيقك أو تصغيره. لمزيد من المعلومات، يُرجى الاطّلاع على نظرة عامة على قواعد الاحتفاظ.
تمنع قواعد الحفاظ المكتوبة بشكل سيئ أداة R8 من تحسين أجزاء كبيرة من قاعدة الرموز البرمجية. تجنَّب قواعد الاحتفاظ الواسعة النطاق واتّبِع أفضل الممارسات التالية:
- القواعد العامة التي يجب تجنُّبها:
-
-dontoptimize: يؤدي إلى إيقاف التحسين للتطبيق بأكمله، ما ينتج عنه ملفات تنفيذية أكبر وأبطأ. -
-dontshrink: يمنع إزالة الرموز والموارد غير المستخدَمة. -
-dontobfuscate: يمنع تصغير الأسماء، ما يؤدي إلى عدم الاستفادة من توفير الذاكرة (خاصةً في التطبيقات الكبيرة).
-
تجنُّب أحرف البدل على مستوى الحزمة: تؤدي القواعد العامة، مثل
-keep class com.example.package.** { *; }، إلى إجبار R8 على الاحتفاظ بكل فئة وحقل وطريقة في تلك الحزمة. يؤدي ذلك إلى إيقاف قدرة R8 تمامًا على إزالة الرمز أو تحسينه أو تصغيره في تلك الحزمة.استخدام ملف الإعداد التلقائي في R8: استخدِم
proguard-android-optimize.txtدائمًا.
لمزيد من المعلومات حول كتابة قواعد الاحتفاظ، يُرجى الاطّلاع على نظرة عامة على قواعد الاحتفاظ. للاطّلاع على الأنماط المحدّدة التي يجب استخدامها وتجنُّبها، راجِع أفضل الممارسات المتعلّقة بقواعد الاحتفاظ.
تقدّم أداة "محلّل إعدادات R8" إحصاءات حول إعدادات R8 وكيفية تأثير كل قاعدة إبقاء في تطبيقك. لمزيد من المعلومات حول كيفية تحديد القواعد التي تحظر التحسين، راجِع محلّل إعدادات R8.
توخَّ الحذر بشأن استخدام المكتبات الخارجية
في كثير من الأحيان، لا تتم كتابة رمز المكتبة الخارجية لبيئات الأجهزة الجوّالة، وقد يكون غير فعّال للعمل على برنامج عميل على جهاز جوّال. عند استخدام مكتبة خارجية، قد تحتاج إلى تحسينها للأجهزة الجوّالة. يجب التخطيط لهذا العمل مسبقًا وتحليل المكتبة من حيث حجم الرمز البرمجي ومساحة ذاكرة الوصول العشوائي (RAM) قبل استخدامها.
حتى بعض المكتبات المحسّنة للأجهزة الجوّالة يمكن أن تتسبّب في حدوث مشاكل بسبب اختلاف عمليات التنفيذ. على سبيل المثال، قد تستخدم إحدى المكتبات بروتوكولات lite protobufs بينما تستخدم مكتبة أخرى بروتوكولات micro protobufs، ما يؤدي إلى تنفيذ بروتوكولات protobuf مختلفة في تطبيقك. ويمكن أن يحدث ذلك مع عمليات التنفيذ المختلفة للتسجيل والتحليلات وأُطر تحميل الصور والتخزين المؤقت والعديد من الأشياء الأخرى التي لا تتوقّعها.
على الرغم من أنّ تحسين تطبيقك باستخدام R8 يمكن أن يزيل الرموز غير المستخدَمة من التبعيات، إلا أنّ فعاليته غالبًا ما تكون محدودة بسبب الإعداد الداخلي للمكتبة. على سبيل المثال، يمكن أن تمنع قواعد الاحتفاظ الواسعة أو استخدام الانعكاس داخل مكتبة أداة R8 من تصغير الرمز البرمجي، ما يؤدي إلى زيادة حجم الذاكرة. للاطّلاع على استراتيجيات اختيار مكتبات فعّالة، راجِع مقالة اختيار المكتبات بحكمة.
تجنَّب استخدام مكتبة مشتركة لميزة واحدة أو اثنتين فقط من بين عشرات الميزات. لا تستخدِم كمية كبيرة من الرموز البرمجية والنفقات العامة التي لا تحتاج إليها. عند التفكير في استخدام مكتبة، ابحث عن طريقة تنفيذ تتطابق بشكل كبير مع ما تحتاجه. بخلاف ذلك، يمكنك إنشاء عملية التنفيذ الخاصة بك.
استخدام Hilt أو Dagger 2 لتوفير التبعيات
يمكن أن تبسّط أُطر عمل إدخال التبعية الرمز الذي تكتبه وتوفّر بيئة قابلة للتكيّف ومفيدة للاختبارات وتغييرات الإعدادات الأخرى.
إذا كنت تنوي استخدام إطار عمل لإدخال التبعيات في تطبيقك، ننصحك باستخدام Hilt أو Dagger. Hilt هي مكتبة لتوفير التبعيات في Android، وتعمل على أساس Dagger. لا تستخدم Dagger الانعكاس لمسح رمز تطبيقك ضوئيًا. يمكنك استخدام التنفيذ الثابت لوقت الترجمة البرمجية في Dagger في تطبيقات Android بدون تكلفة غير ضرورية لوقت التشغيل أو استخدام الذاكرة.
تستخدم أُطر عمل أخرى لتضمين الاعتمادية تستخدم الانعكاس في إعداد العمليات من خلال مسح الرمز بحثًا عن علامات التوضيح. قد تتطلّب هذه العملية عددًا أكبر بكثير من دورات وحدة المعالجة المركزية وذاكرة الوصول العشوائي، وقد تتسبّب في تأخير ملحوظ عند تشغيل التطبيق.
عند استخدام ميزة "إدخال التبعية"، احرص على تجنُّب تسرُّب الذاكرة من خلال التأكّد من تحديد نطاق العناصر بشكل مناسب. قد يؤدي الاحتفاظ بالكائنات لفترة أطول من اللازم من خلال ربطها بدورة حياة غير صحيحة إلى حدوث تسرُّب للذاكرة. لمزيد من المعلومات، راجِع الإرشادات حول تجنُّب تسرُّب الذاكرة باستخدام العناصر ذات النطاق المحدود.
تحديد الغرض من تحميل الصور
عادةً ما تكون الصور النقطية هي أكبر العناصر الشائعة التي تتواجد في ذاكرة تطبيقك. حتى إذا كنت تعمل باستخدام ملفات مضغوطة مثل ملفات JPEG، يجب تضخيم الملف إلى صورة نقطية غير مضغوطة ليتم عرضه على الشاشة. يمكن أن يتوسّع ملف صورة مضغوط صغير ليصبح صورة نقطية كبيرة جدًا.
على سبيل المثال، تستخدم معظم الصور النقطية إعداد ARGB_8888، ما يعني أنّ كل بكسل يتطلّب 4 بايت من الذاكرة، أي بايت واحد لكل من الأحمر والأخضر والأزرق وقناة ألفا (الشفافية). إذا كان لديك ملف JPEG بحجم 100 كيلوبايت وعرضته في عرض بحجم 1000×1000 بكسل، ستتطلّب الصورة النقطية 4 بايت لكل بكسل من هذه البكسلات البالغ عددها مليون بكسل، ما يؤدي إلى إضافة ما يصل إلى 4 ميغابايت من الذاكرة.
هناك العديد من الإجراءات التي يمكنك اتّخاذها لتحسين استخدامك للصور. على سبيل المثال، يمكن أن تساعدك مكتبات تحميل الصور في تحرير الذاكرة عندما لا تكون هناك حاجة إليها. للحصول على معلومات حول كيفية التعامل مع الصور النقطية بكفاءة، يمكنك الاطّلاع على مقالة تحسين الصور النقطية.
مراقبة الذاكرة المتاحة واستخدام الذاكرة
يجب العثور على مشاكل استخدام الذاكرة في تطبيقك قبل أن تتمكّن من حلّها. يساعدك محلّل الذاكرة في استوديو Android في العثور على مشاكل الذاكرة وتشخيصها بالطرق التالية:
- الاطّلاع على كيفية تخصيص تطبيقك للذاكرة بمرور الوقت يعرض محلّل استخدام الذاكرة رسمًا بيانيًا في الوقت الفعلي لمقدار الذاكرة التي يستخدمها تطبيقك، وعدد عناصر Java التي تم تخصيصها، ووقت تنفيذ عملية جمع البيانات غير الضرورية.
- يمكنك بدء أحداث جمع البيانات غير المرغوب فيها وأخذ لقطة من مساحة التخزين المؤقت في Java أثناء تشغيل تطبيقك.
- تسجيل عمليات تخصيص الذاكرة في تطبيقك وفحص جميع العناصر التي تم تخصيصها وعرض تتبُّع تسلسل استدعاء الدوال البرمجية لكل عملية تخصيص والانتقال إلى الرمز البرمجي المقابل في أداة التعديل في "استوديو Android"
تتكامل أداة تحليل استخدام الذاكرة أيضًا مع مكتبة LeakCanary لرصد تسرُّبات الذاكرة. باستخدام LeakCanary، يمكنك نقل تحليل تسرُّب الذاكرة من الجهاز التجريبي إلى جهاز التطوير، ما قد يؤدي إلى تسريع سير العمل بشكل كبير. لمزيد من المعلومات، يُرجى الاطّلاع على ملاحظات إصدار "استوديو Android".
تتوفّر أدوات أخرى يمكنك استخدامها لتشخيص مشاكل الذاكرة استنادًا إلى بيانات من المستخدمين الذين يشغّلون تطبيقك على الإصدار العلني:
- استخدام "مؤشرات Android الحيوية" لتتبُّع أحداث "إغلاق التطبيقات بسبب نقص الذاكرة" (LMK)
- استخدِم أداة Profiling Manager لتتبُّع أخطاء نفاد الذاكرة، بالإضافة إلى سلوك التطبيق غير الطبيعي الذي قد يكون ناتجًا عن تسرُّب الذاكرة.
تحرير الذاكرة استجابةً للأحداث
يمكن لنظام التشغيل 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 للبيانات المتسلسلة
بروتوكولات المخزن المؤقت (protobufs) هي آلية قابلة للتوسيع ومصمَّمة من قِبل Google، وهي لا تعتمد على لغة أو نظام أساسي، وتهدف إلى تسلسل البيانات المنظَّمة، وهي تشبه XML، ولكنها أصغر حجمًا وأسرع وأبسط. إذا كنت تستخدم بروتوكولات protobuf لبياناتك، استخدِم دائمًا بروتوكولات protobuf الخفيفة في الرمز البرمجي من جهة العميل. تنتج بروتوكولات protobuf العادية رمزًا برمجيًا مطوّلاً للغاية، ما يزيد من حجم الرمز البرمجي لتطبيقك في ذاكرة الوصول العشوائي (RAM) (راجِع مقالة إدارة حجم الرمز البرمجي لتطبيقك وتحسينه) ويساهم في زيادة حجم حِزمة APK.
لمزيد من المعلومات، يُرجى الاطّلاع على ملف readme الخاص بـ protobuf.
توخّي الحذر بشأن تسرّبات الذاكرة
يمكن أن تؤدي إدارة المراجع غير السليمة إلى حدوث تسرُّبات في الذاكرة، ما يؤدي إلى بقاء العناصر نشطة لفترة أطول من عمرها المفيد، ويمنع جامع البيانات غير الضرورية من استعادة مساحة الذاكرة التي تم تسريبها. لتجنُّب تسرب الذاكرة، عليك تنفيذ تصميم يراعي مراحل النشاط.
لمزيد من المعلومات، يُرجى الاطّلاع على تسرب الذاكرة.
تجنُّب الاستخدام الزائد للذاكرة
لا تؤثّر أحداث جمع البيانات غير المرغوب فيها في أداء تطبيقك. ومع ذلك، يمكن أن تؤدي العديد من أحداث جمع البيانات غير الضرورية التي تحدث خلال فترة زمنية قصيرة إلى استنزاف البطارية بسرعة، كما يمكن أن تزيد بشكل طفيف من الوقت اللازم لإعداد اللقطات بسبب التفاعلات الضرورية بين أداة جمع البيانات غير الضرورية وسلاسل التطبيق. وكلما زاد الوقت الذي يستغرقه النظام في جمع البيانات غير المرغوب فيها، زادت سرعة استنزاف البطارية.
في كثير من الأحيان، يمكن أن يؤدي الاستخدام الزائد للذاكرة إلى حدوث عدد كبير من أحداث جمع البيانات غير المرغوب فيها. في الواقع، يصف الاستخدام الزائد للذاكرة عدد الكائنات المؤقتة التي يتم تخصيصها خلال فترة زمنية معيّنة.
على سبيل المثال، يمكنك تخصيص كائنات مؤقتة متعددة ضمن حلقة for.
أو قد تنشئ كائنَي Paint أو Bitmap جديدَين داخل الدالة onDraw() الخاصة بأحد العروض. في كلتا الحالتين، ينشئ التطبيق عددًا كبيرًا من العناصر بسرعة وبكميات كبيرة. ويمكن أن تستهلك هذه العمليات بسرعة كل الذاكرة المتاحة في الجيل الشاب، ما يؤدي إلى حدوث حدث لجمع البيانات غير الضرورية.
استخدِم أداة تحليل الذاكرة للعثور على الأماكن التي يكون فيها الاستخدام الزائد للذاكرة مرتفعًا في الرمز البرمجي قبل أن تتمكّن من إصلاحها.
بعد تحديد المناطق التي تتضمّن مشاكل في الرمز، حاوِل تقليل عدد عمليات التخصيص في المناطق المهمة من حيث الأداء. يمكنك نقل العناصر خارج الحلقات الداخلية أو نقلها إلى بنية تخصيص مستندة إلى المصنع.
يمكنك أيضًا تقييم ما إذا كانت مجموعات العناصر ستفيد حالة الاستخدام. باستخدام مجموعة من العناصر، بدلاً من إسقاط مثيل عنصر على الأرض، يمكنك إطلاقه في مجموعة بعد أن تصبح غير ضرورية. في المرة التالية التي تحتاج فيها إلى مثيل كائن من هذا النوع، يمكنك الحصول عليه من المجموعة بدلاً من تخصيصه.
قيِّم الأداء بدقة لتحديد ما إذا كان تجميع العناصر مناسبًا في حالة معيّنة. هناك حالات قد تؤدي فيها مجموعات العناصر إلى تدهور الأداء. على الرغم من أنّ المجموعات تتجنّب عمليات التخصيص، إلا أنّها تتضمّن تكاليف إضافية أخرى. على سبيل المثال، يتطلّب الحفاظ على المجموعة عادةً المزامنة، ما يؤدي إلى تكلفة إضافية كبيرة. بالإضافة إلى ذلك، يمكن أن يؤدي محو مثيل العنصر المجمّع لتجنُّب حدوث تسرُّب للذاكرة أثناء الإصدار ثم تهيئته أثناء الاكتساب إلى حدوث تكلفة إضافية غير صفرية.
يؤدي الاحتفاظ بعدد كبير من مثيلات العناصر في المجموعة أكثر من اللازم إلى زيادة عبء عملية جمع البيانات غير المرغوب فيها. على الرغم من أنّ مجموعات العناصر تقلّل عدد عمليات استدعاء جمع البيانات غير المرغوب فيها، فإنّها تؤدي في النهاية إلى زيادة مقدار العمل المطلوب لكل عملية استدعاء، لأنّها تتناسب مع عدد وحدات البايت النشطة (التي يمكن الوصول إليها).