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

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

فهم مقاييس RSS ومقاييس التبديل

لتحليل سلوك الذاكرة في لعبتك وتصحيح أخطائها بفعالية، عليك فهم المقاييس الفنية الدقيقة التي تستخدمها منصة Android لفرض قيود الذاكرة. للحصول على معلومات تفصيلية عن طريقة معالجة ومراقبة مَعلمة القياس عن بُعد هذه في بيئة التشغيل، يُرجى الاطّلاع على مستندات Android Vitals - استخدام الذاكرة (مجموعة خدمات RSS مجهولة المصدر + مساحة التبديل).

1. ذاكرة RSS المجهولة (RssAnon)

يقيس حجم مجموعة البيانات المقيمة (RSS) جزء الذاكرة الذي يشغله أحد العمليات والمخزَّن في ذاكرة الوصول العشوائي المادية للجهاز. تنقسم ذاكرة RSS إلى ذاكرة مدعومة بملف وذاكرة مجهولة. يركّز مقياس فرض القيود في Android بشكل صارم على خلاصة RSS المجهولة الهوية:

  • ما تتضمّنه: صفحات الذاكرة التي يخصّصها تطبيق لعبتك مباشرةً والتي لا تكون مرتبطة بملف مادي على وحدة التخزين وتتضمّن هذه الصفحات مساحات تخزين مؤقتة في Java أو Kotlin، ومكدّسات تنفيذ سلاسل التعليمات، والأهم من ذلك، عمليات تخصيص الذاكرة الأصلية (مثل أدوات تخصيص المحرّك المخصّصة بلغة C++‎، أو كتل الذاكرة التي يتم طلبها باستخدام malloc أو new الأصلية والتي يتم تعديلها بواسطة منطق اللعبة). يمكنك الاطّلاع على مزيد من المعلومات حول هذا المقياس ضمن قاموس ذاكرة العملية (RSS).
  • أهمية ذلك: تستخدم محركات الألعاب مجموعات كبيرة من الذاكرة الأصلية للتعامل مع الفيزياء والعرض والمنطق. وبما أنّ هذه المجموعات لا تستند إلى ملفات، فهي تتواجد بالكامل في Anonymous RSS وتشكل الجزء الأكبر من حجم اللعبة على القرص.

2. التبديل غير المضغوط (VmSwap)

لا يتيح نظام التشغيل Android مساحة تبديل تقليدية مستندة إلى القرص بسبب قيود التلف والوقت اللازم للاستجابة في مساحة التخزين المؤقت. بدلاً من ذلك، يستخدم zRAM (مساحة تبديل غير مضغوطة):

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

3- حالات المعالجة

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

  • ما يتضمّنه: المقدّمة والخدمات المحسوسة والخلفية وذاكرة التخزين المؤقت
  • أهمية ذلك: تؤثّر حالات العمليات المختلفة بشكل مختلف في إدارة الذاكرة في نظام التشغيل Android. قد لا تعرف أنّ لعبتك تعمل بحالة عملية حساسة إذا كانت أي من حِزم SDK التابعة لجهات خارجية تشغّل مهمة في الخلفية بدون قصد. يمكنك مراقبة ما إذا كانت لعبتك تعمل في الخلفية باستخدام RunningAppProcessInfo.

واجهات برمجة التطبيقات (API)

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

الردّ على أحداث تقليل استخدام الذاكرة

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

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

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

  • مثال: تقليل أو إزالة الصور النقطية المخزّنة مؤقتًا (التي تم فك ترميزها من وحدة التخزين المحلية) استجابةً إلى TRIM_MEMORY_UI_HIDDEN

Kotlin

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {
    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

public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
    public void onTrimMemory(int level) {
        switch (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.
            }
        }
    }
}

ProfilingManager

