Задача — это набор действий, с которыми взаимодействуют пользователи, пытаясь что-то сделать в вашем приложении. Эти действия расположены в стопке, называемой задней стопкой , в том порядке, в котором каждое действие открывается.
Например, приложение электронной почты может иметь одно действие для отображения списка новых сообщений. Когда пользователь выбирает сообщение, открывается новое действие для просмотра этого сообщения. Это новое действие добавляется в задний стек. Затем, когда пользователь нажимает кнопку «Назад» или показывает жестом «Назад», новое действие завершается и удаляется из стека.
Жизненный цикл задачи и ее задний стек
Главный экран устройства является отправной точкой для выполнения большинства задач. Когда пользователь касается значка приложения или ярлыка на панели запуска приложения или на главном экране, задача этого приложения выходит на передний план. Если для приложения не существует задачи, создается новая задача, и основное действие для этого приложения открывается как корневое действие в стеке.
Когда текущее действие запускает другое, новое действие помещается на вершину стека и получает фокус. Предыдущее действие остается в стеке, но останавливается. Когда действие остановлено, система сохраняет текущее состояние своего пользовательского интерфейса. Когда пользователь выполняет обратное действие, текущее действие извлекается из вершины стека и уничтожается. Предыдущее действие возобновляется, и предыдущее состояние пользовательского интерфейса восстанавливается.
Действия в стеке никогда не переупорядочиваются, а только помещаются в стек и извлекаются из него, когда они запускаются текущим действием и закрываются пользователем с помощью кнопки «Назад» или жеста. Таким образом, задний стек работает как объектная структура «последним пришел — первым вышел» . На рис. 1 показана временная шкала, на которой действия помещаются в задний стек и извлекаются из него.
Когда пользователь продолжает нажимать или жестикулировать «Назад», каждое действие в стеке удаляется, открывая предыдущее, пока пользователь не вернется на главный экран или к любому действию, которое выполнялось в момент начала задачи. Когда все действия удаляются из стека, задача больше не существует.
Поведение обратного нажатия для действий root-пусковой установки
Действия корневого средства запуска — это действия, которые объявляют фильтр намерений с помощью ACTION_MAIN
и CATEGORY_LAUNCHER
. Эти действия уникальны, поскольку они служат точками входа в ваше приложение из средства запуска приложения и используются для запуска задачи .
Когда пользователь нажимает или показывает жестом «Назад» из корневой программы запуска, система обрабатывает это событие по-разному в зависимости от версии Android, на которой работает устройство.
- Поведение системы на Android 11 и более ранних версиях
- Система завершает действие.
- Поведение системы на Android 12 и выше
Система перемещает действие и его задачу на задний план вместо того, чтобы завершить действие. Это поведение соответствует поведению системы по умолчанию при выходе из приложения с помощью кнопки «Домой» или жеста.
В большинстве случаев такое поведение означает, что пользователи могут быстрее возобновить работу вашего приложения из теплого состояния вместо того, чтобы полностью перезапускать приложение из холодного состояния .
Если вам необходимо предоставить настраиваемую обратную навигацию , мы рекомендуем использовать API активности AndroidX, а не переопределять
onBackPressed()
. API-интерфейсы действий AndroidX автоматически переходят к соответствующему поведению системы, если нет компонентов, перехватывающих системное нажатие «Назад».Однако если ваше приложение переопределяет
onBackPressed()
для обработки навигации «Назад» и завершения действия, обновите свою реализацию, чтобы она вызывалаsuper.onBackPressed()
вместо завершения. Вызовsuper.onBackPressed()
перемещает действие и его задачу на задний план, когда это необходимо, и обеспечивает более единообразную навигацию для пользователей между приложениями.
Фоновые и передние задачи
Задача — это целостная единица, которая может перемещаться на задний план , когда пользователь начинает новую задачу или переходит на главный экран. В фоновом режиме все действия задачи останавливаются, но задний стек задачи остается нетронутым — задача теряет фокус, пока выполняется другая задача, как показано на рис. 2. Затем задача может вернуться на передний план , чтобы пользователи можно продолжить с того места, где они остановились.
Рассмотрим следующий поток задач для текущей задачи A, в стеке которой есть три действия, в том числе два в рамках текущего действия:
Пользователь использует кнопку или жест «Домой», а затем запускает новое приложение из панели запуска приложений.
Когда появляется главный экран, задача A переходит в фоновый режим. Когда запускается новое приложение, система запускает задачу для этого приложения (Задача B) со своим собственным набором действий.
После взаимодействия с этим приложением пользователь снова возвращается на главную и выбирает приложение, которое изначально запустило задачу А.
Теперь задача А выходит на передний план — все три действия в ее стеке не повреждены, и возобновляется действие, находящееся наверху стека. На этом этапе пользователь также может вернуться к задаче B, перейдя на главную страницу и выбрав значок приложения, запустившего эту задачу, или выбрав задачу приложения на экране «Последние» .
Несколько экземпляров активности
Поскольку действия в заднем стеке никогда не перестраиваются, если ваше приложение позволяет пользователям запускать определенное действие из более чем одного действия, создается новый экземпляр этого действия и помещается в стек, вместо того, чтобы переносить любой предыдущий экземпляр действия в вершина. Таким образом, одно действие в вашем приложении может быть создано несколько раз, даже из разных задач, как показано на рисунке 3.
Если пользователь перемещается назад с помощью кнопки или жеста «Назад», экземпляры действия отображаются в том порядке, в котором они открывались, каждый со своим собственным состоянием пользовательского интерфейса. Однако вы можете изменить это поведение, если не хотите, чтобы действие создавалось более одного раза. Подробнее об этом читайте в разделе об управлении задачами .
Многооконные среды
Когда приложения одновременно запускаются в многооконной среде , поддерживаемой в Android 7.0 (уровень API 24) и выше, система управляет задачами отдельно для каждого окна. В каждом окне может быть несколько задач. То же самое относится и к приложениям Android, работающим на Chromebook : система управляет задачами или группами задач для каждого окна отдельно.
Обзор жизненного цикла
Подводя итог поведению по умолчанию для действий и задач:
Когда действие A запускает действие B, действие A останавливается, но система сохраняет свое состояние, например положение прокрутки и любой текст, введенный в формы. Если пользователь нажимает или использует жест «Назад» во время действия B, действие A возобновляется с восстановлением своего состояния.
Когда пользователь покидает задачу с помощью кнопки или жеста «Домой», текущая активность останавливается, а ее задача переходит в фоновый режим. Система сохраняет состояние каждого действия в задаче. Если позже пользователь возобновляет выполнение задачи, выбрав значок средства запуска, с которого была запущена задача, задача выходит на передний план и возобновляет действие в верхней части стека.
Если пользователь нажимает кнопку «Назад» или показывает жестом «Назад», текущее действие извлекается из стека и уничтожается. Предыдущая активность в стеке возобновляется. Когда активность уничтожается, система не сохраняет состояние активности.
Это поведение отличается от действий корневой программы запуска, когда ваше приложение работает на устройстве под управлением Android 12 или более поздней версии.
Действия могут создаваться несколько раз, даже из других задач.
Управление задачами
Android управляет задачами и резервным стеком, помещая все действия, начатые последовательно, в одну и ту же задачу, в стек «последний пришел — первый вышел». Это отлично работает для большинства приложений, и вам обычно не нужно беспокоиться о том, как ваши действия связаны с задачами или о том, как они существуют в заднем стеке.
Однако вы можете решить, что хотите прервать нормальное поведение. Например, вы можете захотеть, чтобы действие в вашем приложении начинало новую задачу при запуске, а не помещалось в текущую задачу. Или, когда вы запускаете действие, вы можете захотеть перенести существующий его экземпляр вместо создания нового экземпляра поверх заднего стека. Или вы можете захотеть, чтобы ваш задний стек был очищен от всех действий, кроме корневого действия, когда пользователь покидает задачу.
Вы можете делать это и многое другое, используя атрибуты в элементе манифеста <activity>
и флаги в намерении, которое вы передаете в startActivity()
.
Это основные атрибуты <activity>
, которые вы можете использовать для управления задачами:
-
taskAffinity
-
launchMode
-
allowTaskReparenting
-
clearTaskOnLaunch
-
alwaysRetainTaskState
-
finishOnTaskLaunch
Вот основные флаги намерений, которые вы можете использовать:
В следующих разделах обсуждается, как использовать эти атрибуты манифеста и флаги намерений, чтобы определить, как действия связаны с задачами и как они ведут себя в заднем стеке.
Также обсуждаются соображения относительно того, как задачи и действия представляются и управляются на экране «Последние». Обычно вы позволяете системе определять, как ваши задачи и действия будут представлены на экране «Последние», и вам не нужно изменять это поведение. Дополнительную информацию см. в разделе Экран «Недавние» .
Определить режимы запуска
Режимы запуска позволяют вам определить, как новый экземпляр действия связан с текущей задачей. Вы можете определить режимы запуска двумя способами, описанными в следующих разделах:
Объявляя действие в файле манифеста, вы можете указать, как оно будет связано с задачами при его запуске.
Использование флагов намерения
Когда вы вызываете
startActivity()
, вы можете включить вIntent
флаг, который объявляет, как (или будет ли) новое действие связано с текущей задачей.
Таким образом, если действие A запускает действие B, действие B может определить в своем манифесте, как оно связано с текущей задачей, а действие A может использовать флаг намерения, чтобы запросить, как действие B может быть связано с текущей задачей.
Если оба действия определяют, как действие B связывается с задачей, то запрос действия A, как определено в намерении, учитывается по сравнению с запросом действия B, как определено в его манифесте.
Определите режимы запуска с помощью файла манифеста
Объявляя действие в файле манифеста, вы можете указать, как действие связано с задачей, используя атрибут launchMode
элемента <activity>
.
Атрибуту launchMode
можно назначить пять режимов запуска:
-
"standard"
- Режим по умолчанию. Система создает новый экземпляр действия в задаче, из которой оно было запущено, и направляет ему намерение. Экземпляр действия может быть создан несколько раз, каждый экземпляр может принадлежать разным задачам, а одна задача может иметь несколько экземпляров.
-
-
"singleTop"
- Если экземпляр действия уже существует в верхней части текущей задачи, система направляет намерение к этому экземпляру посредством вызова метода
onNewIntent()
, а не создает новый экземпляр действия. Экземпляр действия создается несколько раз, каждый экземпляр может принадлежать разным задачам, а одна задача может иметь несколько экземпляров (но только если действие в верхней части заднего стека не является существующим экземпляром действия).
Например, предположим, что задний стек задачи состоит из корневого действия A с действиями B, C и D наверху (то есть стек имеет вид ABCD с D наверху). Приходит намерение для активности типа D. Если у D установлен
"standard"
режим запуска по умолчанию, запускается новый экземпляр класса, и стек становится ABCDD. Однако если режим запуска D —"singleTop"
, существующий экземпляр D получает намерение черезonNewIntent()
, поскольку он находится на вершине стека, а стек остается ABCD. Если же поступает интент на активность типа B, то в стек добавляется новый экземпляр B, даже если его режим запуска —"singleTop"
.-
-
"singleTask"
- Система создает действие в корне новой задачи или размещает действие в существующей задаче с той же привязкой. Если экземпляр действия уже существует, система направляет намерение существующему экземпляру посредством вызова его метода
onNewIntent()
, а не создания нового экземпляра. При этом все остальные виды деятельности, лежащие в основе этого, уничтожаются.
-
-
"singleInstance"
. - Поведение такое же, как и для
"singleTask"
, за исключением того, что система не запускает никаких других действий в задаче, содержащей экземпляр. Деятельность всегда является единственным членом своей задачи. Любые действия, начатые этим, открываются в отдельной задаче.
-
-
"singleInstancePerTask"
. - Действие может выполняться только как корневое действие задачи, первое действие, создавшее задачу, поэтому в задаче может быть только один экземпляр этого действия. В отличие от режима запуска
singleTask
, эту активность можно запускать несколько раз в разных задачах, если установлен флагFLAG_ACTIVITY_MULTIPLE_TASK
илиFLAG_ACTIVITY_NEW_DOCUMENT
.
-
Другой пример: приложение Android Browser заявляет, что действие веб-браузера всегда открывается в отдельной задаче, указывая режим запуска singleTask
в элементе <activity>
. Это означает, что если ваше приложение выдает намерение открыть браузер Android, его активность не помещается в ту же задачу, что и ваше приложение. Вместо этого либо для браузера запускается новая задача, либо, если в браузере уже есть задача, работающая в фоновом режиме, эта задача переносится вперед для обработки нового намерения.
Независимо от того, начинается ли действие в новой задаче или в той же задаче, что и действие, которое его запустило, кнопка «Назад» и жест всегда переводят пользователя к предыдущему действию. Однако если вы запускаете действие, которое указывает режим запуска singleTask
, и экземпляр этого действия существует в фоновой задаче, то вся эта задача выводится на передний план. На этом этапе задний стек включает в себя все действия из задачи, перенесенной наверх стека. На рисунке 4 показан сценарий этого типа.
Дополнительные сведения об использовании режимов запуска в файле манифеста см. в документации по элементу <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>
, поскольку система использует это имя для определения сходства задач по умолчанию для приложения.
Близость проявляется в двух случаях:
Когда намерение, запускающее действие, содержит флаг
FLAG_ACTIVITY_NEW_TASK
.Новая активность по умолчанию запускается в задаче активности, вызвавшей
startActivity()
. Он помещается в тот же задний стек, что и вызывающий объект.Однако если намерение, переданное в
startActivity()
содержит флагFLAG_ACTIVITY_NEW_TASK
, система ищет другую задачу для размещения нового действия. Часто это новая задача. Однако это не обязательно. Если существует задача с той же привязкой, что и у нового действия, действие запускается в этой задаче. Если нет, начинается новая задача.Если этот флаг заставляет действие начать новую задачу, а пользователь использует кнопку «Домой» или жест, чтобы выйти из нее, у пользователя должен быть какой-то способ вернуться к задаче. Некоторые объекты, такие как менеджер уведомлений, всегда запускают действия во внешней задаче, а не как часть своей собственной, поэтому они всегда помещают
FLAG_ACTIVITY_NEW_TASK
в намерения, которые они передают вstartActivity()
.Если внешний объект, который может использовать этот флаг, может вызвать ваше действие, позаботьтесь о том, чтобы у пользователя был независимый способ вернуться к запущенной задаче, например, с помощью значка запуска, где корневое действие задачи имеет намерение
CATEGORY_LAUNCHER
фильтр. Подробнее см. раздел о запуске задач .Когда для действия
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"
. Дополнительную информацию см. в разделе об очистке заднего стека .
Дополнительную информацию о том, как задачи и действия представляются и управляются на экране «Последние», можно найти на экране «Последние» .