English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

مكافحة تسرب الذاكرة باستخدام Android Studio + MAT

للتمسك بالتسربات الذاكرية، في Android إذا لم يكن الانتباه، من السهل الظهور، خاصة في Activity، من السهل الظهور، وسأذكر كيف أبحث عن تسربات الذاكرة.

أولاً، ما هو تسرب الذاكرة؟

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

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

البحث عن تسرب الذاكرة:

على سبيل المثال، النص الأتي:

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String string = new String();
  }
  public void click(View view){
    Intent intent = new Intent();
    intent.setClass(getApplicationContext(), SecondActivity.class);
    startActivity(intent);
  }
}
public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}

 

في كل مرة نقفز إلى هذه النشاطية يتم استدعاء سطر عمل، ثم يقوم هذا السطر العمل بتنفيذ طريقة run من runnable، حيث أن Runnable هو كائن داخلي مجهول، لذا يتم امتلاك إشارة إلى SecondActivity، لذا بسهولة يمكننا أن ننتقل من MainActivity إلى SecondActivity، سنقوم هنا بالنزول من MainActivity إلى SecondActivity، ثم العودة إلى MainActivity، وبهذا الشكل 5 مرات متتالية، حتى نعود إلى MainActivity في النهاية، وفقًا للعادة، بعد العودة من SecondActivity إلى MainActivity، يجب أن يتم تدمير وتحرير SecondActivity، ولكن قد لا يكون هذا هو الحال في الواقع.

عند هذه النقطة، لتحديد ما إذا كان قد حدث إنفجار ذاكرة أو لا، يجب استخدام الأدوات! هناك طريقتان

1. استخدم أداة MAT للبحث

أولاً، افتح أداة Android Device Monitor في AS كما هو موضح في الصورة التالية:


بعد فتحه سيظهر لك الواجهة التالية


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


الآن ما علينا القيام به هو تشغيل تطبيقنا وإجراء التبديل بينه وبين تطبيقات أخرى 5 مرات.

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

الصورة للملف الذي يتم تصديره كما هو موضح في الصورة التالية:

بعد الحصول على ملف hprof، يمكننا استخدام أداة MAT للتحليل،

فتح أداة MAT

إذا لم يكن لديك، يمكنك تنزيله من الرابط التالي

رابط تحميل أداة MAT

الواجهة كما هو موضح في الصورة التالية:

عند فتح ملف hprof الذي قمنا بتصديره سابقًا، قد يظهر الخطأ التالي

هذا لأن MAT مصمم لتحليل ملفات hprof لبرامج Java، ويختلف قليلاً في الشكل عن ملفات hprof التي تستخرج من Android، لذا نحتاج إلى تحويل ملفات hprof المستخرجة، يقدم لنا SDK أداة التحويل hprof-conv.exe في الموضع التالي في الصورة


الآن نذهب إلى هذا المسار ونن�行 هذا الأمر لتحويل ملفات hprof الخاصة بنا، كما هو موضح في الصورة التالية


استخدام أمر hprof-conv بهذه الطريقة

hprof-conv ملف المصدر ملف الخروج

مثلًا، hprof-conv E:\aaa.hprof E:\output.hprof

هذا هو تحويل aaa.hprof إلى output.hprof كخروج، output.hprof هو الملف الذي تم تحويله، mat2.hprof هو الملف الذي تم تحويله في الصورة.

الآن، افتح ملف mat2.hprof بعد تحويله باستخدام أدوات MAT، وسيتم فتحه بدون خطأ كما هو موضح في الشكل التالي:


بعد ذلك يمكننا التحقق من الأجسام الموجودة في ذاكرة النظام حاليًا، حيث تحدث تسرب الذاكرة عادةً في Activity، لذا يكفي البحث عن Activity.

النقر على أيقونة QQL المميزة في الصورة التالية، ثم إدخال select * from instanceof android.app.Activity

مثل جملة SQL للبحث عن معلومات Activity ذات الصلة، النقر على العلامة العلوية الحمراء تنفيذًا ثم كما هو موضح في الشكل التالي:

next, we can see the filtered Activity information below

as shown in the figure above, there are still 6 SecondActivity instances in memory, but we want to exit all of them, which indicates that a memory leak has occurred

there are two properties, Shallow size and Retained Size

Shallow Size
the memory size occupied by the object itself, excluding the objects it references. For non-array type objects, its size is the sum of the object and all its member variables.
of course, it also includes some data storage units of java language features. For array type objects, its size is the sum of the sizes of array element objects.
Retained Size
Retained Size = current object size + total size of objects that can be directly or indirectly referenced by the current object. (The meaning of indirect reference: A->B->C, C is indirect reference)
however, when releasing, we still need to exclude objects that are directly or indirectly referenced by GC Roots. They will not be considered as garbage temporarily.

