Жизненный цикл активности (Представления)

Концепции и реализация Jetpack Compose

Когда пользователь перемещается по вашему приложению, выходит из него и возвращается обратно, экземпляры Activity в вашем приложении проходят через различные состояния в течение своего жизненного цикла. Класс Activity предоставляет ряд коллбэков, которые сообщают Activity об изменении состояния, а также о создании, остановке или возобновлении Activity, или об уничтожении процесса, в котором находится Activity.

В методах обратного вызова жизненного цикла вы можете объявить, как ваше приложение будет вести себя, когда пользователь покидает его и снова входит в него. Например, если вы создаете проигрыватель потокового видео, вы можете приостановить воспроизведение видео и разорвать сетевое соединение, когда пользователь переключается на другое приложение. Когда пользователь возвращается, вы можете повторно подключиться к сети и позволить ему возобновить воспроизведение видео с того же места.

Каждый коллбэк позволяет выполнить определенную работу, соответствующую конкретному изменению состояния. Выполнение нужной работы в нужное время и правильная обработка переходов делают ваше приложение более надежным и производительным. Например, хорошая реализация коллбэков жизненного цикла может помочь вашему приложению избежать следующих проблем:

  • Приложение может зависнуть, если пользователь получит телефонный звонок или переключится на другое приложение во время использования вашего приложения.
  • Потребление ценных системных ресурсов, когда пользователь ими активно не пользуется.
  • Потеря прогресса пользователя, если он покинет ваше приложение и вернется к нему позже.
  • Сбои или потеря прогресса пользователя при повороте экрана между альбомной и портретной ориентацией.

В этом документе подробно описывается жизненный цикл активности. Документ начинается с описания парадигмы жизненного цикла. Далее объясняется каждый из коллбэков: что происходит внутри во время их выполнения и что необходимо реализовать в процессе их работы.

Далее кратко рассматривается взаимосвязь между состоянием активности и уязвимостью процесса к завершению системой. Наконец, обсуждаются несколько тем, связанных с переходами между состояниями активности.

Для получения информации об обработке жизненных циклов, включая рекомендации по передовым методам, см. разделы «Обработка жизненных циклов с помощью компонентов, учитывающих жизненный цикл» и «Сохранение состояний пользовательского интерфейса» . Чтобы узнать, как спроектировать надежное приложение производственного качества, используя действия в сочетании с архитектурными компонентами, см. «Руководство по архитектуре приложений» .

Концепции жизненного цикла деятельности

Для управления переходами между этапами жизненного цикла активности класс Activity предоставляет основной набор из шести коллбэков: onCreate , onStart , onResume , onPause , onStop и onDestroy . Система вызывает каждый из этих коллбэков, когда активность переходит в новое состояние.

На рисунке 1 представлено визуальное отображение этой парадигмы.

Рисунок 1. Упрощенная иллюстрация жизненного цикла деятельности.

Когда пользователь начинает покидать активность, система вызывает методы для её завершения. В некоторых случаях активность завершается лишь частично и всё ещё остаётся в памяти, например, когда пользователь переключается на другое приложение. В таких случаях активность всё ещё может вернуться на передний план.

Если пользователь возвращается к активности, она возобновляется с того места, где он остановился. За некоторыми исключениями, приложениям запрещено запускать активности, работающие в фоновом режиме .

Вероятность завершения системой определенного процесса, а также входящих в него действий, зависит от состояния этого действия в данный момент. Более подробную информацию о взаимосвязи между состоянием и уязвимостью к удалению см. в разделе о состоянии действия и удалении из памяти .

В зависимости от сложности вашей задачи, вам, вероятно, не потребуется реализовывать все методы жизненного цикла. Однако важно понимать каждый из них и реализовывать те, которые обеспечивают корректное поведение приложения в соответствии с ожиданиями пользователей.

Обратные вызовы жизненного цикла

В этом разделе представлена ​​концептуальная и практическая информация о методах обратного вызова, используемых в течение жизненного цикла действия.

Некоторые действия должны выполняться в методах жизненного цикла активности. Однако код, реализующий действия зависимого компонента, следует размещать в самом компоненте, а не в методе жизненного цикла активности. Для этого необходимо сделать зависимый компонент совместимым с жизненным циклом. Чтобы узнать, как сделать зависимые компоненты совместимыми с жизненным циклом, см. раздел «Обработка жизненных циклов с помощью компонентов, совместимых с жизненным циклом» .

onCreate

Необходимо реализовать этот коллбэк, который срабатывает при первом создании активности системой. При создании активности она переходит в состояние Created . В методе onCreate выполните базовую логику запуска приложения, которая выполняется только один раз за всё время существования активности.

