Use of android taskaffinity
When you call startActivity()
to transition from one Activity
to another, if you do not set Intent.FLAG_ACTIVITY_NEW_TASK
in the Intent
flags, the new Activity
will be started in the same task, regardless of the value of taskAffinity
.
However, if you set Intent.FLAG_ACTIVITY_NEW_TASK
in the Intent
flags, the new Activity
will still be started in the same task if the new Activity
has the same taskAffinity
as the taskAffinity
of the task (this is determined by the taskAffinity
of the root Activity
in the task). But, if the new Activity
has a different taskAffinity
, the new Activity
will be started in a new task.
Based on your description, if you don't set Intent.FLAG_ACTIVITY_NEW_TASK
when starting a new Activity
, then all of your activities will end up in the same task.
android 4.4.X: taskAffinity & launchmode vs. Activity lifecycle
I've created a project based on the code you provided, and I was able to recreate your issue on my own Nexus 7. While I don't have a concrete, academic answer for you, my best explanation is the following:
1) MainActivity is started
2) Button clicked. AffinityTestActivity is started in a new task.
3) Button clicked. AffinityTestActivity finishes.
4) MainActivity resumes within the old task.
5) In MainActivity's onResume, the intent for HelloActivity is called within the same task.
6) The mysterious part which is my theory after a bit of tinkering: Some part of bringing the old task to the foreground continues to interact with MainActivity, the root of the old task, during its onResume call. This interaction causes the HelloActivity's onPause method to be triggered (probably not intended by the OS developers). While this isn't the most satisfying answer (given my limited experience with OS-level scheduling code and timing issues), my experiments point to something along those lines. My first clue to this interference was this frequent error seen in logcat:
06-24 11:06:28.015 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onPause
06-24 11:06:28.055 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onCreate
06-24 11:06:28.075 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onResume
06-24 11:06:28.175 665-685/? I/ActivityManager﹕ Displayed com.stackoverflow/.AffinityTestActivity: +163ms
06-24 11:06:29.997 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity$1@64e24bf8 finishes
06-24 11:06:30.007 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onPause
06-24 11:06:30.027 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onResume
06-24 11:06:30.027 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 starts HelloActivity
06-24 11:06:30.027 665-6346/? I/ActivityManager﹕ START u0 {cmp=com.stackoverflow/.HelloActivity} from pid 27200
06-24 11:06:30.117 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onCreate
06-24 11:06:30.127 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onResume
06-24 11:06:30.137 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onPause
06-24 11:06:30.287 665-685/? I/ActivityManager﹕ Displayed com.stackoverflow/.HelloActivity: +182ms
06-24 11:06:32.389 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity$1@64e356b0 finishes
06-24 11:06:32.389 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onDestroy
06-24 11:06:32.399 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onPause
06-24 11:06:32.399 27200-27200/com.stackoverflow E/ActivityThread﹕ Performing pause of activity that is not resumed: {com.stackoverflow/com.stackoverflow.MainActivity}
java.lang.RuntimeException: Performing pause of activity that is not resumed: {com.stackoverflow/com.stackoverflow.MainActivity}
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3015)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3003)
at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:2981)
at android.app.ActivityThread.access$1000(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1207)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
06-24 11:06:32.409 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onResume
06-24 11:06:32.769 27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onDestroy
As you can see, MainActivity's onPause method wasn't even called until after HelloActivity was finished. That's not right either. That, to me, shows that starting an activity within onResume while the task is being brought to the foreground is causing some unintended conflicts in the lifecycle.
To see what happened if I gave the activity/task a second to complete any unseen processing, I used a handler to call the HelloActivity intent in MainActivity:
@Override
protected void onResume() {
System.out.println(this + " onResume");
super.onResume();
if (!skipHello) {
System.out.println(this+" starts "+HelloActivity.class.getSimpleName());
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(MainActivity.this, HelloActivity.class);
startActivity(intent);
}
}, 1000);
skipHello = true;
} else {
skipHello = false;
}
}
This resulted in much better behavior. HelloActivity acted as it should, and onPause wasn't called. Obviously this isn't ideal for working code, but it shows that simply moving execution time forward by a second fixed the problem. More evidence of internal scheduling conflict within the task.
Next, I tried giving HelloActivity its own task as well:
<activity
android:label="HELLO"
android:name="com.stackoverflow.HelloActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTask"
android:taskAffinity=".DifferentTask">
</activity>
(For the record, this configuration doesn't make much sense, but I assume it's reflecting a scenario in your real project that has a more logical purpose.)
Under this scenario, everything works fine. HelloActivity's lifecycle does not interfere with MainActivity's lifecycle. However, it now has the overhead of its own task and the accompanying problems of running an activity as singleTask
(hitting the "Home" button and reopening the app will take you to MainActivity, leaving HelloActivity inaccessible in its new task even though it was the last activity viewed before closing the app).
My best recommendation would be to find a way to avoid this particular scenario. :) It seems like it's a bug within later versions of Android, albeit a strange edge case. If that's not an option, you could pursue one of the routes I used to get around it. I've tried a couple other things, but it's difficult to get around the fact that the scheduling is controlled at an OS level outside our grasp.
Sorry that I couldn't get you a more in-depth answer, but that's all I've got for now!
Understanding Android Launch modes with splash screens
There are a number of things going on here, I'll try to address these:
First off, using launchMode="singleInstance"
is problematic here, because you have not declared any taskAffinity
for any of your activities. So your SplashActivity
and your other activities have the same (default) taskAffinity
which tells Android that all these activities want to run in the same task, which conflicts with your launch mode declaration of SplashActivity
. This is bad, and you shouldn't do it. If you really want to have activities running in separate tasks, you should ensure that they have different taskAffinity
so as not to cause confusion.
Secondly, you wrote:
This seems to work, but there is something that I don't understand.
Why is the second Splash ignoring the Intent to launch Login
activity?. The logic tells me that a Login #2 should've launched.
What is happening here is this: When SplashActivity
calls startActivity()
to launch LoginActivity
, the Intent
used contains FLAG_ACTIVITY_NEW_TASK
set (because SplashActivity
is declared with launchMode="singleInstance"
so it has to launch other activities into a new task), so Android looks for an existing task that has LoginActivity
as its root Activity
. If it didn't find one, it would simply launch a new instance of LoginActivity
into a new task. However, in this case, it finds one (this is task #2 in your diagram), so instead, it just brings that task to the foreground without launching a new instance of LoginActivity
.
This is the same behaviour as what happens when you have an app running, then press the HOME button and then click the app icon for that app again. Android doesn't launch a new instance of the app's root Activity
in this case, it simply looks for an existing task that has the app's launch Activity
as its root Activity
and brings that task to the foreground in whatever state it was in.
My suggestion for you is as follows:
- Get rid of the use of the special launch modes
singleInstance
andsingleTask
- Create a new
BridgeActivity
which is used as a bridge between the web browser and your app. ThisActivity
should have the<intent-filter>
for the browserIntent
. - The
BridgeActivity
should check if it is the rootActivity
in its task. If it is not, it means that it has been launched into an existing task that was brought to the foreground and it can just quietlyfinish()
inonCreate()
. - If
BridgeActivity
detects that it is the root of its task, it can launchSplashActivity
(make sure to setFLAG_ACTIVITY_NEW_TASK
) to begin a new task (since there obviously wasn't already an existing task of your app) BridgeActivity
should be declared so that it doesn't end up in the task stack history or in the list of recent tasks (noHistory="true"
andexcludeFromRecents="true"
)
Difference between Intent.FLAG_ACTIVITY_CLEAR_TASK and Intent.FLAG_ACTIVITY_TASK_ON_HOME
There is a difference between the 2 snippets. Here's some important background information:
A task contains a stack of activities. A task can be in the foreground or in the background.
Tasks are also "stacked". If you are in task
A
and you start a new taskB
, taskB
is stacked on top of taskA
. If the user presses the BACK key enough times in taskB
, he will eventually end up back in task `A. This is standard Android behaviour.
Your snippet...
Intent intent = new Intent(this, Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
...will do 1 of the following things, depending...
- If
Activity
has the same task affinity as the current task (ie: the task from which this code is executing), it will clear the current task (finish all activities in the task) and launch a new instance ofActivity
into the current task. If the user presses the BACK key, this will finishActivity
and also finish the current task (since there is only 1 activity in the task) and return the user to either the HOME screen or the task that started this task (the task that is underneath this task in the task stack). - If
Activity
has a different task affinity than the current task, and there is already an existing task with that task affinity (ie: an existing task thatActivity
would belong to), then that existing task is brought to the foreground, cleared (all activities in the task are finished), a new instance ofActivity
is created at the root of the task and this task is put on top of the current task (so that whenActivity
finishes, the user is dropped back into the current task). - If
Activity
has a different task affinity than the current task, and there is no existing task with that task affinity, a new task is created and a new instance ofActivity
is created at the root of the task and this task is put on top of the current task (so that whenActivity
finishes, the user is dropped back into the current task).
This code snippet...
Intent intent = new Intent(this, Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(intent);
...will do 1 of the following things depending...
- If
Activity
has the same task affinity as the current task (ie: the task from which this code is executing) andActivity
is the root activity of the current task, this will do nothing. It will not start a new task, it will not clear any activities, it will not create a new instance ofActivity
, and it will not change the behaviour of what happens when the current task is finished (ie: if the current task was started by another task, when all activities in the current task are finished, it will drop the user back into the previous task in the task stack). - If
Activity
has the same task affinity as the current task (ie: the task from which this code is executing) andActivity
is not the root activity of the current task, this will simply create a new instance ofActivity
and put it on top of the current activity in the current task. It will not start a new task, it will not clear any activities, and it will not change the behaviour of what happens when the current task is finished (ie: if the current task was started by another task, when all activities in the current task are finished, it will drop the user back into the previous task in the task stack). - If
Activity
has a different task affinity than the current task, and there is already an existing task with that task affinity (ie: an existing task thatActivity
would belong to) andActivity
is the root activity of that existing task, then that existing task is brought to the foreground and that task is decoupled from the task stack (ie: when all activities in that task are finished, it will return the user to the HOME screen and not to the task that started that task). - If
Activity
has a different task affinity than the current task, and there is already an existing task with that task affinity (ie: an existing task thatActivity
would belong to) andActivity
is not the root activity of that existing task, then that existing task is brought to the foreground and that task is decoupled from the task stack (ie: when all activities in that task are finished, it will return the user to the HOME screen and not to the task that started that task) and a new instance ofActivity
is created and put on top of any existing activities in that task. - If
Activity
has a different task affinity than the current task, and there is no existing task with that task affinity, a new task is created and a new instance ofActivity
is created at the root of the task and the new task is decoupled from the task stack (so that whenActivity
finishes, the user is returned to the HOME screen and not to the task that started it).
and finally, this snippet...
Intent intent = new Intent(this, Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(intent);
...will do 1 of the following things, depending...
- If
Activity
has the same task affinity as the current task (ie: the task from which this code is executing), it will clear the current task (finish all activities in the task) and launch a new instance ofActivity
into the current task. If the user presses the BACK key, this will finishActivity
and also finish the current task (since there is only 1 activity in the task) and return the user to the HOME screen. - If
Activity
has a different task affinity than the current task, and there is already an existing task with that task affinity (ie: an existing task thatActivity
would belong to), then that existing task is brought to the foreground, cleared (all activities in the task are finished), a new instance ofActivity
is created at the root of the task and this task is decoupled from the task stack (so that whenActivity
finishes, the user is returned to the HOME screen). - If
Activity
has a different task affinity than the current task, and there is no existing task with that task affinity, a new task is created and a new instance ofActivity
is created at the root of the task and this task is decoupled from the task stack (so that whenActivity
finishes, the user is returned to the HOME screen).
I realize that this answer is long and complicated, but there are just so many different cases. I probably haven't even covered all the possible cases (for example, if Activity
has a special launch mode)...
What is the difference between `finishAffinity();` and `finish()` methods in Android?
finishAffinity() : finishAffinity()
is not used to "shutdown an application". It is used to remove a number of Activities belonging to a specific application from the current task (which may contain Activities belonging to multiple applications).
Even if you finish all of the Activities in your application, the OS process hosting your app does not automatically go away (as it does when you call System.exit()
). Android will eventually kill your process when it gets around to it. You have no control over this (and that is intentional).
finish() : When calling finish()
in an activity, the method onDestroy()
is executed this method can do things like:
Dismiss any dialogs the activity was managing.
Close any cursors the activity was managing.
Close any open search dialog.
Related Topics
Configuration on Demand Is Not Supported by the Current Version of the Android Gradle Plugin
How to Move Main Content with Drawer Layout Left Side
Should Accessing Sharedpreferences Be Done Off the UI Thread
Failure [Install_Failed_Update_Incompatible] Even If App Appears to Not Be Installed
Get Response Status Code Using Retrofit 2.0 and Rxjava
Onnewintent() Lifecycle and Registered Listeners
Populating Spinner Directly in the Layout Xml
How to Deal with Different Aspect Ratios in Libgdx
Where Is the All Android Broadcast Intent List
Android Opengl .Obj File Loader
What Is the Meaning of Android.Intent.Action.Main
How to Set Custom Typeface to Items in Navigationview
Android Design Support Library Expandable Floating Action Button(Fab) Menu
How Can One Pull the (Private) Data of One's Own Android App
Masked Input Using Edittext Widget in Android
Set Loadurltimeoutvalue on Webview
No Resource Identifier Found for Attribute '...' in Package 'Com.App....'