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

77,222

Solution 1

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

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

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

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

Solution 5

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.

Share:
77,222
bsberkeley
Author by

bsberkeley

Updated on July 19, 2020

Comments

  • bsberkeley
    bsberkeley almost 4 years

    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?

  • bsberkeley
    bsberkeley over 13 years
    I thought of that, but what if the activity is not at the top of the stack? For example, it seems like singleTop will prevent A-A, but not A-B-A.
  • Eric Levine
    Eric Levine over 13 years
    Can you achieve what you want by using singleTop and the finish methods within Activity?
  • bsberkeley
    bsberkeley over 13 years
    Probably won't work because, according to the documentation "re-parenting is limited to the "standard" and "singleTop" modes." because "activities with "singleTask" or "singleInstance" launch modes can only be at the root of a task"
  • bsberkeley
    bsberkeley over 13 years
    I don't know if it will quite accomplish what I want. Example: If I'm on activity C after popping A and B, then a new activity A gets launched and I'll have something like C-A won't I?
  • Eric Levine
    Eric Levine over 13 years
    Its hard to answer this without understanding more about what these activities do. Can you provide more details about your application and the activities? I wonder if there is a mismatch between what the Home button does and how your want it to act. The home button does not exit an Activity, it "backgrounds" it so the user can switch to something else. The back button is what exits/finishes and activity. Breaking that paradigm might confuse/frustrate users.
  • bsberkeley
    bsberkeley over 13 years
    I've added another answer to this thread so you can see a copy of the manifest.
  • ubzack
    ubzack over 12 years
    I've been trying to solve this bug for years, and this was the solution that worked, so thank you very very much! I need to also note that this isn't just a problem within the Android Market, but also sideloading an app by uploading it to a server, or emailing it to your phone, causes this problem. All these things install the app using the Package Installer, where I believe the bug resides. Furthermore, just in case it's not clear, you only need to add this code to the onCreate method of what your root activity is.
  • Matt Connolly
    Matt Connolly over 12 years
    I find it very odd that this happens in a signed app deployed to the device, but not a debug version deployed from Eclipse. Makes it quite difficult to debug!
  • David Wasser
    David Wasser about 12 years
    This does happen with a debug version deployed from Eclipse as long as you START it also via Eclipse (or IntelliJ or other IDE). It has nothing to do with how the app gets installed on the device. The problem is due to the way the app is started.
  • Dave Cameron
    Dave Cameron almost 12 years
    Does anyone know if the same problem exists for Google Play?
  • David Wasser
    David Wasser almost 12 years
    Yes, the same problem exists for Google Play.
  • Carlos P
    Carlos P over 11 years
    Does anyone know if this code will ensure that the existing instance of the app will be brought to the foreground? Or does it just call finish(); and leave the user with no visual indication that anything has happened?
  • David Wasser
    David Wasser over 11 years
    @CarlosP if the activity that is being created is not the root activity of the task, there must (by definition) be at least one other activity underneath it. If this activity calls finish() then the user will see the activity that was underneath. Because of that you can safely assume that the existing instance of the app will be brought to the foreground. If that wasn't the case, you would have multiple instances of the app in separate tasks and the activity being created would be the root of its task.
  • Carlos P
    Carlos P over 11 years
    @DavidWasser Thanks for the clarification.
  • no_ripcord
    no_ripcord over 11 years
    I'm having a problem with this implementation, it doesn't work! Apparently the activity is not closing, because I get a black activity on top when I enter for the second time! Is someone having the same problem?
  • kevinthompson
    kevinthompson almost 11 years
    See also code.google.com/p/android/issues/detail?id=26658, which demonstrates that it is caused by things other than Eclipse.
  • cockadoodledo
    cockadoodledo over 10 years
    Does anyone know of a way to prevent this behaviour happening if the Activity is started using 'adb shell am start' or through Eclipse etc. The code above only seems to work in the case that the app is started through the Package Installer (Google Play, sideloading, email etc).
  • DuneCat
    DuneCat about 10 years
    So I should copy-paste an issue description that might grow stale? Which parts? Should the essential parts be kept if the link changes, and is it my responsibility that the answer is kept up to date? One should think the link only becomes invalid if the issue is solved. This is not a link to a blog, after all.
  • WonderCsabo
    WonderCsabo over 9 years
    You should use primitive boolean, which makes checking for null iunnecessary.
  • Kai
    Kai almost 9 years
    The issue is that, isTaskRoot() takes time to execute on low-end devices, so it will impact app startup time.
  • Hardik Amal
    Hardik Amal about 8 years
    since get_task permission has been deprecated after lollipop.any solution for this?
  • magorich
    magorich about 8 years
    This was the best solution I found and worked really great
  • Harry
    Harry about 5 years
    This only works for me in combination with android:launchMode="singleTop"
  • Warwicky
    Warwicky over 4 years
    I have been struggling with the problem. Hard to replicate the problem if you don't know source of the issue. Thanks.
  • David Wasser
    David Wasser over 4 years
    This won't always work. You would never be able to launch your app, exit your app and then quickly launch your app again. Android doesn't necessarily kill off the hosting OS process as soon as there are no active activities. In this case, when you start the app again, the variable IS_APP_RUNNING will be true and your app will immediately quit. Not something the user is likely to find amusing.