تتيح واجهة برمجة التطبيقات ProfilingManager، التي تم طرحها في Android 15 (مستوى واجهة برمجة التطبيقات 35)، للتطبيقات إمكانية تسجيل لقطات محددة آليًا (مثل لقطات لعناصر متعدّدة وعمليات تتبُّع النظام ولقطات لأجزاء من الذاكرة في Java) مباشرةً أثناء وقت التشغيل.

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

ملاحظة: تدير محركات الألعاب الحديثة (مثل Unity أو Unreal) أداء التنفيذ من خلال التخصيص المسبق لمجموعات كبيرة من الذاكرة الافتراضية من النواة باستخدام mmap مع العلامة MAP_ANONYMOUS. تستخدم المحرّكات بعد ذلك أدوات تخصيص فرعية مخصّصة (مثل أداة إدارة الذاكرة الأصلية في Unity أو BinnedAllocators في Unreal) لتقسيم كتل الذاكرة وتخصيصها داخليًا.

ApplicationExitInfo

إذا تم إنهاء لعبتك في الخلفية أو إغلاقها بسبب انتهاكها لحدود ذاكرة العمليات الفردية، لن تسجّل آليات ملف الذاكرة عند تعطّل البرنامج العادية في Java أو الأصلية (مثل Firebase Crashlytics) الحدث. للاستعلام عن عمليات الإنهاء هذه وتسجيلها آليًا، على المطوّرين الاستفادة من واجهة برمجة التطبيقات ApplicationExitInfo عند بدء تشغيل اللعبة.

  • التنفيذ: عند البدء، اتّصِل بـ ActivityManager.getHistoricalProcessExitReasons() لاسترداد أسباب الخروج من الجلسات الحديثة.
  • أسباب الخروج من الذاكرة الرئيسية:
    • REASON_LOW_MEMORY: يشير إلى أنّ النظام أوقف العملية باستخدام أداة Low Memory Killer (LMK). يحدث هذا الإنهاء عندما يكون ضغط الذاكرة على مستوى الجهاز مرتفعًا ويجب أن يستعيد نظام التشغيل ذاكرة الوصول العشوائي. يشير سبب الخروج هذا إلى أنّ حجم مساحة التخزين التي تشغلها لعبتك في الخلفية كبير جدًا ولا يمكن أن تتواجد مع التطبيقات الأخرى.
    • REASON_MEMORY_LIMITER (الإصدار 17 من نظام التشغيل Android (المستوى 37 من واجهة برمجة التطبيقات) والإصدارات الأحدث): يشير إلى أنّه تم إيقاف العملية تحديدًا لأنّها تجاوزت حد الذاكرة المسموح به في مجموعة التحكّم (RssAnon + VmSwap) الذي حدّده برنامج Memory Limiter في النظام الأساسي. ويمكن أن يحدث هذا الإنهاء حتى إذا كانت هناك مساحة كافية من الذاكرة الفعلية المتبقية على الجهاز، ما يشير إلى انتهاك مباشر لحدود العمليات الفردية.

استخدام الأدوات المتاحة

استخدِم أدوات المنصّة التالية أثناء التطوير وضمان الجودة لقياس استخدام الذاكرة في لعبتك بدقة.

meminfo

تجمع هذه الأداة إحصاءات الذاكرة لعرض مقدار ذاكرة PSS التي تم تخصيصها والفئات التي تم استخدامها فيها.

اطبع إحصاءات meminfo بإحدى الطرق التالية:

  • استخدِم الأمر adb shell dumpsys meminfo package-name.
  • استخدِم طلب MemoryInfo من واجهة برمجة التطبيقات Android Debug API.

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

نقاط تتبُّع الذاكرة

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

Perfetto

