How to prevent multiple instances of an activity when it is launched with different intents

0 votes
asked Dec 3, 2010 by bsberkeley

I've come across a bug in my application when it is launched using the "Open" button on the android market. It seems that launching it from the market uses a different intent then launching it from the phone's applications menu. 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 the above issue can lead to a stack 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 HOME.

Example: A-B-C -> HOME -> A when what I need is A-B-C -> HOME -> A-B-C

Is there a good way to prevent launching multiple activities of the same type without reseting to the root activity when using HOME?

11 Answers

0 votes
answered Jan 3, 2010 by shaireen

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

0 votes
answered Dec 3, 2010 by elevine

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.

0 votes
answered Jan 5, 2011 by gugarush

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.

0 votes
answered Dec 13, 2011 by duane-homick

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;       
    }
}
0 votes
answered Dec 28, 2011 by dunecat

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

0 votes
answered Dec 29, 2013 by user1249350

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.
0 votes
answered Dec 5, 2014 by gilm

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!

0 votes
answered Jan 29, 2015 by odhik-susanto

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

if ( !this.getClass().getSimpleName().equals("YourActivityClassName")) {
    start your activity
}
0 votes
answered Dec 17, 2015 by volodymyr-kulyk

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.

0 votes
answered Dec 22, 2015 by stanislavko

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);
Welcome to Q&A, where you can ask questions and receive answers from other members of the community.
Website Online Counter

...