- Tasks и Back Stack в Android
- Маленький итог
- Управление тасками
- Атрибут launchMode
- Флаги
- Affinity
- Чистка стека
- Activity Back Stack and Launch Mode Part -1
- a) Task Affinity:
- In general, what is affinity?
- Where can one define Task Affinity?
- b) Launch modes:
- 1. “standard”:
- 2. “singleTop”
- 3. “singleTask”
- 4. “singleInstance”
- General Note on using “singleTask” & “singleInstance”:
- 1. “FLAG_ACTIVITY_NEW_TASK”:
- Notes:
- 2. “FLAG_ACTIVITY_SINGLE_TOP”:
- 3. “FLAG_ACTIVITY_CLEAR_TOP”:
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 и удалять из стека именно её
Это всё для данного топика. Статья не импровизированная, а по сути является вольным переводом официальной документации. Рекомендую собрать легкий пример и поэксперементировать с флагами и атрибутами. Некоторые моменты, лично для меня были, неожиданно интересными. любые ошибки и недоработки учту в лс. Спасибо.
Источник
Activity Back Stack and Launch Mode Part -1
Have you ever wondered what happens to the current activity when you navigate to some other activity by taping some view on the screen? When you press back button how suddenly the same activity comes in font of you? If yes, then you are at the right place to get the answer about these kind of questions. In this blog we will learn about the activity back stack and launch mode of the activity. The blog is divided into 2 parts.
Knowledge before you read this blog
Every application has one or more tasks. Each task is differentiated by the task affinity which is unique for every task.
Application by default has only one task with task affinity equal to package name which can be found in androidmanifest.xml
To see, how many tasks a application has, you can just open recent-apps of your device and just count the number of instances of the same app, that is the number of tasks the app has.
Note: Generally, there will be only one task in your application, and which is the best way of writing your application. You should only create more than one task when it is exceptionally necessary. Like payment gateway page, which you want to keep unaffected by other tasks.
Activity Back Stack
The name itself suggest that it has something to do with stack of activities. As we discussed above every activity has either one or more tasks. For each task there is exactly one activity back stack. The topmost activity on the task’s back stack is visible on the screen. Usually, when the app launches the app has one task with just one activity (MainActivity) on it’s backstack.
Whenever, the user navigates from one activity to another the new activity is pushed on the top of the visible task’s back stack and it gets visible to the user, hiding the previous activity’s instance underneath. When user presses back the topmost activity is popped from the visible task’s backstack and the next activity in that stack gets visible on the screen.
This isn’t just limited to activities, even fragments can be pushed to the backstack. While navigating away from the fragment you can just add
so when you press back the previous fragment gets visible on the screen.
Note: I have used “visible task” term in above discussion. What it means is that there might be lot of task but you can see only one on the screen so the one which is being showed to you is visible task.
Launch Modes
Again name itself suggest that it has something to do with how activity gets launched when we call startActivity(intent) or similar functions. There are basically four type of the launch modes.
- Standard
- SingleTop
- Single Task
- Single Instance
Definition as per Android Docs:
“The system always creates a new instance of the activity in the target task and routes the intent to it.”
Standard launch mode is default in all the activities you create in your android application. In this launch mode no matter what is there in activity back stack the new instance of the new activity gets created when we pass the intent of the new activity.
For example: Suppose you have very simple app with just one activity named MainActivity and only one button on the main activity. Now if you call start activity with intent of MainActivity on click of the button then it will launch the new instance of same activity again and the current instance will go to back stack. If you click the button 10 times then there will be 10 instances of the activity in the back stack. So you need to press back 10 times to get out of your application.
Definition as per Android Docs:
“If an instance of the activity already exists at the top of the target task, the system routes the intent to that instance through a call to its onNewIntent() method, rather than creating a new instance of the activity. ”
Which means that if we call startActivity with the intent of same activity which is on the top of back stack then the new instance will not be created rather the intent will be passed to activity via a method onNewIntent().
Taking the same example as above: Suppose you have very simple app with just one activity named MainActivity and only one button on the main activity. Now if the launch mode of the main activity is single top then if you click on the button which call startActivity with the intent of MainActivity then also it won’t launch new instance of the MainActivity because the instance on the top of the backstack is of MainActivity. So you can prevent users from opening new instance of same activity which is visible on the screen.
That’s all for part 1, To learn about SingleTask and SingleInstance checkout the part 2 of this blog.
Источник
a) Task Affinity:
In general, what is affinity?
Like all of you have affinity for Android App Development,
Activities also have affinity towards default package, that is the reason all the Activities in one App falls under one task, by default.
Where can one define Task Affinity?
You define affinity per activity in your AndroidManifest.xml.
Affinity of an Activity is defined by following attribute within tag:
This parameter accepts a String. By default, every activity has same affinity as your package attribute value of mainfest tag.
Affinity of a Task is defined by the affinity of its root Activity.
b) Launch modes:
It is used to instructs Android system on how to launch a particular activity.
Two ways to define launchMode for an Activity:
Defining launch modes in Manifest File:
1. “standard”:
This is the default mode. In case you don’t define any launchMode, the new activity is launched in standard mode.
Here, a new instance of the Activity will be created — everytime.
2. “singleTop”
This is just as same as “standard” mode, except for — Android will perform an extra check “whether this activity is already on the top of the Activity Stack.”
If YES, then it won’t create a new instance of the Activity but instead it will call the onNewIntent() of the same Activity.
Have a look at the Activity Stack for “standard” & “singleTop” launch modes. Consider the Activity Intent order as: A → B → B → B
3. “singleTask”
Here, the referred Activity will be launched in the new Task.
(PS: You can notice this in Recents window)
So, you just define “launchMode”: “singleTask” and it will all work magically, huh?
Your current activity has to have a “taskAffinity” value defined other than the default package name. Or else, even though you have tagged it as “singleTask” it will still open it in the same damn Task.
One last important point to note is:
You CAN still have other Activities coming on top of this Activity, even in the newly created Task.
4. “singleInstance”
Well, this is exactly same as “singleTask” with one teeny-tiny difference.
That is:
You CANNOT have other Activities coming on top of this Activity. This Activity is a lone warrior in the Tasks playground. 😎
General Note on using “singleTask” & “singleInstance”:
When you launch an activity with a launchMode defined in Manifest, the Android system looks for the taskAffinity first. Then in the main stack of Tasks (i.e. Recents screen), Android checks if there is a task already present with the root Activity having same taskAffinity as the one for intended Activity, if Yes , then the Activity is opened in that task, else a new task is created and with Intended Activity placed at its root .
Defining launch modes using Intent Flags:
1. “FLAG_ACTIVITY_NEW_TASK”:
Now, putting it straight — this is just as same behaviour as
launchMode — singleTask.
Here, you do the same thing programmatically while creating your Intent for new Activity. Your code will look something like this:
Notes:
a) If you haven’t defined taskAffinity value different from the default one, setting this flag will have no effect. (Just like you did when using launchMode — singleTask)
b) The default behaviour of back press on newly created activity in a new task is — GO TO HOMESCREEN. If you want to go back to the Activity in your main task, that started this new Activity, then you have to define it in Manifest of your app. See the following snippet:
2. “FLAG_ACTIVITY_SINGLE_TOP”:
Again, this is same as launchMode — singleTop. You have to set the Intent Flag as follows:
Now, if the Activity to be launched is on the Top of the Activity stack, then instead of creating a new Activity, it will call onNewIntent() of the already open Activity. A sample snippet is as follows:
3. “FLAG_ACTIVITY_CLEAR_TOP”:
There are two scenarios of use here:
a) All Activities in the same Task:
As expected, this flag will clear all the Activities on the top of Intended Activity in the stack and bring it to Foreground.
b) Activities spread across different tasks:
If this flag is set along with FLAG_ACTIVITY_NEW_TASK then the system will bring the required Task that contains the intended Activity to Foreground, and clear all the Activities on the top of that Activity.
That will be a wrap on Android Tasks from my side.
Источник