Задачи и задний стек,Задачи и задний стек

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

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

Жизненный цикл задачи и ее задний стек

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

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

Действия в стеке никогда не переупорядочиваются, а только помещаются в стек и извлекаются из него, когда они запускаются текущим действием и закрываются пользователем с помощью кнопки или жеста «Назад». Таким образом, задний стек работает как объектная структура «последним пришел — первым вышел» . На рис. 1 показана временная шкала, на которой действия помещаются в задний стек и извлекаются из него.

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

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

Поведение обратного нажатия для действий root-пусковой установки

Действия корневого средства запуска — это действия, которые объявляют фильтр намерений с помощью ACTION_MAIN и CATEGORY_LAUNCHER . Эти действия уникальны, поскольку они служат точками входа в ваше приложение из средства запуска приложения и используются для запуска задачи .

Когда пользователь нажимает или показывает жестом «Назад» от действия корневого средства запуска, система обрабатывает это событие по-разному в зависимости от версии Android, на которой работает устройство.

Поведение системы на Android 11 и более ранних версиях
Система завершает действие.
Поведение системы на Android 12 и выше

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

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

Если вам необходимо предоставить настраиваемую обратную навигацию , мы рекомендуем использовать API активности AndroidX, а не переопределять onBackPressed() . API действий AndroidX автоматически переходят к соответствующему поведению системы, если нет компонентов, перехватывающих системное нажатие «Назад».

Однако если ваше приложение переопределяет onBackPressed() для обработки навигации «Назад» и завершения действия, обновите свою реализацию, чтобы она вызывала super.onBackPressed() вместо завершения. Вызов super.onBackPressed() перемещает действие и его задачу на задний план, когда это необходимо, и обеспечивает более единообразную навигацию для пользователей между приложениями.

Фоновые и передние задачи

Рисунок 2. Две задачи: Задача Б получает взаимодействие с пользователем на переднем плане, а Задача А находится в фоновом режиме и ожидает возобновления.

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

Рассмотрим следующий поток задач для текущей задачи A, в стеке которой есть три действия, в том числе два в рамках текущего действия:

  1. Пользователь использует кнопку или жест «Домой», а затем запускает новое приложение из панели запуска приложений.

    Когда появляется главный экран, задача A переходит в фоновый режим. Когда запускается новое приложение, система запускает задачу для этого приложения (Задача B) со своим собственным набором действий.

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

    Теперь задача А выходит на передний план — все три действия в ее стеке не повреждены, и возобновляется действие, находящееся наверху стека. На этом этапе пользователь также может вернуться к задаче B, перейдя на главную страницу и выбрав значок приложения, запустившего эту задачу, или выбрав задачу приложения на экране «Последние» .

Несколько экземпляров активности

Рисунок 3. Одно действие может быть создано несколько раз.

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

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

Многооконные среды

Когда приложения одновременно запускаются в многооконной среде , поддерживаемой в Android 7.0 (уровень API 24) и выше, система управляет задачами отдельно для каждого окна. В каждом окне может быть несколько задач. То же самое справедливо и для приложений Android, работающих на Chromebook : система управляет задачами или группами задач для каждого окна отдельно.

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

Подводя итог поведению по умолчанию для действий и задач:

  • Когда действие A запускает действие B, действие A останавливается, но система сохраняет свое состояние, например положение прокрутки и любой текст, введенный в формы. Если пользователь нажимает или использует жест «Назад» во время действия B, действие A возобновляется с восстановлением своего состояния.

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

  • Если пользователь нажимает кнопку «Назад» или показывает жестом «Назад», текущее действие извлекается из стека и уничтожается. Предыдущая активность в стеке возобновляется. Когда активность уничтожается, система не сохраняет состояние активности.

    Это поведение отличается от действий корневой программы запуска, когда ваше приложение работает на устройстве под управлением Android 12 или более поздней версии.

  • Действия могут создаваться несколько раз, даже из других задач.

Управление задачами

Android управляет задачами и резервным стеком, помещая все действия, начатые последовательно, в одну и ту же задачу, в стек «последний пришел — первый вышел». Это отлично работает для большинства приложений, и вам обычно не нужно беспокоиться о том, как ваши действия связаны с задачами или как они существуют в заднем стеке.

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

Вы можете делать это и многое другое, используя атрибуты в элементе манифеста <activity> и флаги в намерении, которое вы передаете в startActivity() .

