130

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?

Mr-IDE
  • 7,051
  • 1
  • 53
  • 59
bsberkeley
  • 1,301
  • 2
  • 9
  • 3
  • Associated tickets in Android bug tracker: https://issuetracker.google.com/issues/36941942 , https://issuetracker.google.com/issues/36907463 , https://issuetracker.google.com/issues/64108432 – Mr-IDE Jul 18 '19 at 14:55

11 Answers11

198

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;       
    }
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Duane Homick
  • 1,989
  • 1
  • 11
  • 3
  • 27
    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. – ubzack Dec 14 '11 at 21:28
  • 2
    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! – Matt Connolly Jan 20 '12 at 06:11
  • 6
    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**. – David Wasser May 30 '12 at 08:31
  • 1
    Does anyone know if the same problem exists for Google Play? – Dave Cameron Jul 18 '12 at 16:09
  • Yes, the same problem exists for Google Play. – David Wasser Aug 02 '12 at 08:28
  • 2
    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? – Carlos P Nov 04 '12 at 15:47
  • 5
    @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. – David Wasser Jan 22 '13 at 18:22
  • 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? – noripcord Feb 26 '13 at 15:48
  • 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). – cockadoodledo Nov 06 '13 at 16:36
  • The issue is that, isTaskRoot() takes time to execute on low-end devices, so it will impact app startup time. – Kai Sep 07 '15 at 05:21
  • since get_task permission has been deprecated after lollipop.any solution for this? – Hardik Amal Apr 20 '16 at 06:31
  • This only works for me in combination with android:launchMode="singleTop" – Harry May 08 '19 at 17:03
29

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!

gilm
  • 7,690
  • 3
  • 41
  • 41
10

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.

Eric Levine
  • 13,536
  • 5
  • 49
  • 49
  • 2
    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. – bsberkeley Dec 03 '10 at 01:23
  • Can you achieve what you want by using singleTop and the finish methods within Activity? – Eric Levine Dec 03 '10 at 01:37
  • 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? – bsberkeley Dec 03 '10 at 06:02
  • 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. – Eric Levine Dec 03 '10 at 17:53
  • I've added another answer to this thread so you can see a copy of the manifest. – bsberkeley Dec 03 '10 at 23:35
4

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.

hvaughan3
  • 10,955
  • 5
  • 56
  • 76
4

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

DuneCat
  • 788
  • 6
  • 17
  • See also https://code.google.com/p/android/issues/detail?id=26658, which demonstrates that it is caused by things other than Eclipse. – Kristopher Johnson Aug 19 '13 at 16:21
  • 1
    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. – DuneCat May 22 '14 at 11:50
2

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);
Community
  • 1
  • 1
StanislavKo
  • 363
  • 3
  • 8
0

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.
Sirko
  • 72,589
  • 19
  • 149
  • 183
0

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.

gugarush
  • 118
  • 2
  • 7
-1

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.

Volodymyr Kulyk
  • 6,455
  • 3
  • 36
  • 63
  • You should use primitive boolean, which makes checking for null iunnecessary. – WonderCsabo Feb 19 '15 at 12:21
  • 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. – David Wasser Feb 18 '20 at 16:24
-2

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

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
Odhik Susanto
  • 197
  • 1
  • 6
-2

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

Shaireen
  • 3,703
  • 5
  • 28
  • 40
  • 2
    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 Dec 03 '10 at 05:51