مراحل النشاط (مرّات المشاهدة)

المفاهيم والتنفيذ في Jetpack Compose

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

يمكنك ضمن طرق الاستدعاء في إحدى مراحل النشاط تحديد طريقة عمل نشاطك عندما يخرج المستخدم منه ثم يعود إليه. على سبيل المثال، إذا كنت بصدد إنشاء مشغّل فيديو للبث المباشر، يمكنك إيقاف الفيديو مؤقتًا وإنهاء اتصال الشبكة عندما ينتقل المستخدم إلى تطبيق آخر. وعندما يعود المستخدم، يمكنك إعادة الاتصال بالشبكة والسماح له باستئناف الفيديو من الموضع نفسه.

تتيح لك كل دالة ردّ تنفيذ عمل محدّد مناسب لتغيير معيّن في الحالة. يؤدي تنفيذ المهام المناسبة في الوقت المناسب والتعامل مع عمليات الانتقال بشكل صحيح إلى جعل تطبيقك أكثر فعالية وأداءً. على سبيل المثال، يمكن أن يساعد التنفيذ الجيد لعمليات معاودة الاتصال بدورة الحياة تطبيقك في تجنُّب ما يلي:

  • تعطُّل التطبيق إذا تلقّى المستخدم مكالمة هاتفية أو بدّل إلى تطبيق آخر أثناء استخدام تطبيقك
  • استهلاك موارد النظام القيّمة عندما لا يكون المستخدم نشطًا
  • فقدان مستوى تقدّم المستخدم إذا غادر تطبيقك وعاد إليه في وقت لاحق
  • تعطُّل التطبيق أو فقدان تقدّم المستخدم عند تدوير الشاشة بين الوضعَين الأفقي والرأسي

يوضّح هذا المستند مراحل النشاط بالتفصيل. يبدأ المستند بوصف نموذج دورة الحياة. بعد ذلك، يشرح كلًّا من دوال الرجوع: ما يحدث داخليًا أثناء تنفيذها وما عليك تنفيذه خلالها.

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

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

مفاهيم مراحل النشاط

للتنقّل بين مراحل دورة حياة النشاط، يوفّر الصف Activity مجموعة أساسية من ستّ عمليات ردّ اتصال: onCreate وonStart وonResume وonPause وonStop وonDestroy. يستدعي النظام كلّ دالة من دوال الاستدعاء هذه عندما ينتقل النشاط إلى حالة جديدة.

يعرض الشكل 1 تمثيلاً مرئيًا لهذا النموذج.

الشكل 1. صورة توضيحية مبسَّطة لمراحل النشاط

عندما يبدأ المستخدم في مغادرة النشاط، يستدعي النظام طرقًا لتفكيك النشاط. في بعض الحالات، يتم إيقاف النشاط جزئيًا فقط ويظل موجودًا في الذاكرة، مثلما يحدث عندما ينتقل المستخدم إلى تطبيق آخر. وفي هذه الحالات، يمكن أن يعود النشاط إلى المقدّمة.

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

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

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

عمليات معاودة الاتصال في إحدى مراحل النشاط

يقدّم هذا القسم معلومات مفاهيمية ومعلومات حول التنفيذ بشأن طرق معاودة الاتصال المستخدَمة خلال دورة حياة النشاط.

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

onCreate

يجب تنفيذ دالة الاستدعاء هذه التي يتم تشغيلها عندما ينشئ النظام النشاط لأول مرة. عند إنشاء نشاط، ينتقل إلى الحالة تم الإنشاء. في طريقة onCreate، نفِّذ منطق بدء تشغيل التطبيق الأساسي الذي يحدث مرة واحدة فقط طوال مدة نشاط التطبيق.

على سبيل المثال، قد يؤدي تنفيذ onCreate إلى ربط البيانات بالقوائم، وربط النشاط ViewModel، وإنشاء بعض المتغيرات ذات النطاق على مستوى الفئة. تتلقّى هذه الطريقة المَعلمة savedInstanceState، وهي عبارة عن كائن Bundle يحتوي على حالة النشاط التي تم حفظها سابقًا. إذا لم يسبق أن كان النشاط موجودًا، ستكون قيمة العنصر Bundle فارغة.

