معالجة مراحل النشاط باستخدام مكوّنات Lifecycle-Aware جزء من Android Jetpack.
تنفِّذ المكونات الواعية لدورة الحياة إجراءات استجابة لأي تغيير في حالة دورة حياة مكوّن آخر، مثل الأنشطة والأجزاء. وتساعدك هذه المكوّنات على إنشاء تعليمات برمجية منظمة بشكل أفضل وأخف وزنًا في أغلب الأحيان، ويسهل صيانتها.
ومن الأنماط الشائعة تنفيذ إجراءات المكوّنات التابعة في طرق دورة حياة الأنشطة والأجزاء. ومع ذلك، يؤدي هذا النمط إلى تنظيم ضعيف للتعليمة البرمجية وانتشار الأخطاء. باستخدام المكونات الواعية لدورة الحياة، يمكنك نقل رمز المكونات التابعة من طرق دورة الحياة إلى المكونات نفسها.
توفِّر حزمة 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
هي واجهة
من واجهة واحدة تشير إلى أنّ الصف يتضمّن
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، نقترح استخدام مكونات واعٍ لمراحل نشاط تجاري. يمكن لعملاء المكتبة دمج هذه المكوّنات بسهولة بدون إدارة يدوية لمراحل النشاط من جانب العميل.
تنفيذ مالك دورة حياة مخصص
تنفّذ الأجزاء والأنشطة في الإصدار 26.1.0 من Support Library والإصدارات الأحدث واجهة 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
" (في حال حدوث تغييرات في الإعدادات)، تسريب النشاط ولن يتم التخلص منه بشكل صحيح من قِبل جامع النفايات. - استخدام كوروتيني كوتلين لإدارة المهام طويلة المدى والعمليات الأخرى التي يمكن أن تعمل بشكل غير متزامن.
حالات الاستخدام للمكوّنات الواعية لدورة الحياة
يمكن للمكونات الواعية لدورة الحياة أن تسهل عليك إدارة دورات الحياة في مجموعة متنوعة من الحالات. في ما يلي بعض الأمثلة:
- التبديل بين تحديثات الموقع التقريبي والدقة الدقيقة. يمكنك استخدام المكوّنات التي تراعي مراحل نشاط المستخدمين لتفعيل تحديثات الموقع الجغرافي الدقيق عندما يكون تطبيق الموقع الجغرافي مرئيًا، والتبديل إلى تحديثات عامة عندما يكون التطبيق
في الخلفية.
LiveData
، وهو مكوّن مدرك لمراحل نشاطه، يسمح لتطبيقك بتعديل واجهة المستخدم تلقائيًا عندما يغيِّر المواقع الجغرافية. - جارٍ إيقاف التخزين المؤقت للفيديو وبدء تشغيله استخدِم المكونات الواعية لمراحل الحياة لبدء تخزين الفيديو مؤقتًا في أقرب وقت ممكن، ولكن مع تأجيل التشغيل إلى أن يبدأ تشغيل التطبيق بالكامل. يمكنك أيضًا استخدام المكونات الواعية لمراحل الحياة لإنهاء التخزين المؤقت عند تدمير تطبيقك.
- بدء الاتصال بالشبكة وإيقافه استخدم المكونات الواعية لدورة الحياة لتمكين التحديث المباشر (البث) لبيانات الشبكة عندما يكون التطبيق في المقدمة وكذلك الإيقاف المؤقت تلقائيًا عندما ينتقل التطبيق إلى الخلفية.
- إيقاف العناصر القابلة للرسم المتحركة مؤقتًا واستئنافها. استخدم المكونات الواعية لدورة الحياة للتعامل مع الإيقاف المؤقت للعناصر المتحركة القابلة للرسم عندما يكون التطبيق في الخلفية واستئناف العناصر القابلة للرسم بعد تشغيل التطبيق في المقدمة.
التعامل مع أحداث التوقّف عند التوقّف
عندما ينتمي حدث Lifecycle
إلى AppCompatActivity
أو Fragment
، تتغير حالة Lifecycle
إلى
CREATED
ويتم إرسال
الحدث ON_STOP
عند استدعاء AppCompatActivity
أو
Fragment
onSaveInstanceState()
.
عند حفظ حالة 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
والإصدارات الأقدم.
مراجع إضافية
لمعرفة المزيد حول التعامل مع دورات الحياة التي تتضمن مكونات تدرك دورة الحياة، راجع الموارد الإضافية التالية.
عيّنات
- النموذج الأساسي للمكوّنات الهندسية لنظام التشغيل Android
- Sunflower، تطبيق تجريبي يعرض أفضل الممارسات باستخدام مكونات البنية
الدروس التطبيقية حول الترميز
المدوّنات
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript.
- نظرة عامة على LiveData
- استخدام الكوروتينات في لغة Kotlin مع مكوّنات تراعي مراحل النشاط
- وحدة الحالة المحفوظة لـ ViewModel