Статьи Tasks и Back Stack
Post
Cancel

Tasks и Back Stack

Task - это набор активити, с которыми пользователь взаимодействует при использовании какого-либо приложения. У каждого task’а есть свой back stack - это что-то вроде способа организации открытых пользователем активити, который устроен по принципу LIFO - “последним вошел - первым вышел”. То есть при открытии новой активити, она становится вершиной стека, а предыдущая уходит в состояние “остановлена”. При нажатии пользователем на кнопку Back, новая активити уничтожается и удаляется из стека, а предыдущая восстанавливается (возвращается в состояние “возобновлена”). Если продолжать нажимать на кнопку Back, то в итоге пользователь вернётся на главный экран устройства, а task перестанет существовать. Если же пользователь свернёт приложение, то task продолжит существовать в фоне и хранить весь свой стек, при этом все активити перейдут в состояние “остановлена”. Поэтому пользователь в любой момент сможет вновь открыть приложение и продолжить работу с того, на чём остановился. Однако такой task может быть удален системой при нехватке ресурсов.

activity-back-stack

Может возникнуть вопрос: а как же фрагменты? Как они сохраняются в стеке? У них всё устроено несколько иначе, чем у активити: фрагмент помещается в back stack, управляемый активити и то, только если был вызван соответствующий метод (addToBackStack()) во время транзакции.

fragment-back-stack

В версии Android 7.0 была добавлена поддержка многооконного режима: пользователь может разделить экран и таким образом работать с несколькими приложениями. В таком режиме система управляет task’ами отдельно для каждого окна, т.е. у каждого окна может быть несколько task’ов.

Визуально task’и можно увидеть на экране последних запущенных задач:

Recents Screen


Управление 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.

This post is licensed under CC BY 4.0 by the author.