Например, ваша реализация метода onCreate может связывать данные со списками, ассоциировать активность с ViewModel и создавать экземпляры некоторых переменных в области видимости класса. Этот метод принимает параметр savedInstanceState , который представляет собой объект Bundle , содержащий ранее сохраненное состояние активности. Если активность никогда ранее не существовала, значение объекта Bundle равно null.

Если у вас есть компонент, отслеживающий жизненный цикл и связанный с жизненным циклом вашей активности, он получает событие ON_CREATE . Вызывается метод, аннотированный @OnLifecycleEvent , чтобы ваш компонент, отслеживающий жизненный цикл, мог выполнить любой необходимый код настройки для созданного состояния.

Следующий пример использования метода onCreate демонстрирует базовую настройку активности, такую ​​как объявление пользовательского интерфейса (определенного в XML-файле разметки), определение переменных-членов и настройка некоторых элементов пользовательского интерфейса. В этом примере XML-файл разметки передает идентификатор ресурса R.layout.main_activity в setContentView .

Котлин

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 . Для получения дополнительной информации о создании пользовательского интерфейса см. документацию по пользовательскому интерфейсу .

Ваша активность не остаётся в состоянии Created. После завершения выполнения метода onCreate активность переходит в состояние Started , и система быстро вызывает методы onStart и onResume .

onStart

Когда активность переходит в состояние Started, система вызывает onStart . Этот вызов делает активность видимой для пользователя, пока приложение готовится к переходу активности на передний план и переходу в интерактивное состояние. Например, в этом методе инициализируется код, отвечающий за пользовательский интерфейс.

Когда активность переходит в состояние «Запущено», любой компонент, отслеживающий жизненный цикл активности и связанный с ней, получает событие ON_START .

Метод onStart завершается быстро, и, как и в случае с состоянием Created, активность не остается в состоянии Started. После завершения этого обратного вызова активность переходит в состояние Resumed , и система вызывает метод onResume .

onResume

Когда активность переходит в состояние «Возобновлено», она выходит на передний план, и система вызывает коллбэк onResume . Это состояние, в котором приложение взаимодействует с пользователем. Приложение остается в этом состоянии до тех пор, пока не произойдет что-либо, что отвлечет фокус от приложения, например, телефонный звонок, переход пользователя к другой активности или выключение экрана устройства.

Когда активность переходит в состояние «Возобновлено», любой компонент, учитывающий жизненный цикл активности, получает событие ON_RESUME . Именно здесь компоненты жизненного цикла могут включить любую функциональность, которая должна выполняться, пока компонент виден и находится на переднем плане, например, запуск предварительного просмотра с камеры.

При возникновении прерывающего события активность переходит в состояние "Приостановлено" , и система вызывает функцию обратного вызова onPause .

Если активность возвращается из состояния "Приостановлено" в состояние "Возобновлено", система снова вызывает метод onResume . Поэтому реализуйте onResume для инициализации компонентов, которые вы освобождаете во время onPause , а также для выполнения любых других инициализаций, которые должны происходить каждый раз, когда активность переходит в состояние "Возобновлено".

Вот пример компонента, учитывающего жизненный цикл, который обращается к камере, когда получает событие ON_RESUME :

Котлин

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 . Однако в многооконном режиме ваша активность может быть полностью видна, даже если она находится в состоянии «Приостановлено». Например, когда приложение находится в многооконном режиме и пользователь нажимает на окно, которое не содержит вашу активность, ваша активность переходит в состояние «Приостановлено».

If you want the camera active only when the app is Resumed (visible and active in the foreground), then initialize the camera after the ON_RESUME event demonstrated previously. If you want to keep the camera active while the activity is Paused but visible, such as in multi-window mode, then initialize the camera after the ON_START event.

Однако, если камера будет активна во время паузы в работе приложения, это может заблокировать доступ к камере для другого приложения, возобновившего работу в многооконном режиме. Иногда необходимо оставлять камеру активной во время паузы, но это может фактически ухудшить общее впечатление от использования приложения.

Поэтому тщательно продумайте, на каком этапе жизненного цикла наиболее целесообразно взять под контроль общие системные ресурсы в контексте многооконного режима. Для получения дополнительной информации о поддержке многооконного режима см. раздел «Поддержка многооконного режима» .

Независимо от того, какое событие инициализации вы выберете для выполнения операции, обязательно используйте соответствующее событие жизненного цикла для освобождения ресурса. Если вы инициализируете что-либо после события ON_START , освободите или завершите это после события ON_STOP . Если вы инициализируете что-либо после события ON_RESUME , освободите после события ON_PAUSE .