إذا كان لديك مكوّن متوافق مع مراحل النشاط ومرتبط بمراحل نشاط تطبيقك، سيتلقّى الحدث ON_CREATE. يتم استدعاء الطريقة التي تمّت إضافة التعليق التوضيحي @OnLifecycleEvent إليها لكي يتمكّن المكوّن المتوافق مع مراحل النشاط من تنفيذ أي رمز إعداد يحتاج إليه للحالة التي تم إنشاؤها.

يوضّح المثال التالي لطريقة onCreate عملية الإعداد الأساسية للنشاط، مثل تعريف واجهة المستخدم (المحدّدة في ملف تنسيق XML)، وتحديد متغيرات العناصر، وضبط بعض عناصر واجهة المستخدم. في هذا المثال، يمرِّر ملف تنسيق XML رقم تعريف المورد R.layout.main_activity الخاص بالملف إلى setContentView.

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;
// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

كبديل لتعريف ملف XML وتمريره إلى setContentView، يمكنك إنشاء عناصر View جديدة في رمز النشاط وإنشاء هيكلية طرق العرض عن طريق إدراج عناصر View جديدة في ViewGroup. بعد ذلك، يمكنك استخدام هذا التصميم من خلال تمرير ViewGroup الجذر إلى setContentView. لمزيد من المعلومات حول إنشاء واجهة مستخدم، يُرجى الاطّلاع على مستندات واجهة المستخدم.

لا يبقى نشاطك في الحالة "تم الإنشاء". بعد انتهاء تنفيذ طريقة onCreate، يدخل النشاط في حالة بدء ويستدعي النظام الطريقتَين onStart وonResume على التوالي.

onStart

عندما ينتقل النشاط إلى الحالة Started، يستدعي النظام onStart. يؤدي هذا الاستدعاء إلى إظهار النشاط للمستخدم أثناء استعداد التطبيق لإدخال النشاط إلى المقدّمة وجعله تفاعليًا. على سبيل المثال، يتم في هذه الطريقة تهيئة الرمز الذي يحافظ على واجهة المستخدم.

عندما ينتقل النشاط إلى الحالة Started، يتلقّى أي مكوّن متوافق مع مراحل النشاط ومرتبط بمراحل نشاط النشاط الحدث ON_START.

تكتمل طريقة onStart بسرعة، وكما هو الحال مع الحالة Created، لا يظل النشاط في الحالة Started. بعد انتهاء عملية الاستدعاء هذه، ينتقل النشاط إلى الحالة مستأنف ويستدعي النظام الطريقة onResume.

onResume

عندما ينتقل النشاط إلى الحالة "مستأنف"، يظهر في المقدّمة، ويستدعي النظام دالة رد الاتصال onResume. هذه هي الحالة التي يتفاعل فيها التطبيق مع المستخدم. يبقى التطبيق في هذه الحالة إلى أن يحدث شيء يؤدي إلى إزالة التركيز من التطبيق، مثل تلقّي الجهاز مكالمة هاتفية أو انتقال المستخدم إلى نشاط آخر أو إيقاف شاشة الجهاز.

عندما ينتقل النشاط إلى الحالة "تمت استئناف العملية"، يتلقّى أي مكوّن متوافق مع مراحل النشاط ومرتبط بدورة حياة النشاط الحدث ON_RESUME. وهنا يمكن لمكوّنات دورة الحياة تفعيل أي وظيفة يجب تشغيلها أثناء ظهور المكوّن في المقدّمة، مثل بدء معاينة الكاميرا.

عند حدوث حدث مقاطِع، ينتقل النشاط إلى الحالة متوقّف مؤقتًا ويستدعي النظام دالة معاودة الاتصال onPause.

إذا عاد النشاط إلى الحالة "مستأنف" من الحالة "متوقف مؤقتًا"، سيستدعي النظام الطريقة onResume مرة أخرى. لهذا السبب، عليك تنفيذ onResume من أجل تهيئة المكوّنات التي يتم تحريرها أثناء onPause وتنفيذ أي عمليات تهيئة أخرى يجب أن تحدث في كل مرة ينتقل فيها النشاط إلى الحالة "مستأنف".

