التعامل مع مراحل النشاط باستخدام المكوّنات المدرِكة لمراحل النشاط جزء من Android Jetpack.
تُنفِّذ المكوّنات المُدركة لحالة النشاط إجراءات استجابةً لتغيير في حالة نشاط مكوّن آخر، مثل الأنشطة والمقاطع. تساعدك هذه المكوّنات في إنشاء رمز برمجي منظَّم بشكل أفضل وغالبًا ما يكون أخف وزنًا، ويسهل الحفاظ عليه.
من الأنماط الشائعة تنفيذ إجراءات المكوّنات التابعة في methods lifecycle (طرق دورة الحياة) للأنشطة والمقاطع. ومع ذلك، يؤدّي هذا النمط إلى تنظيم الرموز البرمجية بشكلٍ سيئ وانتشار الأخطاء. باستخدام المكونات الواعية بمراحل النشاط، يمكنك نقل رمز المكونات التابعة خارج methods lifecycle (طرق دورة النشاط) إلى المكونات نفسها.
توفّر حزمة androidx.lifecycle
فئات وواجهات تتيح لك إنشاء مكونات تراعي مراحل النشاط
، وهي مكونات يمكنها تعديل سلوكها تلقائيًا استنادًا إلى حالة دورة النشاط الحالية أو المكوّن المصغّر.
إنّ معظم مكوّنات التطبيق المحدّدة في إطار عمل Android مرتبطة بدورات حياة. تتم إدارة دورات الحياة من خلال نظام التشغيل أو رمز الإطار الذي يتم تشغيله في العملية. وهي أساسية لعمل Android، ويجب أن يلتزم تطبيقك بها. وقد يؤدي عدم إجراء ذلك إلى تسرُّب الذاكرة أو حتى تعطُّل التطبيق.
لنفترض أنّ لدينا نشاطًا يعرض الموقع الجغرافي للجهاز على الشاشة. قد يكون أحد التنسيقات المعتادة على النحو التالي:
Kotlin
internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } } class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } }
Java
class MyLocationListener { public MyLocationListener(Context context, Callback callback) { // ... } void start() { // connect to system location service } void stop() { // disconnect from system location service } } class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; @Override public void onCreate(...) { myLocationListener = new MyLocationListener(this, (location) -> { // update UI }); } @Override public void onStart() { super.onStart(); myLocationListener.start(); // manage other components that need to respond // to the activity lifecycle } @Override public void onStop() { super.onStop(); myLocationListener.stop(); // manage other components that need to respond // to the activity lifecycle } }
على الرغم من أنّ هذا العيّنة يبدو جيدًا، إلا أنّه في التطبيق الفعلي، سينتهي بك الأمر بإجراء عددٍ كبير جدًا من
الطلبات التي تدير واجهة المستخدم والمكونات الأخرى استجابةً للحالة الراهنة
للرحلة. تؤدي إدارة مكوّنات متعددة إلى وضع قدر كبير من
الرمز البرمجي في طرق دورة النشاط، مثل onStart()
و
onStop()
، ما يجعل من الصعب الحفاظ عليها.
بالإضافة إلى ذلك، لا يمكن ضمان بدء المكوّن قبل إيقاف النشاط أو القطعة. وينطبق ذلك بشكل خاص إذا احتجنا إلى تنفيذ
عملية تستغرق وقتًا طويلاً، مثل التحقّق من بعض الإعدادات في onStart()
. ويمكن أن يؤدي ذلك إلى حدوث تعارض في المعالجة حيث تنتهي طريقة onStop()
قبل onStart()
، ما يحافظ على تنشيط المكوّن لفترة أطول مما هو مطلوب.
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start() } } } public override fun onStop() { super.onStop() myLocationListener.stop() } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, location -> { // update UI }); } @Override public void onStart() { super.onStart(); Util.checkUserStatus(result -> { // what if this callback is invoked AFTER activity is stopped? if (result) { myLocationListener.start(); } }); } @Override public void onStop() { super.onStop(); myLocationListener.stop(); } }
توفّر حزمة androidx.lifecycle
صفوفًا وواجهات تساعدك في معالجة هذه المشاكل بطريقة
متينة ومعزولة.
مراحل النشاط
Lifecycle
هي فئة
تحتوي على معلومات عن حالة دورة حياة المكوّن (مثل
نشاط أو جزء) وتسمح للكائنات الأخرى بمراقبة هذه الحالة.
يستخدم Lifecycle
قائمتَين أساسيتَين من الأرقام الترتيبية لتتبُّع حالة دورة حياة المكوّن المرتبط به:
- حدث
- أحداث دورة النشاط التي يتم إرسالها من إطار العمل و
Lifecycle
يتم ربط هذه الأحداث بأحداث الاستدعاء في الأنشطة والمقاطع. - الولاية
- الحالة الحالية للمكوّن الذي يتم تتبُّعه من خلال كائن
Lifecycle
.
يمكنك اعتبار الحالات على أنّها عقد في رسم بياني والأحداث على أنّها الحواف بين هذه العقد.
يمكن لفئة مراقبة حالة دورة حياة المكوّن من خلال تنفيذ DefaultLifecycleObserver
وتجاوز الطرق المقابلة، مثل onCreate
وonStart
وما إلى ذلك.
يمكنك بعد ذلك إضافة مراقب من خلال استدعاء addObserver()
طريقة Lifecycle
الفئة وضبط مثيل للمراقب، كما هو موضّح في المثال التالي:
Kotlin
class MyObserver : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { connect() } override fun onPause(owner: LifecycleOwner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(MyObserver())
Java
public class MyObserver implements DefaultLifecycleObserver { @Override public void onResume(LifecycleOwner owner) { connect() } @Override public void onPause(LifecycleOwner owner) { disconnect() } } myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
في المثال أعلاه، ينفِّذ العنصر myLifecycleOwner
واجهة
LifecycleOwner
، والتي يتم شرحها في القسم التالي.
LifecycleOwner
LifecycleOwner
هي
واجهة طريقة واحدة تشير إلى أنّ الفئة تحتوي على
Lifecycle
. يحتوي على أسلوب واحد، وهو
getLifecycle()
،
الذي يجب أن تنفذه الفئة.
إذا كنت تحاول إدارة دورة حياة عملية تطبيق بالكامل بدلاً من ذلك، اطّلِع على ProcessLifecycleOwner
.
تُنشئ هذه الواجهة تمثيلًا مجردًا لملكية
Lifecycle
من فئات
فردية، مثل Fragment
وAppCompatActivity
، وتسمح بكتابة مكوّنات
تعمل معها. يمكن لأي فئة تطبيق مخصّصة تنفيذ واجهة
LifecycleOwner
.
تعمل المكوّنات التي تنفِّذ DefaultLifecycleObserver
بسلاسة مع المكوّنات التي تنفِّذ LifecycleOwner
لأنّ المالك يمكنه توفير دورة حياة يمكن للمراقِب تسجيلها لمراقبتها.
في مثال تتبُّع الموقع الجغرافي، يمكننا جعل فئة MyLocationListener
تنفِّذ DefaultLifecycleObserver
ثم تبدأها باستخدام Lifecycle
للنشاط في طريقة onCreate()
. يتيح ذلك لصفّ
MyLocationListener
أن يكون مكتفيًا ذاتيًا، ما يعني أنّه يتمّ تعريف منطق
الاستجابة للتغييرات في حالة دورة الحياة في MyLocationListener
بدلاً من
النشاط. إنّ تخزين المكونات الفردية لمنطقها الخاص يسهّل
إدارة منطق الأنشطة والمقاطع.
Kotlin
class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this, lifecycle) { location -> // update UI } Util.checkUserStatus { result -> if (result) { myLocationListener.enable() } } } }
Java
class MyActivity extends AppCompatActivity { private MyLocationListener myLocationListener; public void onCreate(...) { myLocationListener = new MyLocationListener(this, getLifecycle(), location -> { // update UI }); Util.checkUserStatus(result -> { if (result) { myLocationListener.enable(); } }); } }
ومن حالات الاستخدام الشائعة تجنُّب استدعاء عمليات ردّ اتصال معيّنة إذا لم يكن
Lifecycle
في حالة
جيدة في الوقت الحالي. على سبيل المثال، إذا كان الإجراء المُعاد الاتصال به يُجري معاملة لجزء بعد
حفظ حالة النشاط، سيؤدي ذلك إلى حدوث عطل، لذا لا نريد أبدًا
استدعاء هذا الإجراء المُعاد الاتصال به.
لتسهيل حالة الاستخدام هذه، تسمح فئة
Lifecycle
للكائنات الأخرى باستعلام الحالة الحالية.
Kotlin
internal class MyLocationListener( private val context: Context, private val lifecycle: Lifecycle, private val callback: (Location) -> Unit ): DefaultLifecycleObserver { private var enabled = false override fun onStart(owner: LifecycleOwner) { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } override fun onStop(owner: LifecycleOwner) { // disconnect if connected } }
Java
class MyLocationListener implements DefaultLifecycleObserver { private boolean enabled = false; public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { ... } @Override public void onStart(LifecycleOwner owner) { if (enabled) { // connect } } public void enable() { enabled = true; if (lifecycle.getCurrentState().isAtLeast(STARTED)) { // connect if not connected } } @Override public void onStop(LifecycleOwner owner) { // disconnect if connected } }
من خلال هذا التنفيذ، تكون فئة LocationListener
مدركة بالكامل لمراحل دورة الحياة. إذا أردنا استخدام LocationListener
من نشاط
أو جزء آخر، ما علينا سوى إعداده. تدير الفئة نفسها جميع عمليات الإعداد والإزالة.
إذا كانت المكتبة توفّر فئات تحتاج إلى العمل مع دورة حياة Android، ننصحك باستخدام مكوّنات تراعي دورة الحياة. يمكن لعملاء مكتبتك دمج هذه المكوّنات بسهولة بدون إدارة دورة الحياة يدويًا من جانب العميل.
تنفيذ LifecycleOwner مخصّص
إنّ المقاطع والنشاطات في الإصدار 26.1.0 من "مكتبة الدعم" والإصدارات الأحدث تُنفِّذ
واجهة LifecycleOwner
.
إذا كانت لديك فئة مخصّصة تريد تحويلها إلى
LifecycleOwner
،
يمكنك استخدام فئة
LifecycleRegistry
، ولكن عليك إعادة توجيه الأحداث إلى هذه الفئة، كما هو موضّح في المثال التالي
للرموز البرمجية:
Kotlin
class MyActivity : Activity(), LifecycleOwner { private lateinit var lifecycleRegistry: LifecycleRegistry override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.markState(Lifecycle.State.CREATED) } public override fun onStart() { super.onStart() lifecycleRegistry.markState(Lifecycle.State.STARTED) } override fun getLifecycle(): Lifecycle { return lifecycleRegistry } }
Java
public class MyActivity extends Activity implements LifecycleOwner { private LifecycleRegistry lifecycleRegistry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); lifecycleRegistry = new LifecycleRegistry(this); lifecycleRegistry.markState(Lifecycle.State.CREATED); } @Override public void onStart() { super.onStart(); lifecycleRegistry.markState(Lifecycle.State.STARTED); } @NonNull @Override public Lifecycle getLifecycle() { return lifecycleRegistry; } }
أفضل الممارسات المتعلّقة بالمكوّنات التي تتضمّن معلومات عن دورة الحياة
- يجب أن تكون عناصر التحكّم في واجهة المستخدم (الأنشطة والأجزاء) بسيطة قدر الإمكان. يجب ألا يحاولوا الحصول على بياناتهم الخاصة، بل عليهم استخدام
ViewModel
بدلاً من ذلك، ثم مراقبة عنصرLiveData
لعرض التغييرات على العروض. - حاوِل كتابة واجهات مستخدم مستندة إلى البيانات تكون فيها مسؤولية وحدة التحكّم في واجهة المستخدم هي
تعديل طرق العرض عند تغيُّر البيانات، أو إرسال إشعارات بإجراءات المستخدم إلى
ViewModel
. - ضع منطق البيانات في فئة
ViewModel
. يجب أن يعملViewModel
بمثابة رابط بين وحدة التحكّم في واجهة المستخدم وبقية أجزاء تطبيقك. مع ذلك، يجب الحذر، لأنّه ليسViewModel
مسؤولاً عن جلب البيانات (على سبيل المثال، من شبكة). بدلاً من ذلك، يجب أن يُطلِبViewModel
المكوّن المناسب لجلب البيانات، ثم يقدّم النتيجة مرة أخرى إلى عنصر التحكّم في واجهة المستخدم. - استخدِم ربط البيانات للحفاظ على واجهة نظيفة بين طرق العرض وعنصر التحكّم في واجهة المستخدم. يتيح لك ذلك جعل طرق العرض أكثر وضوحًا وتقليل رمز التعديل الذي تحتاج إلى كتابته في الأنشطة والمقاطع. إذا كنت تفضّل إجراء ذلك بلغة برمجة Java ، استخدِم مكتبة مثل Butter Knife لتجنُّب استخدام رمز برمجي عادي والحصول على نموذج أفضل.
- إذا كانت واجهة المستخدم معقّدة، ننصحك بإنشاء فئة مقدّم لمعالجة تعديلات واجهة المستخدم. قد تكون هذه مهمة شاقة، ولكن يمكن أن يؤدي ذلك إلى تسهيل اختبار مكونات واجهة المستخدم.
- تجنَّب الإشارة إلى سياق
View
أوActivity
فيViewModel
. إذا استمرتViewModel
بعد انتهاء النشاط (في حال تغيير الإعدادات)، قد يتم تسريب نشاطك ولا يتم التخلص منه بشكل صحيح من خلال أداة جمع المهملات. - استخدِم عمليات التشغيل المتعدّدة المتزامنة في Kotlin لإدارة المهام التي تستغرق وقتًا طويلاً والعمليات الأخرى التي يمكن تنفيذها بشكل غير متزامن.
حالات استخدام المكونات المدرِكة لمراحل النشاط
يمكن أن تسهّل عليك المكوّنات المتوافقة مع دورة الحياة إدارة دورات الحياة في مجموعة متنوعة من الحالات. في ما يلي بعض الأمثلة:
- التبديل بين تحديثات الموقع الجغرافي الدقيقة وغير الدقيقة استخدِم
المكوّنات المتوافقة مع رحلة المستخدِم لتفعيل التحديثات الدقيقة للموقع الجغرافي عندما يكون
تطبيق الموقع الجغرافي مرئيًا والتبديل إلى التحديثات الدقيقة عندما يكون التطبيق
في الخلفية.
LiveData
هو مكوّن يراعي دورة الحياة، ويسمح لتطبيقك بتعديل واجهة المستخدم تلقائيًا عندما يغيّر المستخدم المواقع الجغرافية. - إيقاف وبدء تخزين الفيديو مؤقتًا استخدِم المكوّنات المتوافقة مع دورة الحياة لبدء تخزين الفيديو مؤقتًا في أقرب وقت ممكن، ولكن عليك تأجيل التشغيل إلى أن يتم بدء تشغيل التطبيق بالكامل. يمكنك أيضًا استخدام المكوّنات المتوافقة مع دورة الحياة لإنهاء التخزين المؤقت عند إنهاء تطبيقك.
- بدء الاتصال بالشبكة وإيقافه استخدِم المكوّنات المتوافقة مع دورة الحياة لتفعيل التعديل المباشر (البث) لبيانات الشبكة عندما يكون التطبيق في foreground، وكذلك لإيقاف البث مؤقتًا تلقائيًا عندما ينتقل التطبيق إلى background.
- إيقاف الرسومات المتحرّكة مؤقتًا واستئنافها استخدِم المكوّنات المتوافقة مع دورة الحياة للتحكّم في إيقاف الرسومات المتحرّكة مؤقتًا عندما يكون التطبيق في الخلفية واستئناف عرضها بعد أن يصبح التطبيق في المقدّمة.
التعامل مع أحداث الإيقاف
عندما ينتمي Lifecycle
إلى AppCompatActivity
أو Fragment
، تتغيّر حالة Lifecycle
إلى
CREATED
ويتم إرسال حدث ON_STOP
عند استدعاء onSaveInstanceState()
AppCompatActivity
أو
Fragment
.
عند حفظ حالة Fragment
أو AppCompatActivity
من خلال
onSaveInstanceState()
، يُعتبر واجهة المستخدم
غير قابلة للتغيير إلى أن تتم دعوة
ON_START
. من المرجّح أن تؤدي محاولة تعديل واجهة المستخدم بعد حفظ الحالة إلى
عدم اتساق في حالة التنقّل في تطبيقك، ولهذا السبب يُرسِل FragmentManager
استثناءً إذا كان التطبيق يُجري
FragmentTransaction
بعد حفظ الحالة. يُرجى الاطّلاع على
commit()
لمعرفة التفاصيل.
يمنع LiveData
حدوث هذا الخطأ بشكل تلقائي من خلال الامتناع عن
استدعاء المراقب إذا لم يكن Lifecycle
المرتبط به هو
STARTED
على الأقل.
في الكواليس، يتمّ استدعاء
isAtLeast()
قبل اتخاذ قرار استدعاء المراقب.
للأسف، يتمّ استدعاء طريقة onStop()
في AppCompatActivity
بعد
onSaveInstanceState()
،
ما يترك فجوة لا يُسمح فيها بتغييرات حالة واجهة المستخدِم، ولكنّه
Lifecycle
لم يتمّ نقله إلى الحالة
CREATED
بعد.
لتجنّب حدوث هذه المشكلة، تضع فئة Lifecycle
في الإصدار beta2
والإصدارات الأقدم علامة على الحالة على أنّها
CREATED
بدون إرسال الحدث حتى يستدعي النظام onStop()
.
يواجه هذا الحلّ مشكلتَين رئيسيتين:
- في المستوى 23 من واجهة برمجة التطبيقات والإصدارات الأقدم، يحفظ نظام Android حالة أحد
الأنشطة حتى إذا كان جزئيًا مغطّىً بنشاط آخر. بعبارة أخرى، يتصل نظام Android بـ
onSaveInstanceState()
ولكنه لا يتصل بـonStop()
بالضرورة. يؤدي ذلك إلى إنشاء فاصل زمني قد يكون طويلًا يعتقد فيه المراقب أنّ دورة الحياة نشطة حتى لو تعذّر تعديل حالة واجهة المستخدم. - إنّ أي فئة تريد عرض سلوك مشابه لفئة
LiveData
يجب أن تطبّق الحلّ البديل المقدَّم في الإصدارbeta 2
من مكتبةLifecycle
والإصدارات الأقدم.
مصادر إضافية
لمزيد من المعلومات عن التعامل مع مراحل النشاط باستخدام المكوّنات المدركِة لمراحل النشاط، يُرجى الرجوع إلى المراجع الإضافية التالية.
نماذج
- Sunflower، تطبيق تجريبي يعرض أفضل الممارسات باستخدام Architecture Components
الدروس التطبيقية حول الترميز
المدوّنات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- نظرة عامة على LiveData
- استخدام مهام Kotlin المتعدّدة المهام مع المكونات المدرِكة لدورة الحياة
- وحدة "الحالة المحفوظة" لـ ViewModel