Приведённый выше фрагмент кода размещает код инициализации камеры в компоненте, учитывающем жизненный цикл. Вместо этого вы можете поместить этот код непосредственно в коллбэки жизненного цикла активности, такие как onStart и onStop , но мы не рекомендуем этого делать. Добавление этой логики в независимый компонент, учитывающий жизненный цикл, позволяет повторно использовать компонент в нескольких активностях без дублирования кода. Чтобы узнать, как создать компонент, учитывающий жизненный цикл, см. раздел «Обработка жизненных циклов с помощью компонентов, учитывающих жизненный цикл (представления)» .

onPause

Система называет этот метод первым признаком того, что пользователь покидает вашу активность, хотя это не всегда означает, что активность уничтожается. Это указывает на то, что активность больше не находится на переднем плане, но она все еще видна, если пользователь находится в многооконном режиме. Существует несколько причин, по которым активность может перейти в это состояние:

  • Событие, прерывающее выполнение приложения, как описано в разделе о функции обратного вызова onResume , приостанавливает текущую активность. Это наиболее распространенный случай.
  • В многооконном режиме в любой момент времени в фокусе находится только одно приложение, а все остальные приложения система приостанавливает.
  • Открытие нового, полупрозрачного элемента, например диалогового окна, приостанавливает работу элемента, который оно закрывает. Пока элемент частично виден, но не находится в фокусе, он остается приостановленным.

Когда активность переходит в состояние «Приостановлено», любой компонент, отслеживающий жизненный цикл этой активности, получает событие ON_PAUSE . В этом случае компоненты, отслеживающие жизненный цикл, могут остановить любую функциональность, которая не требуется, пока компонент не находится на переднем плане, например, остановить предварительный просмотр с камеры.

Используйте метод onPause для приостановки или корректировки операций, которые не могут быть продолжены или могут быть продолжены в умеренном объеме, пока Activity находится в состоянии Paused, и которые вы ожидаете возобновить в ближайшее время.

Также можно использовать метод onPause для освобождения системных ресурсов, дескрипторов датчиков (например, GPS) или любых ресурсов, влияющих на время работы батареи, пока ваша активность приостановлена ​​и пользователь в них не нуждается.

Однако, как упоминалось в разделе об onResume , приостановленное действие может оставаться полностью видимым, если приложение находится в многооконном режиме. Рекомендуется использовать onStop вместо onPause , чтобы полностью освободить или скорректировать ресурсы и операции, связанные с пользовательским интерфейсом, для лучшей поддержки многооконного режима.

Следующий пример реакции LifecycleObserver на событие ON_PAUSE является аналогом предыдущего примера с событием ON_RESUME , освобождая камеру, которая инициализируется после получения события ON_RESUME :

Котлин

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;
        }
    }
    ...
}

This example places the camera release code after the ON_PAUSE event is received by the LifecycleObserver .

Выполнение onPause очень короткое и не всегда предоставляет достаточно времени для выполнения операций сохранения. По этой причине не следует использовать onPause для сохранения данных приложения или пользователя, выполнения сетевых запросов или операций с базой данных. Такая работа может не завершиться до завершения метода.

Вместо этого выполняйте операции завершения работы с высокой нагрузкой во время onStop . Дополнительную информацию о подходящих операциях для выполнения во время onStop см. в следующем разделе. Дополнительную информацию о сохранении данных см. в разделе о сохранении и восстановлении состояния .

Завершение выполнения метода onPause не означает, что активность выходит из состояния «Приостановлено». Скорее, активность остается в этом состоянии до тех пор, пока не возобновится или не станет полностью невидимой для пользователя. Если активность возобновляется, система снова вызывает обратный вызов onResume .

Если активность возвращается из состояния «Приостановлено» в состояние «Возобновлено», система сохраняет экземпляр Activity в памяти, вызывая его при вызове onResume . В этом сценарии нет необходимости повторно инициализировать компоненты, созданные во время выполнения каких-либо методов обратного вызова, предшествующих состоянию «Возобновлено». Если активность становится полностью невидимой, система вызывает onStop .

onStop

Когда ваша активность перестаёт быть видимой для пользователя, она переходит в состояние «Остановлено» , и система вызывает функцию обратного вызова onStop . Это может произойти, когда недавно запущенная активность занимает весь экран. Система также вызывает onStop , когда активность завершает работу и собирается быть завершена.

Когда активность переходит в состояние «Остановлено», любой компонент, отслеживающий жизненный цикл активности, получает событие ON_STOP . В этом случае компоненты, отслеживающие жизненный цикл, могут остановить выполнение любой функциональности, которая не требуется, пока компонент не отображается на экране.

В методе onStop освобождаются или корректируются ресурсы, которые не требуются, пока приложение не отображается пользователю. Например, ваше приложение может приостанавливать анимацию или переключаться с точных на точные обновления местоположения. Использование onStop вместо onPause означает, что работа, связанная с пользовательским интерфейсом, продолжается, даже когда пользователь просматривает вашу активность в многооконном режиме.