next, right-click on a SecondActivity


select with all references

open the page as shown in the figure below

view the page in the figure below

see that this0 refers to thisActivitywhilethis0 represents the meaning of an inner class, that is, an inner class refers to Activity, and this$0 is referenced by target, target is a thread, the reason has been found, the reason for the memory leak is that Activity is referenced by the inner class and the inner class is used by the thread, so it cannot be released, let's go to the code of this class

public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}
بالطبع، في

بالطبع، هناك كائن فئةRunnable داخلي في SecondActivity، ثم يتم استخدامه من قبل الثنائي، ويتطلب الثنائي تنفيذ 8000 ثانية، لذا يتم الاشارة إلى كائن SecondActivity وسيكون من المستحيل إطلاقه، مما يؤدي إلى انفجار الذاكرة.

لحل هذا النوع من انفجار الذاكرة، يجب إنهاء الثنائي عند مغادرة Activity (لكن ليس من الجيد القيام بذلك..)، أو التحكم الجيد في وقت تنفيذ الثنائي.

بهذا نكون قد اكتشفنا انفجار الذاكرة في هذا البرنامج.

2. استخدم Monitor Memory في Android Studio مباشرة للبحث عن انفجار الذاكرة

ما زلت أستخدم نفس البرنامج، سأقوم بالشرح ببساطة.

أولاً، قم بتشغيل البرنامج على الهاتف، افتح واجهة Monitor في AS لرؤية صورة Memory

بالنقر على أيقونة الصندوق الصغير (أيقونة الموقع 1 في الصورة) يمكن إطلاق GC مرة واحدة


بالنقر على أيقونة الموقع 2 في الصورة يمكن النظر في ملف hprof

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

بهذا يكون قد تم إكمال البحث عن تسرب الذاكرة.

أسباب تسرب الذاكرة في Android تُقسم بشكل عام إلى أنواع التالية:

1. تسرب الذاكرة بسبب المتغيرات الثابتة

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

الحلول:

عند استخدام getApplicationContext عند استخدام المتغيرات الثابتة في Activity، لأن دورة حياة Application من بداية البرنامج إلى نهايته مثل المتغيرات الثابتة.

2. تسرب الذاكرة بسبب الثنائي

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

AsyncTask يحتوي على مكتبة من السطور، ويكون المشكلة أكثر خطورة

الحلول:

1. تنظيم وقت تنفيذ السطر، وتحكم في انتهاء السطر قبل انتهاء Activity.

2. قم بتغيير الكلاس الداخلي إلى الكلاس الداخلي الثابت، واستخدم WeakReference لتح Konsol Activity لأن المرجع الضعيف يُعيد إليه الذاكرة عند اكتشافها من قبل GC، لذا يمكن إعادة التدوير بشكل أسرع

3. استخدام bitmap يأخذ الكثير من الذاكرة

تحليل bitmap يتطلب استخدام ذاكرة، ولكن يوفر الذاكرة فقط 8M لBitMap، إذا كان هناك الكثير من الصور ولم يتم تكرار recycle bitmap في الوقت المناسب، فإن ذلك سيؤدي إلى تجاوز الذاكرة

الحلول:

يجب تكرار تكرار الصور بعد إعادة تدويرها قبل تحميل الصور

4. تسرب ذاكرة بسبب عدم إغلاق الموارد في الوقت المناسب

على سبيل المثال، إذا لم يتم إغلاق Cursor في الوقت المناسب، سيتم الحفاظ على مرجع إلى Activity، مما يؤدي إلى تسرب ذاكرة

الحلول:

يمكن إغلاقها في الوقت المناسب في method onDestory

5. تسرب ذاكرة بسبب استخدام Handler

بسبب استخدام Handler، يتم إرسال كائن message إلى MessageQueue، ثم يقوم Looper بالتحقق من MessageQueue وينتزع Message لتنفيذه، ولكن إذا لم يتم سحب Message لفترة طويلة، بسبب أن Message يحتوي على مرجع إلى Handler، وعادةً ما يكون Handler كائن داخلي، يرتبط Message بHandler، ويقوم Handler بمرجع إلى Activity، مما يجعل Activity غير قابلة للإزالة.

الحلول:

يمكن حل ذلك باستخدام الطريقة المحددة من قبل كلاً من الكلاس الداخلي الثابت والمرجع الضعيف

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

أعجبك هذا