Что такое backstack android

Tasks and the Back Stack

What actually happens when you tap the launcher icon for your app? If you said ‘my app launches’, you are technically correct, the best kind of correct. To dive in a bit deeper, it is helpful to understand what a task is and how it interacts with a little thing called the back button.

Tasks

A task is a collection of metadata and information around a stack of activities (you can see exactly what kind of data by looking at the RecentTaskInfo class).

So when you tap the launcher icon for your app, what the system is actually doing is looking for a previously existing task (determined by the Intent and Activity it points to) to resume — getting you back to exactly where you were. If no existing task is found, then a new task is created with your newly launched activity as the base activity on the task’s back stack.

Back Stack

As you might imagine, a task’s back stack is tied together with the back button, but it goes both ways. When you start a new activity using startActivity(), that is (by default) pushing a new activity onto your task, causing the previous Activity to be paused (and stopped if the new activity fully obscures the previous activity).

The back button (by default) then ‘pops’ the stack, calling finish() on the topmost activity, destroying it and removing it from the back stack and taking you back to the previous activity. This repeats until there’s nothing left in the back stack and you’re back at the launcher.

The Back Stack and Fragments

The back stack doesn’t apply only to activities: it also applies to fragments. When you provide a fragment transaction to add, replace, or remove a fragment from your UI, you can use addToBackStack() to effectively add the FragmentTransaction to the back stack.

This way when the back button is hit, the FragmentTransaction is reversed (an added fragment removed, a replaced fragment restored, or a removed fragment re-added). Each transaction added to the back stack is reversed in turn until they’re all removed at which time the default activity finishing behavior again kicks in.

Back isn’t the only navigation button

Of course, the back button isn’t the only navigation button on a modern Android device (no we don’t talk about the menu button anymore).

The home button is probably the most straightforward with its single focus: it puts the current task into the background, taking you back to your launcher.

Note: moving your task to the background does not kill your task (although the topmost activity is most certainly paused+stopped): it’ll live on until the process is killed. Learn more about process priorities and when your app can be killed in the ‘Who lives and who dies?’ blog post.

The overview button (formerly recents), takes you, as you might imagine, to the Overview screen. This is the ‘app switcher’ of the Android world — here you’ll see your most recent tasks and be able to select one to bring it back into the foreground.

Okay, that’s it. Nothing more to see.

Neat, startActivity() or addToBackStack() and the default back button behavior. Nothing special here, but nothing confusing either — these symmetric, consistent behavior serves as the defaults. In most cases, you should be using this default behavior.

Before you go running off and overriding onBackPressed() directly, there’s a few specific cases you might want to consider:

Preventing back button fatigue

Of course, the back button loses some of its luster when you need to press it 10+ times to get out where you are. One case where this is easy to avoid is when you are launching the same Activity you are currently on.

Instead of creating a stack of multiple copies of the same Activity (which is less fun both from a memory pressure perspective and back button fatigue), your Activity can use launchMode=”singleTop” in the Android manifest or you can add Intent.FLAG_ACTIVITY_SINGLE_TOP to your Intent.

This prevents multiple copies of the same Activity on the top of the back stack. Instead, you’ll get a callback to onNewIntent() with the new Intent and any extras.

Note: Read the documentation on onNewIntent() carefully: getIntent() will still return the original Intent unless you use setIntent() to override it.

The back stack and notifications

If you’re building a notification that points to an Activity deep within your app, there’s one case you want to avoid: tapping the back button exiting directly to the launcher. This occurs when the PendingIntent you provide starts a new task with just the one activity. And unless your notification is opening your launcher Activity, that’s not what you want. The user should be in the exact place they’d be as if they had navigated to that part of the app themselves. Your notification just saved them the intermediate steps.

Читайте также:  Nfc from android to android

For something so important, it would be nice to have a class that does all the work for you. Enter TaskStackBuilder: a class specifically for handling the flags and back stack for you for exactly this case:

You’ll notice use of the addNextIntentWithParentStack() method — this is the shortcut to building an entire task stack with just passing in the normal Intent you’d have created a PendingIntent from. It does have one requirement though: each Activity needs to have its parent Activity declared in the Android manifest (see the example from the docs).

And you don’t need to throw out TaskStackBuilder if the defaults don’t work in your case: editIntentAt() allows you to retrieve a specific Intent and set the action, set the data URI, or add extras. If you need even more customization, you can forgo using the *ParentStack() methods entirely and use addNextIntent() to directly add the exact Intents you need for your specific case.

Note: as mentioned in the docs, the other type of Activity you might launch from a notification is one specific to the Notification (i.e., not one in your normal application flow). An example of this might be Hangout’s Direct Reply-like Activity used prior to Android N. These activities are generally semi-transparent — you can see the other app below your Activity — and generally don’t have any synthetic back stack or new task associated with them.

Tasks and the back stack, working together

Remember the important part here is being predictable. If you’re messing with your back stack, make sure to test very thoroughly to ensure the best user experience.

Источник

Tasks и Back Stack в Android