Perfetto هي مجموعة من الأدوات لجمع معلومات الأداء والذاكرة على أحد الأجهزة وعرضها في واجهة مستخدم مستندة إلى الويب. يتيح هذا الخيار تتبُّع عمليات طويلة بشكل عشوائي، ما يتيح لك الاطّلاع على كيفية تغيُّر RSS بمرور الوقت. يمكنك أيضًا إصدار طلبات بحث بلغة SQL بشأن البيانات التي تنتجها للمعالجة بلا إنترنت. فعِّل عمليات التتبُّع الطويلة من تطبيق "تتبُّع نشاط النظام". تأكَّد من تفعيل فئة memory:Memory لعملية التتبُّع. بالنسبة إلى أدوات قياس الذاكرة المخصّصة في مراحل التطوير والاختبار، يمكنك أيضًا استخدام heapprofd API (إصدار تجريبي).

فحص RssAnon واستبداله بـ Perfetto

للتحقّق من تأثير الذاكرة المجهولة ومساحة التبديل zRAM في لعبتك، حمِّل ملف التتبُّع في واجهة المستخدم المستندة إلى الويب على ui.perfetto.dev واتّبِع تقنيات التحليل التالية المصمَّمة لدراسات حالة مفصّلة حول الذاكرة (راجِع دراسات حالة حول تحليل الذاكرة في Perfetto لمزيد من التفاصيل):

1. تصوُّر عدّادات الذاكرة على المخطّط الزمني

  • ابحث عن العملية: في قائمة التنقّل، ابحث عن حزمة لعبتك أو اسم العملية.
  • توسيع مجموعة المسارات: انقر على صف العملية لتوسيع مسارات سلاسل التنفيذ الخاصة بها، ثم ابحث عن المجموعة الفرعية المسماة "الذاكرة".
  • تحليل المقاطع الصوتية:
    • mem.rss.anon (ذاكرة RSS مجهولة): يعرض هذا الرسم البياني الخطي ذاكرة الوصول العشوائي (RAM) الفعلية التي تشغلها مجموعات الذاكرة غير المُدارة في لعبتك في الوقت الفعلي. يمكنك تتبُّع هذا المخطط الزمني أثناء تحميل المشاهد أو ظهور النوافذ المنبثقة في واجهة المستخدم أو الانتقال إلى اللعب للتحقّق من ارتفاع معدّل التخصيص.
    • mem.swap (مساحة التبديل المضغوطة أو VmSwap): يعرض هذا الرسم البياني حجم كتل الذاكرة المضغوطة مسبقًا التي تم نقلها إلى zRAM. يشير النشاط المرتفع في التبديل المتزامن مع اللعب إلى أنّ لعبتك تعمل على جهاز ذاكرة محدودة وأنّ النظام يضغط بشكل نشط على موارد الخلفية.

2. تنفيذ طلبات بحث SQL (أداة معالجة عمليات التتبُّع) لإجراء تحليل تفصيلي بلا إنترنت، يمكنك تنفيذ طلبات بحث SQL مباشرةً داخل وحدة تحكّم واجهة مستخدم Perfetto أو استخدام مكتبة Python المستقلة لأداة معالجة عمليات التتبُّع من أجل حساب القيم القصوى الإحصائية.

  • ابحث عن الحد الأقصى لمساحة RSS المجهولة المخصّصة:

    SELECT
      max(value) / 1024 / 1024 AS max_rss_anon_mb
    FROM counter
    JOIN counter_track ON counter.track_id = counter_track.id
    WHERE counter_track.name = 'mem.rss.anon'
      AND counter_track.upid IN (
        SELECT upid FROM process WHERE name = 'your.game.package.name'
      );
    
  • ربط RssAnon وVmSwap عند أي طابع زمني معيّن:

    SELECT
      ts,
      track.name AS metric_type,
      value / 1024 / 1024 AS size_mb
    FROM counter
    JOIN counter_track track ON counter.track_id = track.id
    WHERE (track.name = 'mem.rss.anon' OR track.name = 'mem.swap')
      AND track.upid IN (
        SELECT upid FROM process WHERE name = 'your.game.package.name'
      )
    ORDER BY ts ASC;
    

