Задачи и стек переходов назад

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

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

Задача — это коллекция операций, с которыми взаимодействует пользователь при выполнении определенного задания. Операции упорядочены в виде стека (стека переходов назад), в том порядке, в котором открывались операции.

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

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

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

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

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

Рисунок 3. Создается несколько экземпляров одной операции.

Задача — это связанный блок, который может переходить в фоновый режим, когда пользователи начинают новую задачу или переходят на главный экран с помощью кнопки Домой. В фоновом режиме все операции задачи остановлены, но стек обратного вызова для задачи остается неизменным. Задача просто потеряла фокус во время выполнения другой задачи, как показано на рисунке 2. Затем задача может вернуться на передний план, так что пользователи могут продолжить ее с прерванного места. Предположим, например, что текущая задача (Задача A) содержит три операции в своем стеке — две операции под текущей операцией. Пользователь нажимает кнопку Домой, затем запускает новое приложение из средства запуска приложений. Когда появляется главный экран, Задача A переходит в фоновый режим. Когда запускается новое приложение, система запускает задачу для этого приложения (Задачу B) со своим собственным стеком операций. После взаимодействия с этим приложением пользователь снова возвращается на главный экран и выбирает изначально запущенную Задачу A. Теперь Задача A переходит на передний план — все три операции ее стека остались неизменными, и возобновляется операция, находящаяся на вершине стека. В этот момент пользователь может также переключиться обратно на Задачу B, перейдя на главный экран и выбрав значок приложения, которое запустило эту задачу (или выбрав задачу приложения на экране обзора). Это пример многозадачности в системе Android.

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

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

Подведем итоги поведения операций и задач:

  • Когда Операция A запускает Операцию B, Операция A останавливается, но система сохраняет ее состояние (например, положение прокрутки и текст, введенный в формы). Если пользователь нажимает кнопку Назад в Операции B, Операция A возобновляет работу из сохраненного состояния.
  • Когда пользователь выходит из задачи нажатием кнопки Домой, текущая операция останавливается и ее задача переводится в фоновый режим. Система сохраняет состояние каждой операции в задаче. Если пользователь впоследствии возобновляет задачу, выбирая значок запуска задачи, она переводится на передний план и возобновляет операцию на вершине стека.
  • Если пользователь нажимает кнопку Назад, текущая операция удаляется из стека и уничтожается. Возобновляется предыдущая операция в стеке. Когда операция уничтожается, система не сохраняет состояние операции.
  • Можно создавать несколько экземпляров операции, даже из других задач.

Дизайн навигации

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

Сохранение состояния операции

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

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

Дополнительную информацию о сохранении состояния вашей операции см. в документе Операции.

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

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

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

В этом смысле главными атрибутами <activity>, которые вы можете использовать, являются следующие:

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

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

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

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

Определение режимов запуска

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

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

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

Использование файла манифеста

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

Атрибут launchMode указывает инструкцию по запуску операции в задаче. Существует четыре различных режима запуска, которые вы можете назначить атрибуту launchMode:

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

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

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

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

Примечание. Хотя операция запускает новую задачу, кнопка Назад возвращает пользователя к предыдущей операции.

"singleInstance".
То же, что и "singleTask", но при этом система не запускает никаких других операций в задаче, содержащей этот экземпляр. Операция всегда является единственным членом своей задачи; любые операции, запущенные этой операцией, открываются в отдельной задаче.

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

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

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

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

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

Использование флагов намерений

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

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

Обработка привязок

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

Вы можете изменить привязку любой данный операции с помощью атрибута taskAffinity элемента &lt;activity&gt;.

Атрибут taskAffinity принимает строковое значение, которое должно отличаться от имени пакета по умолчанию, объявленного в элементе &lt;manifest&gt; , поскольку система использует это имя для идентификации привязки задачи по умолчанию для приложения.

Привязка вступает в игру в двух случаях:

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

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

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

  • Если для атрибута allowTaskReparenting операции установлено значение "true".

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

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

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

Очистка стека переходов назад

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

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

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" (см. раздел Очистка стека).

Дополнительную информацию о представлении задач и операций и управлении ими на экране обзора см. в разделе Экран обзора.