في ما يلي مثال على مكوّن متوافق مع مراحل النشاط يصل إلى الكاميرا عندما يتلقّى المكوّن الحدث ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

يعمل الرمز السابق على تهيئة الكاميرا بمجرد أن يتلقّى LifecycleObserver الحدث ON_RESUME. في وضع النوافذ المتعددة، قد يظهر نشاطك بالكامل حتى عندما يكون في الحالة "متوقف مؤقتًا". على سبيل المثال، عندما يكون التطبيق في وضع النوافذ المتعددة وينقر المستخدم على النافذة التي لا تحتوي على نشاطك، ينتقل نشاطك إلى الحالة "متوقف مؤقتًا".

إذا أردت أن تكون الكاميرا نشطة فقط عندما يكون التطبيق في حالة "استئناف" (مرئيًا ونشطًا في المقدّمة)، عليك تهيئة الكاميرا بعد حدث ON_RESUME الموضّح سابقًا. إذا أردت إبقاء الكاميرا نشطة أثناء إيقاف النشاط مؤقتًا مع إبقائه مرئيًا، مثلاً في وضع النوافذ المتعددة، عليك تهيئة الكاميرا بعد حدث ON_START.

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

لهذا السبب، يجب التفكير مليًا في الوقت الأنسب للتحكّم في موارد النظام المشترَكة خلال مراحل النشاط في سياق وضع النوافذ المتعددة. لمزيد من المعلومات حول إتاحة وضع النوافذ المتعددة، يُرجى الاطّلاع على إتاحة وضع النوافذ المتعددة.

بغض النظر عن حدث الإنشاء الذي تختار إجراء عملية تهيئة فيه، احرص على استخدام حدث يتم في مراحل النشاط المناسب لإتاحة المورد. إذا بدأت في تنفيذ إجراء ما بعد حدث ON_START، عليك إيقافه أو إنهاءه بعد حدث ON_STOP. إذا أجريت عملية التهيئة بعد حدث ON_RESUME، عليك إجراء عملية الإفراج بعد حدث ON_PAUSE.

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

onPause

يستدعي النظام هذه الطريقة كأول إشارة إلى أنّ المستخدم سيغادر نشاطك، مع أنّ ذلك لا يعني دائمًا أنّه سيتم إيقاف النشاط. تشير هذه الحالة إلى أنّ النشاط لم يعُد في المقدّمة، ولكنّه يظل مرئيًا إذا كان المستخدم في وضع النوافذ المتعددة. هناك عدة أسباب محتملة لدخول نشاط إلى هذه الحالة:

  • يؤدي حدث يعطّل تنفيذ التطبيق، كما هو موضّح في القسم حول الدالة onResume، إلى إيقاف النشاط الحالي مؤقتًا. وهذه هي الحالة الأكثر شيوعًا.
  • في وضع النوافذ المتعددة، يركّز النظام على تطبيق واحد فقط في كل مرة، ويوقف جميع التطبيقات الأخرى مؤقتًا.
  • عند فتح نشاط جديد شبه شفاف، مثل مربّع حوار، يتم إيقاف النشاط الذي يغطيه مؤقتًا. ويظل النشاط متوقفًا مؤقتًا طالما أنّه مرئي جزئيًا ولكن ليس في المقدّمة.

عندما ينتقل نشاط إلى الحالة "متوقّف مؤقتًا"، يتلقّى أي مكوّن متوافق مع مراحل النشاط ومرتبط بدورة حياة النشاط الحدث ON_PAUSE. وهنا يمكن لمكوّنات دورة الحياة إيقاف أي وظيفة لا يلزم تشغيلها عندما لا يكون المكوّن في المقدّمة، مثل إيقاف معاينة الكاميرا.

استخدِم طريقة onPause لإيقاف العمليات التي لا يمكن مواصلتها أو التي قد تتم مواصلتها بشكل معتدل مؤقتًا أثناء حالة Activity "متوقّف مؤقتًا"، والتي تتوقّع استئنافها قريبًا، أو لتعديلها.

