Android Task Affinity Explanation

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 and singleTask
  • Create a new BridgeActivity which is used as a bridge between the web browser and your app. This Activity should have the <intent-filter> for the browser Intent.
  • The BridgeActivity should check if it is the root Activity 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 quietly finish() in onCreate().
  • If BridgeActivity detects that it is the root of its task, it can launch SplashActivity (make sure to set FLAG_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" and excludeFromRecents="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 task B, task B is stacked on top of task A. If the user presses the BACK key enough times in task B, 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...

  1. 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 of Activity into the current task. If the user presses the BACK key, this will finish Activity 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).
  2. 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 that Activity would belong to), then that existing task is brought to the foreground, cleared (all activities in the task are finished), a new instance of Activity is created at the root of the task and this task is put on top of the current task (so that when Activity finishes, the user is dropped back into the current task).
  3. 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 of Activity is created at the root of the task and this task is put on top of the current task (so that when Activity 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...

  1. If Activity has the same task affinity as the current task (ie: the task from which this code is executing) and Activity 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 of Activity, 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).
  2. If Activity has the same task affinity as the current task (ie: the task from which this code is executing) and Activity is not the root activity of the current task, this will simply create a new instance of Activity 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).
  3. 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 that Activity would belong to) and Activity 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).
  4. 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 that Activity would belong to) and Activity 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 of Activity is created and put on top of any existing activities in that task.
  5. 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 of Activity is created at the root of the task and the new task is decoupled from the task stack (so that when Activity 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...

  1. 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 of Activity into the current task. If the user presses the BACK key, this will finish Activity and also finish the current task (since there is only 1 activity in the task) and return the user to the HOME screen.
  2. 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 that Activity would belong to), then that existing task is brought to the foreground, cleared (all activities in the task are finished), a new instance of Activity is created at the root of the task and this task is decoupled from the task stack (so that when Activity finishes, the user is returned to the HOME screen).
  3. 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 of Activity is created at the root of the task and this task is decoupled from the task stack (so that when Activity 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



Leave a reply



Submit