لمزيد من التفاصيل حول فحص ملفات التتبُّع باستخدام "استوديو Android"، يمكنك الاطّلاع على فحص عمليات تتبُّع النظام: ذاكرة العملية (RSS). للحصول على تفاصيل حول ملفات تعريف ذاكرة البرامج النصية، اطّلِع على تسجيل عمليات التخصيص الأصلية.

heapprofd

heapprofd هي أداة لتتبُّع الذاكرة وهي جزء من Perfetto. يمكن أن تساعدك هذه الأداة في العثور على تسرُّبات الذاكرة من خلال عرض الأماكن التي تم فيها تخصيص الذاكرة باستخدام malloc. يمكن بدء heapprofd باستخدام نص برمجي بلغة Python، وبما أنّ الأداة تتطلّب الحد الأدنى من الموارد، فإنّها لا تؤثّر في الأداء مثل الأدوات الأخرى، مثل Malloc Debug.

bugreport

bugreport هي أداة لتسجيل البيانات تتيح معرفة ما إذا كانت لعبتك قد تعطلت بسبب نفاد الذاكرة أم لا. تكون نتائج الأداة أكثر تفصيلاً من استخدام logcat. ويفيد هذا المقياس في تصحيح أخطاء الذاكرة لأنّه يوضّح ما إذا كانت لعبتك قد تعطّلت بسبب نفاد الذاكرة أو تم إغلاقها بسبب عُطل LMK.

لمزيد من المعلومات، يُرجى الاطّلاع على تسجيل تقارير الأخطاء وقراءتها.

أدوات محرّك الألعاب

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

Unity

في بيئة Unity Engine، يمكنك تقدير استهلاك الذاكرة في Android Anonymous RSS + Swap بدقة عالية أثناء وقت التشغيل (عادةً ما يكون التباين أقل من% 10 مقارنةً بالقيم الحقيقية على مستوى نظام التشغيل) باستخدام أدوات وفئات إنشاء الملفات الشخصية الأصلية في Unity.

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

  • واجهة برمجة التطبيقات الخاصة بأداة Unity لتحديد المشاكل: يمكنك تقدير استهلاك الذاكرة غير المُدارة في لعبتك آليًا في وقت التشغيل من خلال طلب مقاييس المحرّك الأساسية:
    • استخدام فئة Profiler: تتبُّع إجمالي عمليات تخصيص الذاكرة من خلال جمع قيمتَي Profiler.GetTotalReservedMemoryLong() وProfiler.GetMonoHeapSizeLong().
    • استخدام فئة ProfilerRecorder: مراقبة فئات الذاكرة بشكل ديناميكي لإنشاء تقريب أساسي موثوق به، يمكنك جلب إجمالي الذاكرة المحجوزة (في الإصدارات العلنية) أو طرح ذاكرة الرسومات المحجوزة من إجمالي الذاكرة المحجوزة (في إصدارات التطوير) لإزالة مكوّنات ذاكرة الرسومات المستندة إلى الملفات.
  • أداة تحليل بيانات ذاكرة Unity: لتحديد أخطاء تسريب الذاكرة وتصحيحها بلا إنترنت، يمكنك التقاط لقطة لذاكرة الجهاز وفحص الرسم البياني "الذاكرة المقيمة على الجهاز" (Resident Memory on Device) ضمن قسم "كل الذاكرة" (All of Memory). لاحتساب المساحة التقريبية، اجمع إجمالي الفئات التالية: غير متتبّع وAndroid Runtime وNative وManaged.
    • قيود zRAM: في حال توفّر مساحة ذاكرة محدودة، يمكن لنواة Android ضغط صفحات الذاكرة غير النشطة في مساحة التبديل (zRAM). بما أنّ أداة Unity Memory Profiler لا يمكنها رصد مَعلمات التبديل على مستوى نظام التشغيل، قد تلاحظ اختلافات طفيفة في حجم الذاكرة المستخدَمة أثناء مشاهد الذاكرة الكبيرة. يمكنك المقارنة بين مصادر بيانات متعددة لتقديراتك مع Perfetto للتأكّد من القيم الدقيقة.