يمكنك أيضًا استخدام طريقة onPause لإتاحة موارد النظام أو مقابض أجهزة الاستشعار (مثل نظام تحديد المواقع العالمي (GPS)) أو أي موارد تؤثر في عمر البطارية عندما يكون نشاطك في حالة "متوقف مؤقتًا" ولا يحتاج إليها المستخدم.

ومع ذلك، كما هو موضّح في القسم حول onResume، قد يظل النشاط المتوقّف مؤقتًا مرئيًا بالكامل إذا كان التطبيق في وضع النوافذ المتعددة. ننصحك باستخدام onStop بدلاً من onPause لإتاحة أو تعديل الموارد والعمليات ذات الصلة بواجهة المستخدم بشكل كامل بهدف توفير دعم أفضل لوضع النوافذ المتعددة.

في ما يلي مثال على LifecycleObserver يتفاعل مع الحدث ON_PAUSE، وهو نظير لمثال الحدث ON_RESUME السابق، حيث يتم تحرير الكاميرا التي يتم تهيئتها بعد تلقّي الحدث ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

يضع هذا المثال رمز تحرير الكاميرا بعد أن يتلقّى LifecycleObserver الحدث ON_PAUSE.

يكون تنفيذ onPause موجزًا جدًا ولا يوفّر بالضرورة وقتًا كافيًا لتنفيذ عمليات الحفظ. لهذا السبب، لا تستخدِم onPause لحفظ بيانات التطبيق أو بيانات المستخدم أو إجراء طلبات عبر الشبكة أو تنفيذ معاملات قاعدة البيانات. وقد لا يكتمل هذا العمل قبل اكتمال الطريقة.

بدلاً من ذلك، نفِّذ عمليات إيقاف التشغيل التي تتطلّب تحميلًا كبيرًا خلال onStop. لمزيد من المعلومات حول العمليات المناسبة التي يمكن تنفيذها أثناء onStop، يُرجى الاطّلاع على القسم التالي. لمزيد من المعلومات حول حفظ البيانات، راجِع القسم حول حفظ الحالة واستعادتها.

لا يعني إكمال طريقة onPause أنّ النشاط سيخرج من حالة Paused. بل يظل النشاط في هذه الحالة إلى أن يتم استئنافه أو يصبح غير مرئي تمامًا للمستخدم. إذا تم استئناف النشاط، سيستدعي النظام مرة أخرى الدالة onResume.

إذا انتقل النشاط من الحالة "متوقف مؤقتًا" إلى الحالة "مستأنف"، سيحتفظ النظام بنسخة Activity في الذاكرة، وسيستدعي هذه النسخة عندما يستدعي النظام onResume. في هذه الحالة، لن تحتاج إلى إعادة تهيئة المكوّنات التي تم إنشاؤها خلال أي من طرق معاودة الاتصال التي تؤدي إلى حالة Resumed. إذا أصبح النشاط غير مرئي تمامًا، سيطلب النظام onStop.

onStop

عندما لا يعود نشاطك مرئيًا للمستخدم، ينتقل إلى حالة متوقف، ويستدعي النظام عملية رد الاتصال onStop. يمكن أن يحدث ذلك عندما يغطّي نشاط تم إطلاقه حديثًا الشاشة بأكملها. يستدعي النظام أيضًا onStop عندما ينتهي النشاط من التشغيل ويوشك على الإنهاء.

عندما ينتقل النشاط إلى الحالة Stopped، يتلقّى أي مكوّن متوافق مع مراحل النشاط ومرتبط بدورة حياة النشاط الحدث ON_STOP. وهنا يمكن لمكوّنات دورة الحياة إيقاف أي وظيفة لا يلزم تشغيلها عندما لا يكون المكوّن مرئيًا على الشاشة.

في طريقة onStop، يمكنك تحرير أو تعديل الموارد غير اللازمة عندما لا يكون التطبيق مرئيًا للمستخدم. على سبيل المثال، قد يوقف تطبيقك مؤقتًا الرسوم المتحركة أو ينتقل من تحديثات الموقع الجغرافي الدقيقة إلى تحديثات الموقع الجغرافي التقريبية. يؤدي استخدام onStop بدلاً من onPause إلى استمرار العمل المرتبط بواجهة المستخدم، حتى عندما يشاهد المستخدم نشاطك في وضع النوافذ المتعددة.

