I'm getting extremely confused with activity launch modes. Here is the scenario.
Activity A - main, launcher with singleTask launch mode
Activity B - singleTask launch mode
Activity C - standard launch mode
Now here is what I'm doing
Activity A > Activity B > Activity C
And I press the home button. And I resume the app from recent apps. What actually happens is that
Activity A
But I have read the documentation and it should be like this after resume
Activity A > Activity B
Or am I missing something?
Well, a lot depends on how you have set taskAffinity for the various activities in the manifest.
Let's assume that you configured it so that activity A and activity B have different taskAffinity (which is the way you SHOULD configure it). When you start your app, Android creates a new task containing A. When you launch B from A, Android should create another new task containing only B. The task containing A is sent to the background. You should now have 2 tasks, one contains only A and one contains only B. When you launch C from B, the second task should now contain B->C. When you press the HOME button, the second task is sent to the background.
Now, when you look in the list of recent tasks, you should actually see 2 tasks for your app. Depending on which one you choose, you will get either the task containing only A, or the task containing B->C.
This is why you should NOT use special launch modes singleTask or singleInstance, because they do a lot of stuff that you don't expect. If you ignore this advice and use these launch modes anyway, you must be aware how taskAffinity impacts the way activities are launched into the various tasks, and you also need to be aware that you may end up with multiple tasks, in which case you need to provide a way for the user to return to the correct task from the list of recent tasks. This also means that you may need to provide different labels for the tasks and/or differnt icons for the tasks, in order to help the user find the correct task to resume.
Related
As per the documentation of FLAG_ACTIVITY_MULTIPLE_TASK:
This flag is used to create a new task and launch an activity into it.
This flag is always paired with either FLAG_ACTIVITY_NEW_DOCUMENT or FLAG_ACTIVITY_NEW_TASK. In both cases these flags alone would search through existing tasks for ones matching this Intent.This flag is ignored if one of FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NEW_DOCUMENT is not also set.
When paired with FLAG_ACTIVITY_MULTIPLE_TASK both of these behaviours are modified to skip the search for a matching task and unconditionally start a new task. When used with FLAG_ACTIVITY_NEW_TASK, a new task is always started to host the Activity for the Intent, regardless of whether there is already an existing task running the same thing.
Because the default system does not include graphical task management, you should not use this flag unless you provide some way for a user to return back to the tasks you have launched. See FLAG_ACTIVITY_NEW_DOCUMENT for details of this flag's use for creating new document tasks.
So this means that if I have an activity 1 which opens activity 2 with both of these flags, the 2 activities are opened in different stacks and are thus available as different tasks in recent.
However my question is to understand the behaviour of back button in this scenario.
ideally when activities are launched without any flags, pressing back on them destroys the activity and it is removed from the backstack. if there are no activities, the system exits the app but keeps the backstack with the last activity.
however in this case when i press back, 2 different beaviors are observed:
(Scenario: An app that launches Activity1 which launches Activity2 which launches Activity3 via startActivity(Intent(...).also{it.flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_MULTIPLE_TASK}) )
if i just press back on Activity 3 , i move to Activity 2 . similarly when i press back on Activity 2, i move to Activity 1. and finally when i press back on Activity 1, i exit the app . however, i can see all the activities remain in recents and available to be opened. when i open any of them and press back, then this time, i just get exited from the app into homescreen. video sample
if i press recents on any of the activities while traversing forward, the back-press stops working, and instead i get exited from any of the activity in recents. video sample
So :
why is this behaviour?
Can we assume that if there is just 1 activity in a back-stack, it will show up in recents and not get destroyed, unless the user explicitly clears it from the recents?
Why Activities show a weak link to the caller activity in 1st case and going back to the parent back-stack when press back in 1st scenario?
I'm trying to understand the Android Tasks and Back stack by reading the official documentation: https://developer.android.com/guide/components/activities/tasks-and-back-stack
I have a couple of questions regarding the documentation.
Question 1: In Figure 4. in the documentation, there are 2 tasks - a foreground and a background task, the background tasks contains Activity Y and X, if the Activity Y is declared with a singleTask launch mode, how is it possible to create a task with activity Y on top of activity X?
For other questions, I prepared a simple project - 2 activities, A and B. On both activities I have 2 buttons:
Button A opens Activity A
Button B opens Activity B
Activity A is the MAIN (LAUNCHER) activity.
Question 2: The documentation says that using the intent flag FLAG_ACTIVITY_NEW_TASK produces the same behavior as using the singleTask launcher mode:
This produces the same behavior as the "singleTask" launchMode value, discussed in the previous section.
This is not what I see from my test application. If the activity B has a launcher mode set to singleTask and if my backstack is A -> B -> A -> A, then if I open B the back stack will look like A -> B (it will pop the last 2 As). I guess that the reason behind it is the tasks created: when the launcher intent was sent it opened the activity A in the new task (let's call it to task 1), when I opened B, it opened it in task 2 (because it has a singleTask launch mode), then the 2 A activities where opened in Task 2 as well. After opening B again, Android found a task that already has a B activity (Task 2) and it brought it to the front, poping 2 As.
However, using a FLAG_ACTIVITY_NEW_TASK instead of singleTask does not produce the same behavior, it just opens B on top of everything else. Is the documentation wrong, or am I doing something wrong?
Question 3: The example at the end says:
the two launch modes that mark activities as always initiating a task, "singleTask" and "singleInstance", should be used only when the activity has an ACTION_MAIN and a CATEGORY_LAUNCHER filter. Imagine, for example, what could happen if the filter is missing: An intent launches a "singleTask" activity, initiating a new task, and the user spends some time working in that task. The user then presses the Home button. The task is now sent to the background and is not visible. Now the user has no way to return to the task, because it is not represented in the app launcher.
How to reproduce that? In my previous example, I open the activity B in a new task, but if I hit the home button and the launcher icon again, I get back to activity B. So even though I'm using a singleTask launcher mode, I'm still able to return to that task by using the launcher icon or selecting it from the list of the recent applications.
It's entirely possible that I'm doing something wrong here and that new tasks are not generated, is there a way to see all tasks and activities for a specific application?
Thanks.
Today I spent more time trying to understand what's going on here and I think I have the answers. I realized that nothing in my example created new tasks. Tasks are visible in the Recent screens (apps in the background) - at least on Android 5+.
So, the first question that should be answered is: Why creating activity B is not creating a new task?
It's because generating new tasks goes hand in hand with the taskAffinity property. If you don't specify this property, your activity will have the default taskAffinity which is your application's package name. When you open the activity with a singleTask launcher mode, Android will look for tasks with the same affinity (task's affinity is defined by its root activity affinity) and if it finds one it will add your activity to that task. Because I didn't specify the affinity, android assumed I want to add my activity to the task with an affinity equal to the application's package and it didn't create new tasks at all. It just added an activity to an existing task.
That being said, FLAG_ACTIVITY_NEW_TASK and singleTask are very much different, although sometimes they produce the same behavior. If you trigger an intent with the FLAG_ACTIVITY_NEW_TASK flag that is trying to open an activity A without specifying activity's taskAffinity you'll just normally add an activity on top of the current task. (This is still confusing to me, I would expect it to open an activity only if the activity is not in the stack; if it is, it should do nothing.)
Opening the same activity without this flag but using the singleTask mode, will again not create a new task but will:
Add the activity on top if it's not in the stack (this is the answer to question 1)
Destroy all activities on top of yours and call onNewIntent if the activity is already in the stack. (I think that branch.io uses this method to handle deep links - which seems really hacky to me, but...)
On the other hand, if you specify the taskAffinity for your activity then:
if singleTask mode is set, android will search for the task with this affinity and if it's present it will
add your activity on top if the activity is not on task's backstack
destroy all activities on top of your activity and call onNewIntent if the activity is in the back stack.
If the task is not present, it will create a new one and add your activity as root.
if FLAG_ACTIVITY_NEW_TASK flag is set in the intent, android will search for the task with the specified affinity and if it finds one it will just bring that task to the foreground, without destroying the stack or calling onNewIntent. (I tested that if the activity is in the back stack, I guess that if the activity is not in the back stack it would push it to the task's stack).
So, to answer question 2. The flag FLAG_ACTIVITY_NEW_TASK and the launcher mode singleTask are different. I guess that similar behavior can be achieved by using 2 more flags FLAG_ACTIVITY_CLEAR_TOP and FLAG_ACTIVITY_SINGLE_TOP.
I think that the example in the documentation regarding question 3 is relevant for older versions of Android (lower than 5.0). There different tasks were not shown in the Recent Screens - here's the documentation: https://developer.android.com/guide/components/activities/recents.
I've been reading doc regarding to launch modes and there is one thing I don't understard. The doc says that singleTask activity is always the root of the stack:
In contrast, "singleTask" and "singleInstance" activities can only
begin a task. They are always at the root of the activity stack.
Moreover, the device can hold only one instance of the activity at a
time — only one such task.
But: if you look at this part of doc at Figure 4, you see that when Activity 2 starts Activity Y (puts that task to foreground), Activity Y was already on the top of the task and will be on the top of the current task, not the root.
I tried this scenario in this simulation app and when I create singleTask activity, it always creates a new task. However, if the only instance already exists, it finishes all activities above this one so the only instance can be the root (and also the only activity in the task).
How could the Activity Y become on the top of the task above the Activity X?
Is there any other reason I'm missing?
PS: I also don't really understand the difference between Task and back stack.
As usual (sigh), the documentation is wrong. In the diagram you referenced, obviously Activity Y can't be defined as singleTask and yet be the top activity in a background task containing 2 activities.
When testing scenarios with special launch modes singleTask and singleInstance, please be aware that taskAffinity plays an important role in this behaviour, as taskAffinity takes priority over special launch modes.
Regarding the difference between "task" and "back stack":
A "task" is a stack of activities that can be manipulated as a whole group.
When you launch an application (assuming that it isn't currently running), Android creates a new task which is in the foreground and contains the root activity of the application you launched.
When that activity starts new activities, these new activities are added to current task (usually, although there are exceptions to this behaviour).
When you press the HOME button, the current task is moved from the foreground to the background.
When you show the list of "recents", what is displayed is the list of recent tasks, not the list of recent activities or list of recent applications.
When you select a task from the list of recent tasks, if that task is still active (still has live activities in it), the entire task (including all of its activities) will be brought from the background to the foreground.
Tasks can also be "stacked". When an activity in the current task starts an activity in a new task, the new task is stacked on top of the current task. This only serves to control what happens when the new task completes. In the usual case, when the new task completes (all of its activities have finished), Android will return the user to the previous task (ie: the task that started the completing task).
A "back stack" usually refers to a set of activities within a task. Each task has its own stack of activities. This is used to control what happens when the current activity (the one on top of the back stack) finishes. Normally Android returns the user to the activity that is directly underneath (below) the finishing activity in the back stack.
The Android code and documentation often refer to the "root" of a task (this is the activity that was used to start the task) and the "top" or "front" of a task (this is the activity that is currently being shown).
Actually, the documentation lies :-( Here's an example:
In contrast, "singleTask" and "singleInstance" activities can only
begin a task.
This statement is usually, but not always correct. For example, let's say I have 2 activities: A and B. A is the launch activity (ie: the one with ACTION=MAIN and CATEGORY=DEFAULT) and is defined with standard launch mode. B is defined with launchMode="singleTask". I start the application and Android creates an instance of A. In A I then do:
startActivity(new Intent(this, B.class));
This will create a new instance of activity B and put it on top of A in the same task. It will not create a new task with activity B as the root. The reason is that activity A and activity B have the same taskAffinity (by default all activities of an application have the same taskAffinity), and Android will ignore the launch mode of B in this case.
The documentation also says:
Moreover, the device can hold only one instance of the activity at a
time — only one such task.
Again, taskAffinity can break this behaviour. Assume again we have A, B and C, all with the same (default) taskAffinity. A and C have standard launch mode, B has launchMode="singleTask". If A starts B, the instance of B ends up not in a new task, but in the same task as A (see above). Now B starts C. Android creates an instance of C and puts it on top of B in the same task. Now C calls:
startActivity(new Intent(this, B.class));
Android creates a new instance of B and puts this on top of C in the task. There are now 2 instances of B and neither of them is the root activity of the task! This behaviour is also due to the fact that taskAffinity trumps launch mode.
I have to app stacks like this:
A->B-C->D
X->Y
In Y I want to use getLaunchIntentForPackage("package") to get to D, however I get to
A. What launch mode should I use etc? I would like to use singleTask for ABCD if possible.
Also when in D and I press home and launch via the Icon for A again then depending on launch mode I don't get to D (that I would like), but to A. DCB are cleared.
I tried a lot of things but I just don't understand how it works. I don't seem to get a consequent behaviour.
getLaunchIntentForPackage just gives you the Main activity of package. In your first app i am guessing it is A. So if you try starting it from 2nd app (X->Y), it will start A and not D which is a different activity. So you are not getting to D.
When you press home and click launch icon for first app, if there is already a task for it, it is brought foreground. Looks like A, the app's main activity (A) gets invoked - not the stack top activity D. And B->C-D get cleared because you may be using singleTask launch mode for Activity A, which tries to take you back to activity in existing task.
So what you observe is expected. The launch modes are defined per activity, not per app/package level.
So to get from Y to D, you need to start activity D using intent flag set to FLAG_ACTIVITY_NEW_TASK, this will ensure you get to the existing D, that is already on top in your first task (A->B->C->D)
To do the same from home screen (launcher icon) you may need to try below options for SingleTask mode, since you want to preserve the existing stack (A->B->C-D) , and simply want get to D; Quoting from the link:
"+ For launchMode=singleTask if there is an intent_filter in the
manifest the task stack is always cleared after returning to Home and
re-launching (returns to main activity instead of last activity).
+ For launchMode=standard re-launch from Home instead returns to last
activity in task (as expected).
+ If there is no intent_filter listed then even with
launchMode=singleTask re-launch from Home returns to the last activity
in the task"
So, set your main activity launch mode to standard, it will let you preserve the stack as-is (2nd +point above). You can still start this in singleTask mode using intent-flags (eg: FLAG_ACTIVITY_NEW_TASK) from your other app. Intent flags override manifest launch modes, see here
I have multiple activities in my application .
The flow for activities is
A->B->C...
A->D->E...
A->F->G..
....
And each has a back button to go back to previous activity and home to go back to A.
My question is when i launch each of these as singletask, i can visible feel fast switching between activities when i click back or home. I dont see any issues if i keep them as singletask.
BUt are there any issues that i am verlooking if i keep the activities as singletask in my application. Thanks a lot for your time and help
Yes there are issues in changing the behaviour mode of activity from mainfest.xml, When you will press the home button for android home screen and get back to the application the previous activity will be gone and will finish the application, then better is to use intent instead using single task mode, you can use flags with intent to cleat the activity stacks..and by launching the mode in single task you are changing the systems settings, why dont you keep the launch mode and give it to system for handling the activities..!
Intent intent = new Intent(getApplicationContext(),A.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
A "standard" or "singleTop" activity can be instantiated many
times. They can belong to multiple tasks, and a given task can
have multiple instances of the same activity.
In contrast, "singleTask" and "singleInstance" activities are
limited to just one instance. Since these activities are at the
root of a task, this limitation means that there is never more
than a single instance of the task on the device at one time.
The clearTaskOnLaunch attribute If this attribute is set to
"true" in the root activity of a task, the stack is cleared down
to the root activity whenever the user leaves the task and
returns to it. In other words, it's the polar opposite of
alwaysRetainTaskState(which u can use if u want to retain all activities in a stsck). The user always returns to the task in
its initial state, even after a momentary absence or even after pressing home in ur case.