Кроме того, используйте onStop для выполнения относительно ресурсоемких операций завершения работы системы. Например, если вы не можете найти более подходящего времени для сохранения информации в базу данных, вы можете сделать это во время onStop . В следующем примере показана реализация onStop , которая сохраняет содержимое черновика заметки в постоянное хранилище:

Котлин

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 в ваше приложение, см. руководство по библиотеке Room для работы с постоянными данными .

Когда ваша активность переходит в состояние «Остановлено», объект Activity остается в памяти: он хранит всю информацию о состоянии и членах класса, но не привязан к менеджеру окон. При возобновлении активности он восстанавливает эту информацию.

Нет необходимости повторно инициализировать компоненты, созданные во время выполнения каких-либо методов обратного вызова, предшествующих состоянию «Возобновлено». Система также отслеживает текущее состояние каждого объекта View в макете, поэтому, если пользователь вводит текст в виджет EditText , это содержимое сохраняется, и вам не нужно его сохранять и восстанавливать.

Из состояния «Остановлено» активность либо возобновляет взаимодействие с пользователем, либо завершает выполнение и исчезает. Если активность возобновляет работу, система вызывает onRestart . Если Activity завершает выполнение, система вызывает onDestroy .

onDestroy

onDestroy вызывается перед уничтожением активности. Система вызывает этот коллбэк по одной из двух причин:

  1. Действие завершается либо из-за полного закрытия пользователем этого действия, либо из-за вызова функции finish действия.
  2. Система временно прерывает работу из-за изменения конфигурации, например, поворота устройства или перехода в многооконный режим.

Когда активность переходит в состояние уничтожения, любой компонент, отслеживающий жизненный цикл активности, получает событие ON_DESTROY . В этот момент компоненты жизненного цикла могут выполнить необходимую очистку перед уничтожением Activity .

Вместо того чтобы добавлять логику в Activity для определения причины её уничтожения, используйте объект ViewModel для хранения соответствующих данных представления для вашей Activity . Если Activity создаётся заново из-за изменения конфигурации, ViewModel не нужно ничего делать, поскольку он сохраняется и передаётся следующему экземпляру Activity .

If the Activity isn't recreated, then the ViewModel has the onCleared method called, where it can clean up any data it needs to before being destroyed. You can distinguish between these two scenarios with the isFinishing method.

Если выполнение действия завершается, 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 необходимо вызвать реализацию суперкласса, если вы хотите, чтобы состояние иерархии представлений сохранялось в реализации по умолчанию. Это показано в следующем примере:

Котлин

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 вызывается независимо от того, создает ли система новый экземпляр вашей активности или восстанавливает предыдущий, вам необходимо проверить, равен ли null Bundle , прежде чем пытаться его прочитать. Если он равен null, то система создает новый экземпляр активности, а не восстанавливает предыдущий, который был уничтожен.

Следующий фрагмент кода показывает, как можно восстановить некоторые данные состояния в onCreate :

Котлин

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 нулю.

Котлин

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 см. разделы «Intents» и «Intent Filters» .

начало активности

Если только что запущенное действие не требует возврата результата, текущее действие может запустить его, вызвав метод startActivity .

При работе в собственном приложении часто возникает необходимость просто запустить известную активность. Например, следующий фрагмент кода показывает, как запустить активность под названием SignInActivity .

Котлин

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

Java

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

Вашему приложению также может потребоваться выполнить какое-либо действие, например, отправить электронное письмо, текстовое сообщение или обновить статус, используя данные из вашей активности. В этом случае у вашего приложения может не быть собственных активностей для выполнения таких действий, поэтому вы можете использовать активности, предоставляемые другими приложениями на устройстве, которые могут выполнить эти действия за вас.

Вот где намерения действительно полезны. Вы можете создать намерение, описывающее действие, которое вы хотите выполнить, и система запустит соответствующее действие из другого приложения. Если существует несколько действий, которые могут обрабатывать это намерение, то пользователь может выбрать, какое из них использовать. Например, если вы хотите, чтобы пользователь отправил электронное письмо, вы можете создать следующее намерение:

Котлин

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 добавляемый к интенту, представляет собой строковый массив адресов электронной почты, на которые должно быть отправлено письмо. Когда почтовое приложение отвечает на этот интент, оно считывает предоставленный в дополнительном параметре строковый массив и помещает адреса в поле «Кому» формы создания письма. В этом случае начинается работа почтового приложения, и когда пользователь заканчивает работу, ваша работа возобновляется.

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 .

Котлин

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 объекта Activity A.
  2. Методы onCreate , onStart и onResume Activity B выполняются последовательно. Теперь Activity B находится в фокусе пользователя.
  3. Если Activity A больше не отображается на экране, выполняется его метод onStop .

Эта последовательность обратных вызовов жизненного цикла позволяет управлять передачей информации от одного действия к другому.