استخدِم أيضًا onStop لتنفيذ عمليات إيقاف التشغيل التي تتطلّب قدرًا كبيرًا نسبيًا من وحدة المعالجة المركزية. على سبيل المثال، إذا لم تتمكّن من العثور على وقت أفضل لحفظ المعلومات في قاعدة بيانات، يمكنك إجراء ذلك خلال onStop. يوضّح المثال التالي عملية تنفيذ onStop التي تحفظ محتوى مسودة ملاحظة في مساحة تخزين ثابتة:

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

يستخدم نموذج الرمز البرمجي السابق SQLite مباشرةً. ومع ذلك، ننصح باستخدام Room، وهي مكتبة ثبات توفّر طبقة تجريد فوق SQLite. لمعرفة المزيد حول مزايا استخدام Room وكيفية تنفيذها في تطبيقك، راجِع دليل مكتبة Room لاستدامة البيانات.

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

ليس عليك إعادة تهيئة المكوّنات التي تم إنشاؤها خلال أي من طرق معاودة الاتصال التي تؤدي إلى الحالة "تمت استعادة النشاط". يتتبّع النظام أيضًا الحالة الحالية لكل عنصر View في التصميم، لذا إذا أدخل المستخدم نصًا في أداة EditText، سيتم الاحتفاظ بهذا المحتوى بدون الحاجة إلى حفظه واستعادته.

من الحالة "متوقّف"، يعود النشاط إما للتفاعل مع المستخدم أو ينتهي تشغيل النشاط ويختفي. إذا عاد النشاط، يستدعي النظام onRestart. إذا انتهى تنفيذ Activity، يستدعي النظام onDestroy.

onDestroy

يتم استدعاء onDestroy قبل إيقاف النشاط. يستدعي النظام وظيفة معاودة الاتصال هذه لأحد السببَين التاليَين:

  1. ينتهي النشاط إما لأنّ المستخدم أغلق النشاط بالكامل أو لأنّه تم استدعاء finish في النشاط.
  2. يؤدي النظام إلى إتلاف النشاط مؤقتًا بسبب تغيير في الإعدادات، مثل تدوير الجهاز أو الانتقال إلى وضع النوافذ المتعددة.

عندما ينتقل النشاط إلى الحالة "تم إتلافه"، يتلقّى أي مكوّن متوافق مع مراحل النشاط مرتبط بدورة حياة النشاط الحدث ON_DESTROY. وهذا هو المكان الذي يمكن فيه لمكوّنات دورة الحياة تنظيف أي بيانات تحتاج إليها قبل إتلاف Activity.

بدلاً من وضع منطق في Activity لتحديد سبب إتلافه، استخدِم عنصر ViewModel لتضمين بيانات العرض ذات الصلة في Activity. إذا تمت إعادة إنشاء Activity بسبب تغيير في الإعدادات، ليس على ViewModel اتّخاذ أي إجراء، لأنّه يتم الاحتفاظ به ومنحه إلى مثيل Activity التالي.

إذا لم تتم إعادة إنشاء Activity، سيتم استدعاء الطريقة onCleared في ViewModel، حيث يمكنها تنظيف أي بيانات تحتاج إليها قبل إيقافها. يمكنك التمييز بين هاتين الحالتين باستخدام الطريقة isFinishing.

إذا كان النشاط سينتهي، فإنّ onDestroy هو آخر ردّ اتصال لدورة الحياة يتلقّاه النشاط. إذا تم استدعاء onDestroy نتيجةً لتغيير في الإعدادات، ينشئ النظام على الفور مثيلاً جديدًا للنشاط ثم يستدعي onCreate في هذا المثيل الجديد بالإعدادات الجديدة.

يؤدي استدعاء onDestroy إلى تحرير جميع الموارد التي لم يتم تحريرها بواسطة عمليات الاستدعاء السابقة، مثل onStop.

حفظ حالة واجهة المستخدم المؤقتة واستعادتها

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

