كيفية تسرب الذاكرة مع الاشتراكات في RxJava

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

حل مهمة بسيطة

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

النهج القائم على MVP هو واحد من العديد من الطرق للقيام بذلك. يمكنك إنشاء طريقة عرض بسيطة تحتوي على كل من الحاجيات ProProBarBarTextView. يعالج RecommendedMovieUseCase توفير عنوان فيلم عشوائي.
يتصل مقدم العرض بحالة الاستخدام ويعرض عنوانًا على طريقة العرض. يتم تنفيذ حفظ حالة مقدم العرض عن طريق الاحتفاظ به في الذاكرة حتى عندما يتم إعادة إنشاء نشاطك (فيما يسمى NonConfigurationScope).

هنا هو كيف يبدو مقدم الخاص بك. لغرض هذه المقالة ، دعنا نفترض أنك تريد تخزين علامة تشير إلى ما إذا كان المستخدم قد نقر العنوان.

ستتم إضافة عنصر واجهة المستخدم إلى حاوية أرجوانية عندما يطلب المستخدم توصية. ستتم إزالته بعد أن يقرر المستخدم مسحه.

المنتج الحد الأدنى قابلة للحياة ، هاه؟

يبدو أن كل شيء يعمل بشكل جيد في الوقت الحالي.

لتكون أكثر أمانًا ، قررنا تهيئة StrictMode في تصميمات التصحيح.
نبدأ في اللعب مع تطبيقنا ومحاولة تدوير أجهزتنا عدة مرات. فجأة ، تظهر رسالة السجل.

StrictMode يضربنا مع تحذير ودية

هذا لا يبدو صحيحا. يمكنك تجربة إلقاء حالة الذاكرة الحالية وإلقاء نظرة أعمق على المشكلة:

تفريغ الذاكرة بعد تحذير StrictMode

هناك الجاني الخاص بك ملحوظ مع الخط الأزرق. لسبب ما ، لا يزال هناك مثيل لـ MovieSuggestionView يحتفظ بمرجع إلى مثيل MainActivity قديم.

لكن لماذا؟ لقد ألغيت اشتراكك في وظيفة الخلفية الخاصة بك وقمت أيضًا بمسح المرجع إلى MovieSuggestionView عند إسقاط العرض من مقدم العرض. من أين يأتي هذا التسرب؟

البحث عن تسرب

عن طريق تخزين مرجع إلى اشتراك ، فأنت تخزن فعليًا نسخة من ActionSubscriber ، والتي تبدو كما يلي:

نظرًا لكون الحقول onNext و onError و onCompleted نهائية ، لا توجد طريقة نظيفة لإبطالها. المشكلة هي أن استدعاء إلغاء الاشتراك () على المشترك يضع علامة فقط على أنه غير مشترك (واثنين من الأشياء ، ولكن هذا ليس مهمًا في حالتنا).

بالنسبة لأولئك الذين يتساءلون من أين يأتي كائن ActionSubscriber ، ألق نظرة على تعريف طريقة الاشتراك:

يثبت التحليل الإضافي لتفريغ الذاكرة أن مرجع MovieSuggestionView لا يزال موجودًا بالفعل داخل حقول onNext و onError.

لا يزال يتم الرجوع إلى طريقة العرض بواسطة ActionSubscriber

لفهم المشكلة بشكل أفضل ، دعنا نحفر أعمق قليلاً ونرى ما يحدث بعد تجميع الشفرة.

=> ls -1 app / build / intermediates / classes / debug / me / scana / subscribeleak
...
مقدم $ 1.class
مقدم $ 2.class
Presenter.class
...

يمكنك أن ترى أنه بالإضافة إلى فصل مقدم العرض الرئيسي ، يمكنك الحصول على ملفين إضافيين للفصل الدراسي ، أحدهما لكل فئة من فئات Action1 <> المجهولة التي قدمتها.

دعونا نتحقق مما يحدث داخل أحد تلك الفئات المجهولة باستخدام أداة javap سهلة الاستخدام:

=> javap -c Presenter \ $ 1

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

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

أنت تعرف ما هو الخطأ في الحل الحالي لدينا. إذا كيف يمكنك إصلاحه؟

إنه سهل للغاية.

يمكنك تعيين كائن الاشتراك الخاص بنا إلى Subscription.empty () ، وبالتالي مسح الإشارة إلى ActionObserver القديم.

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

لحسن الحظ ، هناك طريقة واضحة () متاحة ، والتي تلغي اشتراك كل شيء ثم تقوم بإزالة المراجع. كما يسمح لك بإعادة استخدام كائن CompositeSubscription بدلاً من إلغاء الاشتراك () مما يجعل الكائن غير صالح للاستخدام.

هنا فئة مقدم العرض الثابت مع تنفيذ إحدى الطرق المذكورة أعلاه:

تجدر الإشارة إلى أنه يمكنك بالفعل حل هذه المشكلة بعدة طرق مختلفة. ضع في اعتبارك دائمًا أنه لا توجد رصاصة فضية لكل مشكلة تثيرها.

لتلخيص الأشياء:

  • تحتوي كائنات الاشتراك على مراجع نهائية لعمليات الاسترجاعات الخاصة بك. يمكن أن تحتوي عمليات الاسترجاعات الخاصة بك على مراجع لكائنات دورة حياة Android. كلاهما يمكن أن تسرب الذاكرة عندما لا تعامل بعناية
  • يمكنك استخدام أدوات مثل StrictMode و javap و HPROF Viewer للعثور على مصدر التسريبات وتحليلها. لم أذكرها في المقال ، لكن يمكنك أيضًا الاطلاع على مكتبة LeakCanary من سكوير.
  • يساعد البحث في المكتبات التي تستخدمها يوميًا في حل المشكلات المحتملة التي قد تنشأ

شكر!

آمل أن تكون قد استمتعت بالمقال وربما تعلمت شيئًا
جديد منه.
لا تتردد في طرح سؤال ومشاركة أفكارك حول العمل مع RxJava في قسم التعليق أدناه!
يمكنك أيضًا الوصول إلي على حساب Twitter الخاص بي :)

قد تجد أيضًا اهتمامًا:

الاستيلاء على رمز من هذه المادة:
https://github.com/scana/subscriptions-leak-example

إصدار حول المراجع النهائية في المشتركين على GitHub:
https://github.com/ReactiveX/RxJava/issues/3148

تطبيق NonConfigurationScope:
https://github.com/partition/Dagger-Non-Configuration-Scope

StrictMode لنظام Android: https://developer.android.com/reference/android/os/StrictMode.html

javap ، و Java Class File Disassembler
http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javap.html