How to prevent multiple instances of an Activity when it is launched with different Intents

AndroidGoogle PlayBack StackActivity Stack

Android Problem Overview


I've come across a bug in my application when it is launched using the "Open" button on the Google Play Store app (previously called Android Market). It seems that launching it from the Play Store uses a different Intent than launching it from the phone's application menu of icons. This is leading to multiple copies of the same Activity being launched, which are conflicting with each other.

For example, if my app consists of the Activities A-B-C, then this issue can lead to a stack of A-B-C-A.

I tried using android:launchMode="singleTask" on all the Activities to fix this problem, but it has the unwanted side-effect of clearing the Activity stack to root, whenever I hit the HOME button.

The expected behavior is: A-B-C -> HOME -> And when the app is restored, I need: A-B-C -> HOME -> A-B-C

Is there a good way to prevent launching multiple Activities of the same type, without resetting to the root activity when using the HOME button?

Android Solutions


Solution 1 - Android

Add this to onCreate and you should be good to go:

// Possible work around for market launches. See https://issuetracker.google.com/issues/36907463
// for more details. Essentially, the market launches the main activity on top of other activities.
// we never want this to happen. Instead, we check if we are the root and if not, we finish.
if (!isTaskRoot()) {
    final Intent intent = getIntent();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(intent.getAction())) {
        Log.w(LOG_TAG, "Main Activity is not the root.  Finishing Main Activity instead of launching.");
        finish();
        return;       
    }
}

Solution 2 - Android

I'm just going to explain why it fails, and how to reproduce this bug programmatically so you can incorporate this in your test suite:

  1. When you launch an app through Eclipse or Market App, it launches with intent flags: FLAG_ACTIVITY_NEW_TASK.

  2. When launching through the launcher (home), it uses flags: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, and uses action "MAIN" and category "LAUNCHER".

If you would like to reproduce this in a test case, use these steps:

adb shell am start -f 0x10000000 -n com.testfairy.tests.regression.taskroot/.MainActivity 

Then do whatever is needed to get to the other activity. For my purposes, I just placed a button that starts another activity. Then, go back to the launcher (home) with:

adb shell am start -W -c android.intent.category.HOME -a android.intent.action.MAIN

And simulate launching it via the launcher with this:

adb shell am start -a "android.intent.action.MAIN" -c "android.intent.category.LAUNCHER" -f 0x10600000 -n com.testfairy.tests.regression.taskroot/.MainActivity

If you haven't incorporated the isTaskRoot() workaround, this will reproduce the problem. We use this in our automatic testing to make sure this bug never occurs again.

Hope this helps!

Solution 3 - Android

Have you tried the singleTop launch mode?

Here is some of the description from http://developer.android.com/guide/topics/manifest/activity-element.html:

> ... a new instance of a "singleTop" > activity may also be created to handle > a new intent. However, if the target > task already has an existing instance > of the activity at the top of its > stack, that instance will receive the > new intent (in an onNewIntent() call); > a new instance is not created. In > other circumstances — for example, if > an existing instance of the > "singleTop" activity is in the target > task, but not at the top of the stack, > or if it's at the top of a stack, but > not in the target task — a new > instance would be created and pushed > on the stack.

Solution 4 - Android

Perhaps it is this issue? Or some other form of the same bug?

Solution 5 - Android

I realize that the question does not have anything to do with Xamarin Android but I wanted to post something since I did not see it anywhere else.

To fix this in Xamarin Android I used the code from @DuaneHomick and added into MainActivity.OnCreate(). The difference with Xamarin is that is must go after Xamarin.Forms.Forms.Init(this, bundle); and LoadApplication(new App());. So my OnCreate() would look like:

protected override void OnCreate(Bundle bundle) {
    base.OnCreate(bundle);

    Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new App());

    if(!IsTaskRoot) {
        Intent intent = Intent;
        string action = intent.Action;
        if(intent.HasCategory(Intent.CategoryLauncher) && action != null && action.Equals(Intent.ActionMain, System.StringComparison.OrdinalIgnoreCase)) {
            System.Console.WriteLine("\nIn APP.Droid.MainActivity.OnCreate() - Finishing Activity and returning since a second MainActivity has been created.\n");
            Finish();
            return; //Not necessary if there is no code below
        }
    }
}

*Edit: Since Android 6.0, the above solution is not enough for certain situations. I have now also set LaunchMode to SingleTask, which seems to have made things work correctly once again. Not sure what effects this might have on other things though unfortunately.

Solution 6 - Android

I think the accepted answer (Duane Homick) has unhandled cases:

You have different extras (and app duplicates as a result):

  • when you launch application from Market or by home screen icon (which is placed by Market automatically)
  • when you launch application by launcher or manually created home screen icon

Here is a solution (SDK_INT>=11 for notifications) which i belive handle these cases and statusbar notifications also.

Manifest:

    <activity
        android:name="com.acme.activity.LauncherActivity"
        android:noHistory="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>
    <service android:name="com.acme.service.LauncherIntentService" />

Launcher activity:

