في برمجة Android الشائعة، غالبًا ما يتم استخدام Handler عند إجراء عمليات غير متزامنة ومعالجة النتائج التي تم إرجاعها. عادةً ما يتم تنفيذ الكود الخاص بنا على هذا النحو.
انسخ رمز الكود كما يلي:
الطبقة العامة SampleActivity تمتد النشاط {
المعالج النهائي الخاص mLeakyHandler = new Handler() {
@تجاوز
مقبض الفراغ العام (رسالة رسالة) {
// ...
}
}
}
ومع ذلك، في الواقع، قد يتسبب الكود أعلاه في حدوث تسرب للذاكرة. عند استخدام أداة Android lint، سوف تحصل على مثل هذا التحذير.
انسخ رمز الكود كما يلي:
في Android، يجب أن تكون فئات المعالج ثابتة وإلا قد يحدث تسرب، كما تحتفظ الرسائل الموجودة في قائمة انتظار الرسائل الخاصة بمؤشر ترابط التطبيق بالمعالج المستهدف إذا كان المعالج فئة داخلية، فسيتم الاحتفاظ بفئتها الخارجية أيضًا. قم بتعريف المعالج كفئة متداخلة ثابتة مع مرجع ضعيف لفئتها الخارجية
عند رؤية هذا، ربما لا تزال في حيرة من أمرك حيث يمكن أن يتسبب في تسرب الذاكرة في الكود، وكيف يمكن أن يسبب تسربًا للذاكرة؟ ثم دعونا نحللها ببطء.
1. عند بدء تشغيل تطبيق Android، يتم إنشاء مثيل Looper تلقائيًا للاستخدام بواسطة سلسلة المحادثات الرئيسية للتطبيق. تتمثل المهمة الرئيسية لـ Looper في معالجة كائنات الرسائل في قائمة انتظار الرسائل واحدًا تلو الآخر. في Android، يتم وضع جميع أحداث إطار عمل Android (مثل استدعاءات أسلوب دورة حياة النشاط ونقرات الأزرار وما إلى ذلك) في الرسائل، ثم يتم إضافتها إلى قائمة انتظار الرسائل لتتم معالجتها بواسطة Looper، ويكون Looper مسؤولاً عن معالجتها مرة واحدة بواحد. دورة حياة Looper في الخيط الرئيسي هي نفس دورة حياة التطبيق الحالي.
2. عندما تتم تهيئة المعالج في سلسلة الرسائل الرئيسية ونرسل رسالة تستهدف هذا المعالج إلى قائمة انتظار الرسائل التي تتم معالجتها بواسطة Looper، فإن الرسالة المرسلة بالفعل تحتوي بالفعل على مرجع لمثيل المعالج، وبهذه الطريقة فقط تتم معالجة Looper فقط تم استلام الرسالة ويمكن استدعاء Handler#handleMessage(Message) لإكمال المعالجة الصحيحة للرسالة.
3. في Java، تحتوي الفئات الداخلية غير الثابتة والفئات الداخلية المجهولة ضمنيًا على إشارات إلى فئاتها الخارجية. لا تحتوي الفئات الداخلية الثابتة على إشارات إلى الفئات الخارجية. لمزيد من المعلومات حول هذا، يرجى مراجعة Chat Java: المعدل الخاص "غير صالح".
صحيح أن مثال التعليمات البرمجية أعلاه يصعب قليلاً اكتشاف تسرب الذاكرة، والمثال التالي واضح جدًا
انسخ رمز الكود كما يلي:
الطبقة العامة SampleActivity تمتد النشاط {
المعالج النهائي الخاص mLeakyHandler = new Handler() {
@تجاوز
مقبض الفراغ العام (رسالة رسالة) {
// ...
}
}
@تجاوز
باطل محمي onCreate(حزمة saveInstanceState) {
super.onCreate(savedInstanceState);
// أرسل رسالة وتأخير تنفيذها لمدة 10 دقائق.
mLeakyHandler.postDelayed(new Runnable() {
@تجاوز
تشغيل الفراغ العام () { /* ... */ }
}, 1000*60*10);
// العودة إلى النشاط السابق.
ينهي()؛
}
}
بتحليل الكود أعلاه، عندما نقوم بتنفيذ طريقة إنهاء النشاط، ستكون الرسالة المؤجلة موجودة في قائمة انتظار رسائل الموضوع الرئيسي لمدة 10 دقائق قبل معالجتها، وتحتوي هذه الرسالة على مرجع إلى المعالج، والمعالج مجهول مثيل تحتوي الفئة الداخلية على مرجع إلى SampleActivity الخارجي، لذلك يتسبب هذا في عدم إمكانية إعادة تدوير SampleActivity، ونتيجة لذلك، لا يمكن إعادة تدوير العديد من الموارد التي يحتفظ بها SampleActivity، وهذا ما نسميه غالبًا تسرب الذاكرة.
لاحظ أن Runnable الجديد أعلاه يتم تنفيذه أيضًا بواسطة فئة داخلية مجهولة، كما أنه يحمل مرجعًا إلى SampleActivity ويمنع إعادة تدوير SampleActivity.
لحل هذه المشكلة، لا تتمثل الفكرة في استخدام فئات داخلية غير ثابتة عند وراثة المعالج، إما أن تضعه في ملف فئة منفصل أو تستخدم فئة داخلية ثابتة. نظرًا لأن الفئة الداخلية الثابتة لا تحتوي على مرجع للفئة الخارجية، فلن تتسبب في تسرب الذاكرة لمثيل الفئة الخارجية. عندما تحتاج إلى استدعاء نشاط خارجي في فئة داخلية ثابتة، يمكننا استخدام مراجع ضعيفة للتعامل معه. بالإضافة إلى ذلك، يحتاج Runnable أيضًا إلى تعيينه كسمة عضو ثابتة. ملاحظة: لا يحتوي مثيل الفئة الداخلية الثابتة والمجهولة على مرجع للفئة الخارجية. الكود الذي لن يسبب تسرب للذاكرة بعد التعديل هو كما يلي:
انسخ رمز الكود كما يلي:
الطبقة العامة SampleActivity تمتد النشاط {
/**
* لا تحتوي مثيلات الفئات الداخلية الثابتة على أي معنى ضمني
* إشارة إلى طبقتهم الخارجية.
*/
فئة ثابتة خاصة MyHandler تمتد Handler {
WeakReference النهائي الخاص <SampleActivity> mActivity؛
عامة MyHandler(نشاط SampleActivity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@تجاوز
مقبض الفراغ العام (رسالة رسالة) {
نشاط SampleActivity = mActivity.get();
إذا (النشاط! = فارغ) {
// ...
}
}
}
نهائي خاص MyHandler mHandler = new MyHandler(this);
/**
* مثيلات الفئات المجهولة لا تحمل ضمنيًا
* الإشارة إلى طبقتهم الخارجية عندما تكون "ثابتة".
*/
نهائي ثابت خاص قابل للتشغيل sRunnable = جديد Runnable() {
@تجاوز
تشغيل الفراغ العام () { /* ... */ }
};
@تجاوز
باطل محمي onCreate(حزمة saveInstanceState) {
super.onCreate(savedInstanceState);
// أرسل رسالة وتأخير تنفيذها لمدة 10 دقائق.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// العودة إلى النشاط السابق.
ينهي()؛
}
}
في الواقع، يحدث العديد من حالات تسرب الذاكرة في Android بسبب استخدام الفئات الداخلية غير الثابتة في النشاط، كما هو مذكور في هذه المقالة، لذلك يجب أن نولي اهتمامًا خاصًا عند استخدام الفئات الداخلية غير الثابتة إذا كانت دورة حياة مثيلاتها الكائن المحفوظ أكبر من كائن الطبقة الخارجية، فقد يؤدي ذلك إلى تسرب الذاكرة. أنا شخصياً أميل إلى استخدام الفئات الثابتة للمقالة والأساليب المرجعية الضعيفة لحل هذه المشكلة.