web-dev-qa-db-fra.com

Comment empêcher plusieurs instances d'une activité lorsqu'elle est lancée avec différentes intentions

J'ai rencontré un bogue dans mon application lors de son lancement à l'aide du bouton "Ouvrir" de l'application Google Play Store (précédemment appelé Android Market). Il Il semble que son lancement à partir du Play Store utilise _ Intent autre chose que son lancement à partir du menu d'applications du téléphone, ce qui entraîne le lancement de plusieurs copies de la même activité en conflit.

Par exemple, si mon application se compose des activités A-B-C, ce problème peut alors générer une pile de A-B-C-A.

J'ai essayé d'utiliser Android:launchMode="singleTask" sur toutes les activités pour résoudre ce problème, mais cela a pour effet indésirable d'effacer la pile d'activités à la racine, dès que je clique sur le bouton ACCUEIL.

Le comportement attendu est: A-B-C -> HOME -> Et lorsque l'application est restaurée, j'ai besoin de: A-B-C -> HOME -> A-B-C

Existe-t-il un moyen efficace d’empêcher le lancement de plusieurs activités du même type sans réinitialiser l’activité racine lorsque vous utilisez le bouton ACCUEIL?

116
bsberkeley

Ajoutez ceci à onCreate et vous devriez être prêt à partir:

// 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;       
    }
}
179
Duane Homick

Je vais simplement expliquer pourquoi cela échoue et comment reproduire ce bogue par programme afin de l'intégrer à votre suite de tests:

  1. Lorsque vous lancez une application via Eclipse ou Market App, celle-ci se lance avec des indicateurs d'intention: FLAG_ACTIVITY_NEW_TASK.

  2. Lors du lancement via le programme de lancement (home), il utilise des indicateurs: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_BROUGHT_TO_FRONT | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED et utilise l'action " PRINCIPALE " et la catégorie " LANCEUR ".

Si vous souhaitez reproduire cela dans un scénario de test, procédez comme suit:

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

Ensuite, faites ce qui est nécessaire pour passer à l’autre activité. Pour mes besoins, je viens de placer un bouton qui lance une autre activité. Ensuite, retournez au lanceur (maison) avec:

adb Shell am start -W -c Android.intent.category.HOME -a Android.intent.action.MAIN

Et simulez le lancement via le lanceur avec ceci:

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

Si vous n'avez pas incorporé la solution de contournement isTaskRoot (), cela reproduira le problème. Nous l'utilisons dans nos tests automatiques pour nous assurer que ce bogue ne se reproduira plus jamais.

J'espère que cela t'aides!

25
gilm

Avez-vous essayé le mode de lancement singleTop ?

Voici une partie de la description de http://developer.Android.com/guide/topics/manifest/activity-element.html :

... une nouvelle instance d'une activité "singleTop" peut également être créée pour gérer une nouvelle intention. Cependant, si la tâche cible a déjà une instance existante de l'activité au sommet de sa pile, cette instance recevra la nouvelle intention (dans un appel onNewIntent ()); une nouvelle instance n'est pas créée. Dans d’autres circonstances - par exemple, si une instance existante de l’activité "singleTop" se trouve dans la tâche cible, mais pas en haut de la pile, ou si elle se trouve en haut d’une pile, mais pas dans la tâche cible, une nouvelle instance serait créé et mis sur la pile.

7
elevine

Peut-être que c'est ce numéro ? Ou une autre forme du même bug?

4
DuneCat

Je pense que la réponse acceptée ( Duane Homick ) a des cas non traités:

Vous avez différents extras (et les doublons d'applications en conséquence):

  • lorsque vous lancez une application depuis Market ou par l'icône de l'écran d'accueil (qui est automatiquement placé par Market)
  • lorsque vous lancez une application par un lanceur ou une icône d’écran d’accueil créée manuellement

Voici une solution (SDK_INT> = 11 pour les notifications) qui, à mon avis, permet également de gérer ces cas et les notifications de la barre d'état.

manifeste :

    <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" />

Activité du lanceur :

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);
2
StanislavKo

Je me rends compte que la question n’a rien à voir avec Xamarin Android mais je voulais poster quelque chose car je ne l’ai vue nulle part ailleurs.

Pour résoudre ce problème dans Xamarin Android, j’ai utilisé le code de @DuaneHomick et l’a ajouté à MainActivity.OnCreate(). La différence avec Xamarin est qu’il faut aller après Xamarin.Forms.Forms.Init(this, bundle); et LoadApplication(new App());. Ainsi, ma OnCreate() ressemblerait à ceci:

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: Depuis Android 6.0, la solution ci-dessus ne suffit pas dans certaines situations. J'ai également paramétré LaunchMode sur SingleTask, ce qui semble avoir rendu les choses fonctionnent correctement une fois de plus. Je ne suis pas sûr des effets que cela pourrait avoir sur d’autres choses, malheureusement.

2
hvaughan3

Essayez cette solution:
Créez la classe Application et définissez-la ici:

public static boolean IS_APP_RUNNING = false;

Ensuite, dans votre première activité (lanceur) dans onCreate avant setContentView(...), ajoutez ceci:

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

P.S. Controlleris ma classe Application.

0
Volodymyr Kulyk

J'ai aussi eu ce problème

  1. N'appelez pas finish (); dans l'activité à la maison, elle fonctionnerait indéfiniment - l'activité à la maison est appelée par ActivityManager à la fin.
  2. Généralement, lorsque la configuration change (c.-à-d. Rotation de l'écran, modification de la langue, modification du service de téléphonie, c.-à-d. Mcc mnc, etc.), l'activité est recréée - et si l'activité de rattachement est en cours d'exécution, elle appelle à nouveau A. si besoin d'être ajouté au manifeste Android:configChanges="mcc|mnc" - si vous avez une connexion cellulaire, voir http://developer.Android.com/guide/topics/manifest/activity-element.html#config pour quelle configuration il y a lors du démarrage le système ou Push ouvert ou autre.
0
user1249350