public static Integer lastLaunchTag = null;
@Override
public void onCreate(final Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	mInflater = LayoutInflater.from(this);
	View mainView = null;
	mainView = mInflater.inflate(R.layout.act_launcher, null); // empty layout
	setContentView(mainView);

	if (getIntent() == null || getIntent().getExtras() == null || !getIntent().getExtras().containsKey(Consts.EXTRA_ACTIVITY_LAUNCH_FIX)) {
		Intent serviceIntent = new Intent(this, LauncherIntentService.class);
		if (getIntent() != null && getIntent().getExtras() != null) {
			serviceIntent.putExtras(getIntent().getExtras());
		}
		lastLaunchTag = (int) (Math.random()*100000);
		serviceIntent.putExtra(Consts.EXTRA_ACTIVITY_LAUNCH_TAG, Integer.valueOf(lastLaunchTag));
		startService(serviceIntent);

		finish();
		return;
	}

	Intent intent = new Intent(this, SigninActivity.class);
	if (getIntent() != null && getIntent().getExtras() != null) {
		intent.putExtras(getIntent().getExtras());
	}
	startActivity(intent);
}

Service:

@Override
protected void onHandleIntent(final Intent intent) {
	Bundle extras = intent.getExtras();
	Integer lastLaunchTag = extras.getInt(Consts.EXTRA_ACTIVITY_LAUNCH_TAG);
	
	try {
		Long timeStart = new Date().getTime(); 
		while (new Date().getTime() - timeStart < 100) {
			Thread.currentThread().sleep(25);
			if (!lastLaunchTag.equals(LauncherActivity.lastLaunchTag)) {
				break;
			}
		}
		Thread.currentThread().sleep(25);
		launch(intent);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
}

private void launch(Intent intent) {
	Intent launchIintent = new Intent(LauncherIntentService.this, LauncherActivity.class);
	launchIintent.addCategory(Intent.CATEGORY_LAUNCHER);
	launchIintent.setAction(Intent.ACTION_MAIN); 
	launchIintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
	launchIintent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 
	if (intent != null && intent.getExtras() != null) {
		launchIintent.putExtras(intent.getExtras());
	}
	launchIintent.putExtra(Consts.EXTRA_ACTIVITY_LAUNCH_FIX, true);
	startActivity(launchIintent);
}

Notification:

ComponentName actCN = new ComponentName(context.getPackageName(), LauncherActivity.class.getName()); 
Intent contentIntent = new Intent(context, LauncherActivity.class);
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);    
if (Build.VERSION.SDK_INT >= 11) { 
	contentIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // if you need to recreate activity stack
}
contentIntent.addCategory(Intent.CATEGORY_LAUNCHER);
contentIntent.setAction(Intent.ACTION_MAIN);
contentIntent.putExtra(Consts.EXTRA_CUSTOM_DATA, true);

Solution 7 - Android

I had the same problem, and I fixed it using the following solution.

In your main activity add this code on the top of the onCreate method:

ActivityManager manager = (ActivityManager) this.getSystemService( ACTIVITY_SERVICE );
List<RunningTaskInfo> tasks =  manager.getRunningTasks(Integer.MAX_VALUE);
	
for (RunningTaskInfo taskInfo : tasks) {
    if(taskInfo.baseActivity.getClassName().equals(<your package name>.<your class name>) && (taskInfo.numActivities > 1)){
        finish();
    }
}

don't forget to add this permission in your manifest.

< uses-permission android:name="android.permission.GET_TASKS" />

hope it helps you.

Solution 8 - Android

I had this problem also

  1. Don't call finish(); in the home activity it would run endlessly - home activity is being called by ActivityManager when it finished.
  2. Usually when the configuration is changing (i.e. rotate screen, change language, telephony service changes i.e. mcc mnc etc.) the activity recreate - and if the home activity is running then it calls again to A. for that need to add to manifest android:configChanges="mcc|mnc" - if you have connection to cellular, see http://developer.android.com/guide/topics/manifest/activity-element.html#config for which configuration there is when booting the system or push open or whatever.

Solution 9 - Android

Try this solution:
Create Application class and define there:

public static boolean IS_APP_RUNNING = false;

Then in your first (Launcher) Activity in onCreate before setContentView(...) add this:

if (Controller.IS_APP_RUNNING == false)
{
  Controller.IS_APP_RUNNING = true;
  setContentView(...)
  //Your onCreate code...
}
else
  finish();

P.S. Controlleris my Application class.

Solution 10 - Android

try using SingleInstance launch mode with affinity set to allowtaskreparenting This will always create the activity in new task but also allow its reparenting. Check dis :Affinity attribute

Solution 11 - Android

I found a way to prevent starting same activities, this works great for me

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbsberkeleyView Question on Stackoverflow
Solution 1 - AndroidDuane HomickView Answer on Stackoverflow
Solution 2 - AndroidgilmView Answer on Stackoverflow
Solution 3 - AndroidEric LevineView Answer on Stackoverflow
Solution 4 - AndroidDuneCatView Answer on Stackoverflow
Solution 5 - Androidhvaughan3View Answer on Stackoverflow
Solution 6 - AndroidStanislavKoView Answer on Stackoverflow
Solution 7 - AndroidgugarushView Answer on Stackoverflow
Solution 8 - Androiduser1249350View Answer on Stackoverflow
Solution 9 - AndroidVolodymyr KulykView Answer on Stackoverflow
Solution 10 - AndroidShaireenView Answer on Stackoverflow
Solution 11 - AndroidOdhik SusantoView Answer on Stackoverflow