Итак. Каждое Android приложение, как минимум, состоит из фундаментальных объектов системы — Activity. Activity — это отдельный экран который имеет свою отдельную логику и UI. Количество Activity в приложении бывает разное, от одного до много. При переходах между различными Activity пользователь всегда может вернуться на предыдущую, закрытую Activity при нажатии кнопки back на устройстве. Подобная логика реализована с помощью стека (Activity Stack). Его организация «last in, first out» — т.е. последний вошел, первый вышел. При открытии новой Activity она становится вершиной, а предыдущая уходит в режим stop. Стек не может перемешиваться, он имеет возможность добавления на вершину новой Activity и удаление верхней текущей. Одна и та же Activity может находиться в стеке, сколько угодно раз.
Task — это набор Activity. Каждый таск содержит свой стек. В стандартной ситуации, каждое приложение имеет свой таск и свой стек. При сворачивании приложения, таск уходит в background, но не умирает. Он хранит весь свой стек и при очередном открытии приложения через менеджер или через launcher, существующий таск восстановится и продолжит свою работу.

Ниже покажу картинку, как работает стек.

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

Маленький итог

Управление тасками

Существует два пути для изменения стандартной организации тасков. Мы можем устанавливать специальные атрибуты в манифесте для каждой Activity. Также мы можем устанавливать специальные флаги для Intent, который запускает новую Activity с помощью startActivity(). Заметьте, что иногда атрибуты в манифесте и флаги в Intent могут противоречить друг другу. В этом случаи флаги Intent будут более приоритетны.

Атрибут launchMode

Для каждой Activity в манифесте можно указать атрибут launchMode. Он имеет несколько значений:

  • standard — (по умолчанию) при запуске Activity создается новый экземпляр в стеке. Activity может размещаться в стеке несколько раз
  • singleTop — Activity может распологаться в стеке несколько раз. Новая запись в стеке создается только в том случаи, если данная Activity не расположена в вершине стека. Если она на данный момент является вершиной, то у нее сработает onNewIntent() метод, но она не будет пересоздана
  • singleTask — создает новый таск и устанавливает Activity корнeвой для него, но только в случаи, если экземпляра данной Activity нет ни в одном другом таске. Если Activity уже расположена в каком либо таске, то откроется именно тот экземпляр и вызовется метод onNewIntent(). Она в свое время становится главной, и все верхние экземпляры удаляются, если они есть. Только один экземпляр такой Activity может существовать
  • singleInstance — тоже что и singleTask, но для данной Activity всегда будет создаваться отдельный таск и она будет в ней корневой. Данный флаг указывает, что Activity будет одним и единственным членом своего таска
Читайте также:  Первое включение телевизора хаер андроид

На самом деле не важно, в каком таске открыта новая Activity. При нажатии кнопки back мы все равно вернемся на предыдущий таск и предыдущие Activity. Единственный момент, который нужно учитывать — это параметр singleTask. Если при открытии такой Activity мы достанем ее из другого background таска, то мы полностью переключаемся на него и на его стек. на картинке ниже это продемонстрировано.

Флаги

Как и говорил, мы можем устанавливать специальный флаги для Intent, который запускает новую Activity. Флаги более приоритетны, чем launchMode. Существует несколько флагов:

  • FLAG_ACTIVITY_NEW_TASK — запускает Activity в новом таске. Если уже существует таск с экземпляром данной Activity, то этот таск становится активным, и срабатываем метод onNewIntent().
    Флаг аналогичен параметру singleTop описанному выше
  • FLAG_ACTIVITY_SINGLE_TOP — если Activity запускает сама себя, т.е. она находится в вершине стека, то вместо создания нового экземпляра в стеке вызывается метод onNewIntent().
    Флаг аналогичен параметру singleTop описанному выше
  • FLAG_ACTIVITY_CLEAR_TOP — если экземпляр данной Activity уже существует в стеке данного таска, то все Activity, находящиеся поверх нее разрушаются и этот экземпляр становится вершиной стека. Также вызовется onNewIntent()

Affinity

Стандартно все Activity нашего приложения работают в одном таске. По желанию мы можем изменять такое поведение и указывать, чтобы в одном приложении Activity работали в разных тасках, или Activity разных приложений работали в одном. Для этого мы можем в манифесте для каждой Activity указывать название таска параметром taskAffinity. Это строковое значение, которое не должно совпадать с названием package, т.к. стандартный таск приложения называется именно как наш пакет. В общем случаи данный параметр указывает, что Activity будет гарантированно открываться в своём отдельном таске. Данный параметр актуален, если мы указываем флаг FLAG_ACTIVITY_NEW_TASK или устанавливаем для Activity атрибут allowTaskReparenting=«true». Этот атрибут указывает, что Activity может перемещаться между тасками, который её запустил и таском, который указан в taskAffinity, если один из них становится активным.

Чистка стека

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

  • alwaysRetainTaskState — если флаг установлен в true для корневой Activity, то стек не будет чиститься и полностью восстановится даже после длительного времени
  • clearTaskOnLaunch — если установить флаг в true для корневой Activity, то стек будет чиститься моментально, как только пользователь покинет таск. Полная противоположность alwaysRetainTaskState
  • finishOnTaskLaunch — работает аналогично clearTaskOnLaunch, но может устанавливаться на любую Activity и удалять из стека именно её

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

Источник

Tasks и Back Stack

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

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

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

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

Управление task’ами

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

Читайте также:  Отличия miui от андроид

Обратите внимание, что иногда атрибуты в манифесте и флаги в 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, а затем вернётся — активити уже в нём не будет.

Источник

Оцените статью