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