وبالمثل، يتوقّع المستخدم أن تظل حالة واجهة المستخدم كما هي إذا انتقل مؤقتًا من تطبيقك إلى تطبيق آخر ثم عاد إلى تطبيقك لاحقًا. ومع ذلك، يمكن للنظام إيقاف عملية تطبيقك أثناء عدم نشاط المستخدم وإيقاف نشاطك.

عندما تؤدي قيود النظام إلى إيقاف النشاط، احتفِظ بحالة واجهة المستخدم المؤقتة للمستخدم باستخدام مجموعة من ViewModel وonSaveInstanceState و/أو التخزين المحلي. لمزيد من المعلومات حول توقعات المستخدمين مقارنةً بسلوك النظام وكيفية الحفاظ على بيانات حالة واجهة المستخدم المعقّدة على أفضل نحو عند إيقاف النشاط وإيقاف العملية نهائيًا من قِبل النظام، اطّلِع على حفظ حالات واجهة المستخدم.

يوضّح هذا القسم حالة المثيل وكيفية تنفيذ الطريقة onSaveInstance، وهي دالة ردّ الاتصال في النشاط نفسه. إذا كانت بيانات واجهة المستخدم خفيفة، يمكنك استخدام onSaveInstance وحده للحفاظ على حالة واجهة المستخدم في حال حدوث تغييرات في الإعدادات أو إيقاف العملية نهائيًا من قِبل النظام. ولكن بما أنّ onSaveInstance يتكبّد تكاليف تسلسل/إلغاء تسلسل، فإنّه في معظم الحالات، يتم استخدام كل من ViewModel وonSaveInstance، كما هو موضّح في حفظ حالات واجهة المستخدم.

حالة الجهاز الافتراضي

هناك بعض السيناريوهات التي يتم فيها إيقاف نشاطك بسبب السلوك العادي للتطبيق، مثل عندما ينقر المستخدم على الزر "رجوع" أو عندما يشير نشاطك إلى إيقافه من خلال استدعاء الطريقة finish.

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

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

تُعرف البيانات المحفوظة التي يستخدمها النظام لاستعادة الحالة السابقة باسم حالة المثيل. وهي مجموعة من أزواج المفاتيح والقيم المخزّنة في كائن Bundle. يستخدم النظام تلقائيًا حالة مثيل Bundle لحفظ معلومات حول كل عنصر View في تصميم نشاطك، مثل قيمة النص التي تم إدخالها في أداة EditText.

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

لا يكون العنصر Bundle مناسبًا للاحتفاظ بكمية كبيرة من البيانات، لأنّه يتطلّب نشرًا على نحو متسلسِل على سلسلة التعليمات الرئيسية ويستهلك ذاكرة عملية النظام. للحفاظ على كمية أكبر من البيانات، اتّبِع نهجًا مشتركًا للحفاظ على البيانات، وذلك باستخدام مساحة التخزين المحلية الثابتة والطريقتين onSaveInstanceState وViewModel، كما هو موضّح في حفظ حالات واجهة المستخدم.

حفظ حالة واجهة المستخدم البسيطة والخفيفة باستخدام onSaveInstanceState

عندما يبدأ نشاطك في التوقّف، يستدعي النظام الطريقة onSaveInstanceState ليتمكّن نشاطك من حفظ معلومات الحالة في حزمة حالة مثيل. يحفظ التنفيذ التلقائي لهذه الطريقة معلومات مؤقتة حول حالة هيكلية طرق العرض للنشاط، مثل النص في تطبيق مصغّر EditText أو موضع التمرير لتطبيق مصغّر ListView.

لحفظ معلومات إضافية حول حالة مثيل نشاطك، عليك إلغاء onSaveInstanceState وإضافة أزواج المفتاح/القيمة إلى العنصر Bundle الذي يتم حفظه في حال إيقاف نشاطك بشكل غير متوقّع. عند إلغاء onSaveInstanceState، عليك استدعاء تنفيذ الفئة الأساسية إذا كنت تريد أن يحفظ التنفيذ التلقائي حالة هيكلية طرق العرض. يظهر ذلك في المثال التالي:

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

