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

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

يمكن للصف مراقبة حالة مراحل نشاط المكوِّن من خلال تنفيذ 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 والإصدارات الأقدم.

مراجع إضافية

لمعرفة المزيد حول التعامل مع دورات الحياة التي تتضمن مكونات تدرك دورة الحياة، راجع الموارد الإضافية التالية.

عيّنات

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

المدوّنات