Это основные атрибуты <activity> , которые вы можете использовать для управления задачами:

Вот основные флаги намерений, которые вы можете использовать:

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

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

Определить режимы запуска

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

Таким образом, если действие A запускает действие B, действие B может определить в своем манифесте, как оно связано с текущей задачей, а действие A может использовать флаг намерения, чтобы запросить, как действие B может быть связано с текущей задачей.

Если оба действия определяют, как действие B связывается с задачей, то запрос действия A, определенный в намерении, учитывается по сравнению с запросом действия B, как определено в его манифесте.

Определите режимы запуска с помощью файла манифеста

Объявляя действие в файле манифеста, вы можете указать, как действие связано с задачей, используя атрибут launchMode элемента <activity> .

Атрибуту launchMode можно назначить пять режимов запуска:

  1. "standard"
    Режим по умолчанию. Система создает новый экземпляр действия в задаче, из которой оно было запущено, и направляет ему намерение. Экземпляр действия может быть создан несколько раз, каждый экземпляр может принадлежать разным задачам, а одна задача может иметь несколько экземпляров.
  2. "singleTop"
    Если экземпляр действия уже существует в верхней части текущей задачи, система направляет намерение к этому экземпляру посредством вызова метода onNewIntent() , а не создает новый экземпляр действия. Экземпляр действия создается несколько раз, каждый экземпляр может принадлежать разным задачам, а одна задача может иметь несколько экземпляров (но только если действие в верхней части заднего стека не является существующим экземпляром действия).

    Например, предположим, что задний стек задачи состоит из корневого действия A с действиями B, C и D наверху (то есть стек имеет вид ABCD с D наверху). Приходит намерение для активности типа D. Если у D установлен "standard" режим запуска по умолчанию, запускается новый экземпляр класса, и стек становится ABCDD. Однако если режим запуска D — "singleTop" , существующий экземпляр D получает намерение через onNewIntent() , поскольку он находится на вершине стека, а стек остается ABCD. Если же поступает интент на активность типа B, то в стек добавляется новый экземпляр B, даже если его режим запуска — "singleTop" .

  3. "singleTask"
    Система создает действие в корне новой задачи или размещает действие в существующей задаче с той же привязкой. Если экземпляр действия уже существует, система направляет намерение существующему экземпляру посредством вызова его метода onNewIntent() , а не создания нового экземпляра. При этом все остальные виды деятельности, лежащие на его основе, уничтожаются.
  4. "singleInstance" .
    Поведение такое же, как и для "singleTask" , за исключением того, что система не запускает никаких других действий в задаче, содержащей экземпляр. Деятельность всегда является единственным членом своей задачи. Любые действия, начатые этим, открываются в отдельной задаче.
  5. "singleInstancePerTask" .
    Действие может выполняться только как корневое действие задачи, первое действие, создавшее задачу, поэтому в задаче может быть только один экземпляр этого действия. В отличие от режима запуска singleTask , эту активность можно запускать несколько раз в разных задачах, если установлен флаг FLAG_ACTIVITY_MULTIPLE_TASK или FLAG_ACTIVITY_NEW_DOCUMENT .

В качестве другого примера: приложение Android Browser заявляет, что действие веб-браузера всегда открывается в отдельной задаче, указав режим запуска singleTask в элементе <activity> . Это означает, что если ваше приложение выдает намерение открыть браузер Android, его активность не помещается в ту же задачу, что и ваше приложение. Вместо этого либо для браузера запускается новая задача, либо, если в браузере уже есть задача, работающая в фоновом режиме, эта задача переносится вперед для обработки нового намерения.

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

Рисунок 4. Представление того, как активность с режимом запуска "singleTask" добавляется в задний стек. Если действие уже является частью фоновой задачи со своим собственным задним стеком, то весь этот задний стек также выходит вперед поверх текущей задачи.

Дополнительные сведения об использовании режимов запуска в файле манифеста см. в документации по элементу <activity> .

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

При запуске действия вы можете изменить ассоциацию действия по умолчанию с его задачей, включив флаги в намерение, которое вы доставляете в startActivity() . Флаги, которые вы можете использовать для изменения поведения по умолчанию, следующие:

FLAG_ACTIVITY_NEW_TASK

Система начинает действие с новой задачи. Если задача для запускаемого действия уже запущена, эта задача выводится на передний план с восстановлением ее последнего состояния, и действие получает новое намерение в onNewIntent() .