لحفظ البيانات الثابتة، مثل الإعدادات المفضَّلة للمستخدم أو بيانات قاعدة البيانات، عليك الاستفادة من الفرص المناسبة عندما يكون نشاطك في المقدّمة. إذا لم تتوفر فرصة من هذا النوع، احفظ البيانات الثابتة أثناء تنفيذ طريقة onStop.

استعادة حالة واجهة مستخدم النشاط باستخدام حالة المثيل المحفوظة

عند إعادة إنشاء نشاطك بعد أن تم إتلافه سابقًا، يمكنك استرداد حالة مثيلك المحفوظة من Bundle الذي يمرّره النظام إلى نشاطك. تتلقّى كلّ من طريقتَي رد الاتصال onCreate وonRestoreInstanceState الرمز Bundle نفسه الذي يحتوي على معلومات حالة المثيل.

بما أنّ الطريقة onCreate يتم استدعاؤها سواء كان النظام ينشئ مثيلاً جديدًا لنشاطك أو يعيد إنشاء مثيل سابق، عليك التحقّق مما إذا كانت الحالة Bundle فارغة قبل محاولة قراءتها. إذا كانت القيمة فارغة، يعني ذلك أنّ النظام بصدد إنشاء مثيل جديد للنشاط بدلاً من استعادة مثيل سابق تم إتلافه.

يوضّح مقتطف الرمز التالي كيف يمكنك استعادة بعض بيانات الحالة في onCreate:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

بدلاً من استعادة الحالة أثناء onCreate، يمكنك اختيار تنفيذ onRestoreInstanceState، وهو الإجراء الذي يستدعيه النظام بعد تنفيذ طريقة onStart. لا يستدعي النظام onRestoreInstanceState إلا إذا كانت هناك حالة محفوظة يجب استعادتها، لذا لا تحتاج إلى التحقّق مما إذا كان Bundle فارغًا.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

من المحتمل أن يدخل التطبيق نشاطًا ويخرج منه، ربما عدة مرات، خلال فترة تشغيل التطبيق، مثلاً عندما ينقر المستخدم على زر "رجوع" في الجهاز أو عندما يبدأ النشاط نشاطًا آخر.

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

بدء نشاط من نشاط آخر

يحتاج النشاط غالبًا إلى بدء نشاط آخر في مرحلة ما. تظهر هذه الحاجة، على سبيل المثال، عندما يحتاج تطبيق إلى الانتقال من الشاشة الحالية إلى شاشة جديدة.

استنادًا إلى ما إذا كان نشاطك يريد الحصول على نتيجة من النشاط الجديد الذي سيتم بدءه، يمكنك بدء النشاط الجديد باستخدام إما طريقة startActivity أو طريقة startActivityForResult. في كلتا الحالتين، عليك تمرير عنصر Intent.

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

startActivity

إذا كان النشاط الذي تم بدؤه حديثًا لا يحتاج إلى عرض نتيجة، يمكن للنشاط الحالي بدؤه من خلال استدعاء الطريقة startActivity.

عند العمل داخل تطبيقك، غالبًا ما تحتاج إلى تشغيل نشاط معروف. على سبيل المثال، يوضّح مقتطف الرمز التالي كيفية تشغيل نشاط باسم SignInActivity.

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

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

هنا تكمن أهمية الأهداف. يمكنك إنشاء هدف يصف الإجراء الذي تريد تنفيذه، وسيشغّل النظام النشاط المناسب من تطبيق آخر. إذا كانت هناك أنشطة متعدّدة يمكنها معالجة الغرض، يمكن للمستخدم اختيار النشاط الذي يريد استخدامه. على سبيل المثال، إذا أردت السماح للمستخدم بإرسال رسالة إلكترونية، يمكنك إنشاء النية التالية:

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

EXTRA_EMAIL الإضافي الذي تمت إضافته إلى intent هو مصفوفة السلاسل النصية من عناوين البريد الإلكتروني التي سيتم إرسال الرسالة الإلكترونية إليها. عندما يستجيب تطبيق بريد إلكتروني لهذا الغرض، يقرأ مصفوفة السلسلة المتوفّرة في البيانات الإضافية ويضع العناوين في الحقل "إلى" في نموذج إنشاء الرسالة الإلكترونية. في هذه الحالة، يبدأ نشاط تطبيق البريد الإلكتروني، وعندما ينتهي المستخدم، يتم استئناف نشاطك.

