Tasks и Back Stack
Task - это набор активити, с которыми пользователь взаимодействует при использовании какого-либо приложения. У каждого task’а есть свой back stack - это что-то вроде способа организации открытых пользователем активити, который устроен по принципу LIFO - “последним вошел - первым вышел”. То есть при открытии новой активити, она становится вершиной стека, а предыдущая уходит в состояние “остановлена”. При нажатии пользователем на кнопку Back, новая активити уничтожается и удаляется из стека, а предыдущая восстанавливается (возвращается в состояние “возобновлена”). Если продолжать нажимать на кнопку Back, то в итоге пользователь вернётся на главный экран устройства, а task перестанет существовать. Если же пользователь свернёт приложение, то task продолжит существовать в фоне и хранить весь свой стек, при этом все активити перейдут в состояние “остановлена”. Поэтому пользователь в любой момент сможет вновь открыть приложение и продолжить работу с того, на чём остановился. Однако такой task может быть удален системой при нехватке ресурсов.
Может возникнуть вопрос: а как же фрагменты? Как они сохраняются в стеке? У них всё устроено несколько иначе, чем у активити: фрагмент помещается в back stack, управляемый активити и то, только если был вызван соответствующий метод (addToBackStack()
) во время транзакции.
В версии Android 7.0 была добавлена поддержка многооконного режима: пользователь может разделить экран и таким образом работать с несколькими приложениями. В таком режиме система управляет task’ами отдельно для каждого окна, т.е. у каждого окна может быть несколько task’ов.
Визуально task’и можно увидеть на экране последних запущенных задач:
Управление task’ами
Некоторые приложения спроектированы таким образом, что есть несколько точек перемещения к одной и той же активити. Несмотря на то, что такая активити уже может находится в стеке, каждый раз будет создаваться её новый экземпляр и также сохраняться в стек. Таким образом, когда пользователь решит переместиться к самой первой активити, он увидит все открытые им, казалось бы одинаковые активити, но в разном состоянии. Подобного эффекта можно избежать при помощи специальных атрибутов манифеста и флагов для Intent.
Обратите внимание, что иногда атрибуты в манифесте и флаги в Intent могут противоречить друг другу. В этом случаи флаги Intent будут более приоритетны.
Атрибуты
launchMode
Данный атрибут можно указать для каждой активити в манифесте. Имеет несколько значений:
standard
- режим по умолчанию. Активити может быть создана несколько раз, при этом каждый экземпляр может находится в разных task’ах, а каждый task содержать несколько её экземпляров.singleTop
- если активити на данный момент является вершиной стека, то вместо создания нового экземпляра у нее сработает методonNewIntent()
. Если активити не является вершиной стека, то будет создан и помещён в стек её новый экземпляр. Активити может быть создана несколько раз, при этом каждый экземпляр может находится в разных task’ах, а каждый task содержать несколько её экземпляров.singleTask
- создает новый task и устанавливает активити корневой для него, но только в случае, если экземпляра данной активити нет ни в одном другом task’е. Если активити уже расположена в каком либо task’е, то откроется именно тот экземпляр активити и для неё будет вызван методonNewIntent()
. Она в свою очередь становится главной, а все верхние экземпляры удаляются (если они есть). При этом, если активити была вытащена из фонового task’а, то мы переключимся на этот task и его стек. Только один экземпляр такой активити может существоватьsingleInstance
- тоже что иsingleTask
, но для данной активити всегда будет создаваться отдельный task и она будет в ней корневой. Данное значение указывает, что активити будет одним и единственным членом своего task’а. Все активити, запускаемые посредством такой активити будут открываться в отдельном task’е.
taskAfinity
Позволяет изменять поведение активити, например, чтобы в одном приложении активити работали в разных task’ах или активити разных приложений работали в одном. Для этого в манифесте для каждой активити указывается название task’а с помощью атрибута taskAfinity
. Имя task’а должно быть отличным от имени пакета, объявленного в манифесте. Данный параметр будет работать при следующих обстоятельствах:
- Intent, который запускает активити, содержит флаг
FLAG_ACTIVITY_NEW_TASK
. По умолчанию новая активити запускается в том же task’е, что и активити, из которой она была запущена. А данный флаг заставляет систему искать другой task для её размещения (по имени, указанному в атрибутеtaskAfinity
). Если нужный task уже существует, то активити будет помещена в него. Если нет, то запуститься новый task. - У запускаемой активити установлен атрибут
allowTaskReparenting = true
. Этот атрибут означает, что активити может перемещаться между task’ом, который ее вызвал, и task’ом, который указан вtaskAfinity
- в зависимости от того, какой task сейчас активен.
Флаги
Флаги устанавливаются для Intent, который открывает новую активити при помощи метода startActivity()
. Флаги приоритетнее атрибутов в манифесте.
Виды флагов:
FLAG_ACTIVITY_NEW_TASK
- запускает активити в новом task’е. Если уже существует task с экземпляром данной активити, то этот task выводится на передний план, активити восстанавливает своё последнее состояние и для неё срабатывает методonNewIntent()
. Данный флаг аналогичен значениюsingleTask
атрибутаlaunchMode
.FLAG_ACTIVITY_SINGLE_TOP
- если активити запускает сама себя, то есть она находится в вершине стека, то вместо создания нового экземпляра в стеке вызывается методonNewIntent()
. Данный флаг аналогичен значениюsingleTop
атрибутаlaunchMode
.FLAG_ACTIVITY_CLEAR_TOP
- если экземпляр данной активити уже существует в стеке данного task’а, то все активити, находящиеся поверх нее разрушаются и этот экземпляр становится вершиной стека. Также вызовется методonNewIntent()
.FLAG_ACTIVITY_CLEAR_TOP
чаще всего используется вместе сFLAG_ACTIVITY_NEW_TASK
. При совместном использовании эти флаги позволяют найти существующую активити в другом task’е и поставить её в такое положение, в котором она сможет реагировать на полученный Intent.
Очистка стека
Если task долгое время находится в фоне, то система сама чистит его стек, оставляя только корневую активити. Подобное поведение объясняется тем, что по прошествии длительного времени пользователь, вероятно, забыл, что он делал в приложении и открыл его повторно уже с иной целью.
У активити существует три атрибута для изменения такого поведения:
alwaysRetainTaskState
- если значение этого атрибута для корневой активитиtrue
, то стек не будет чиститься и полностью восстановится даже после длительного времени.clearTaskOnLaunch
- если значение этого атрибута для корневой активитиtrue
, то стек будет чиститься моментально, как только пользователь покинет task. Полная противоположностьalwaysRetainTaskState
. Пользователь всегда будет возвращаться к task’у в его начальном состоянии, даже если покинет task всего на мгновение.finishOnTaskLaunch
- атрибут похож наclearTaskOnLaunch
, но он работает с одной активити, а не со всем task’ом. Если значение этого атрибутаtrue
, то активити будет частью task’а только в рамках текущего сеанса. Если пользователь покинет task, а затем вернётся - активити уже в нём не будет.
Полезные ссылки
Understand Tasks and Back Stack - официальная документация по этой теме.
Tasks и Back Stack в Android - статья на хабре.
Task. Что это такое и как формируется - статья со startandroid.ru.
Поведение Activity в Task. Intent-флаги, launchMode, affinity - статья со startandroid.ru.
Navigation and Task Stacks - статья с codepath.com.
Android Tasks: One and for all - статья с medium.com.