Это приводит к тому же поведению, что и значение launchMode "singleTask" , обсуждавшееся в предыдущем разделе.

FLAG_ACTIVITY_SINGLE_TOP

Если запускаемое действие является текущим действием в верхней части заднего стека, то существующий экземпляр получает вызов onNewIntent() вместо создания нового экземпляра действия.

Это приводит к тому же поведению, что и значение launchMode "singleTop" , обсуждавшееся в предыдущем разделе.

FLAG_ACTIVITY_CLEAR_TOP

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

Атрибут launchMode , вызывающий такое поведение, не имеет значения.

FLAG_ACTIVITY_CLEAR_TOP чаще всего используется вместе с FLAG_ACTIVITY_NEW_TASK . При совместном использовании эти флаги определяют существующее действие в другой задаче и помещают его в положение, в котором оно может реагировать на намерение.

Обработка сходства

Сходство указывает, к какой задаче «предпочитает» относиться деятельность. По умолчанию все действия из одного приложения связаны друг с другом: они «предпочитают» выполнять одну и ту же задачу.

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

Вы можете изменить привязку действия, используя атрибут taskAffinity элемента <activity> .

Атрибут taskAffinity принимает строковое значение, которое должно отличаться от имени пакета по умолчанию, объявленного в элементе <manifest> , поскольку система использует это имя для определения сходства задач по умолчанию для приложения.

Близость проявляется в двух случаях:

  1. Когда намерение, запускающее действие, содержит флаг FLAG_ACTIVITY_NEW_TASK .

    Новая активность по умолчанию запускается в задаче активности, вызвавшей startActivity() . Он помещается в тот же задний стек, что и вызывающий объект.

    Однако если намерение, переданное в startActivity() содержит флаг FLAG_ACTIVITY_NEW_TASK , система ищет другую задачу для размещения нового действия. Часто это новая задача. Однако это не обязательно. Если существует задача с той же привязкой, что и у нового действия, действие запускается в этой задаче. Если нет, начинается новая задача.

    Если этот флаг заставляет действие начать новую задачу, а пользователь использует кнопку «Домой» или жест, чтобы выйти из нее, у пользователя должен быть какой-то способ вернуться к задаче. Некоторые объекты, такие как диспетчер уведомлений, всегда запускают действия во внешней задаче, а не как часть своей собственной, поэтому они всегда помещают FLAG_ACTIVITY_NEW_TASK в намерения, которые они передают в startActivity() .

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

  2. Когда для действия allowTaskReparenting имеет значение "true" .

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

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

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

Очистить заднюю стопку

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

Есть некоторые атрибуты активности, которые можно использовать для изменения этого поведения:

alwaysRetainTaskState
Если для этого атрибута установлено значение "true" в корневом действии задачи, только что описанное поведение по умолчанию не происходит. Задача сохраняет все действия в своем стеке даже по истечении длительного периода времени.
clearTaskOnLaunch

Если для этого атрибута установлено значение "true" в корневом действии задачи, задача переводится в корневое действие всякий раз, когда пользователь покидает задачу и возвращается к ней. Другими словами, это противоположность alwaysRetainTaskState . Пользователь всегда возвращается к задаче в исходное состояние, даже покинув задачу всего на мгновение.

finishOnTaskLaunch

Этот атрибут похож clearTaskOnLaunch , но он работает с одним действием, а не со всей задачей. Это также может привести к завершению любого действия, за исключением корневого действия. Если для него установлено значение "true" , действие остается частью задачи только для текущего сеанса. Если пользователь уходит, а затем возвращается к задаче, ее больше нет.

Запустить задачу

Вы можете настроить действие в качестве точки входа для задачи, присвоив ему фильтр намерений с "android.intent.action.MAIN" в качестве указанного действия и "android.intent.category.LAUNCHER" в качестве указанной категории:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

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

Эта вторая способность важна. Пользователи должны иметь возможность выйти из задачи, а затем вернуться к ней позже с помощью этого средства запуска действий. По этой причине используйте только два режима запуска, которые отмечают действия как всегда инициирующие задачу: "singleTask" и "singleInstance" , когда действие имеет фильтр ACTION_MAIN и CATEGORY_LAUNCHER .

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

В тех случаях, когда вы не хотите, чтобы пользователь мог вернуться к действию, установите для finishOnTaskLaunch элемента <activity> значение "true" . Дополнительную информацию см. в разделе об очистке заднего стека .

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

Больше ресурсов