startActivityForResult

في بعض الأحيان، تريد الحصول على نتيجة من نشاط ما عند انتهائه. على سبيل المثال، يمكنك بدء نشاط يتيح للمستخدم اختيار شخص من قائمة جهات الاتصال. وعندما تنتهي، تعرض الشخص الذي تم اختياره. لإجراء ذلك، عليك استدعاء طريقة startActivityForResult(Intent, int)، حيث تحدّد مَعلمة العدد الصحيح المكالمة.

يهدف هذا المعرّف إلى التمييز بين طلبات متعدّدة إلى startActivityForResult(Intent, int) من النشاط نفسه. وهو ليس معرّفًا عامًا ولا يمكن أن يتعارض مع تطبيقات أو أنشطة أخرى. ستصلك النتيجة من خلال طريقة onActivityResult(int, int, Intent).

عند الخروج من نشاط فرعي، يمكنه استدعاء setResult(int) لعرض البيانات في النشاط الرئيسي. يجب أن يوفّر النشاط الفرعي رمز نتيجة، ويمكن أن يكون هذا الرمز هو النتائج العادية RESULT_CANCELED, RESULT_OK أو أي قيم مخصّصة تبدأ من RESULT_FIRST_USER.

بالإضافة إلى ذلك، يمكن أن تعرض أنشطة الأطفال اختياريًا عنصر Intent يحتوي على أي بيانات إضافية تريدها. يستخدم النشاط الرئيسي الطريقة onActivityResult(int, int, Intent)، بالإضافة إلى المعرّف العددي الذي قدّمه النشاط الرئيسي في الأصل، لتلقّي المعلومات.

إذا تعذّر تنفيذ نشاط الطفل لأي سبب، مثل حدوث عطل، يتلقّى نشاط الوالدَين نتيجة تتضمّن الرمز RESULT_CANCELED.

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
    // ...

    static final int PICK_CONTACT_REQUEST = 0;

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                new Intent(Intent.ACTION_PICK,
                new Uri("content://contacts")),
                PICK_CONTACT_REQUEST);
            return true;
        }
        return false;
    }

    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        if (requestCode == PICK_CONTACT_REQUEST) {
            if (resultCode == RESULT_OK) {
                // A contact was picked. Display it to the user.
                startActivity(new Intent(Intent.ACTION_VIEW, data));
            }
        }
    }
}

تنسيق الأنشطة

عندما يبدأ نشاط ما نشاطًا آخر، يخضع كلاهما لعمليات انتقال دورة الحياة. يتوقف النشاط الأول عن العمل ويدخل في حالة "متوقف مؤقتًا" أو "متوقف"، بينما يتم إنشاء النشاط الآخر. في حال مشاركة هذه الأنشطة للبيانات المحفوظة على القرص أو في مكان آخر، من المهم معرفة أنّه لا يتم إيقاف النشاط الأول تمامًا قبل إنشاء النشاط الثاني. بل تتداخل عملية بدء الثانية مع عملية إيقاف الأولى.

يتم تحديد ترتيب عمليات معاودة الاتصال بدورة الحياة بشكل جيد، خاصةً عندما يكون النشاطان في العملية نفسها، أي التطبيق نفسه، ويبدأ أحدهما الآخر. في ما يلي ترتيب العمليات التي تحدث عند بدء النشاط (أ) للنشاط (ب):

  1. يتم تنفيذ طريقة onPause في النشاط A.
  2. يتم تنفيذ طرق onCreate وonStart وonResume الخاصة بالنشاط B بالتسلسل. يتم الآن التركيز على النشاط B.
  3. إذا لم يعُد النشاط (أ) مرئيًا على الشاشة، سيتم تنفيذ الطريقة onStop الخاصة به.

يتيح لك تسلسل عمليات معاودة الاتصال بدورة الحياة هذا إدارة عملية نقل المعلومات من نشاط إلى آخر.