التعامل مع مراحل النشاط باستخدام المكوّنات المدرِكة لمراحل النشاط   جزء من 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.
مخطّط بياني لحالات دورة الحياة
الشكل 1. الحالات والأحداث التي تشكّل مراحل activity

يمكنك اعتبار الحالات على أنّها عقد في رسم بياني والأحداث على أنّها الحواف بين هذه العقد.

يمكن لفئة مراقبة حالة دورة حياة المكوّن من خلال تنفيذ 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

الدروس التطبيقية حول الترميز

المدوّنات