Lors de la déconnexion, effacez la pile de l'historique des activités, empêchant le bouton «retour» d'ouvrir les activités uniquement connectées

235

Toutes les activités de mon application nécessitent qu'un utilisateur soit connecté pour voir. Les utilisateurs peuvent se déconnecter de presque toutes les activités. C'est une exigence de l'application. À tout moment, si l'utilisateur se déconnecte, je veux envoyer l'utilisateur à la connexion Activity. À ce stade, je veux que cette activité se trouve au bas de la pile d'historique afin que le fait d'appuyer sur le bouton "retour" ramène l'utilisateur à l'écran d'accueil d'Android.

J'ai vu cette question posée à différents endroits, toutes ont répondu avec des réponses similaires (que je décris ici), mais je veux la poser ici pour recueillir des commentaires.

J'ai essayé d'ouvrir l'activité de connexion en définissant ses Intentindicateurs FLAG_ACTIVITY_CLEAR_TOPqui semblent faire comme indiqué dans la documentation, mais n'atteint pas mon objectif de placer l'activité de connexion au bas de la pile d'historique et d'empêcher l'utilisateur de revenir en arrière aux activités enregistrées précédemment. J'ai également essayé d'utiliser android:launchMode="singleTop"pour l'activité de connexion dans le manifeste, mais cela n'atteint pas mon objectif non plus (et semble n'avoir aucun effet de toute façon).

Je crois que je dois effacer la pile d'historique ou terminer toutes les activités précédemment ouvertes.

Une option consiste à demander à chaque activité de onCreatevérifier l'état de connexion et, finish()si elle n'est pas connectée. Je n'aime pas cette option, car le bouton de retour sera toujours disponible pour une utilisation, en revenant à la fermeture des activités.

L'option suivante consiste à conserver une LinkedListréférence à toutes les activités ouvertes qui est accessible statiquement de partout (peut-être à l'aide de références faibles). Lors de la déconnexion, j'accéderai à cette liste et parcourrai toutes les activités précédemment ouvertes, en invoquant finish()chacune d'entre elles. Je vais probablement commencer à implémenter cette méthode bientôt.

Je préfère cependant utiliser une Intentsupercherie de drapeau pour y parvenir. Je serais plus que ravi de constater que je peux répondre aux exigences de mon application sans avoir à utiliser l'une des deux méthodes décrites ci-dessus.

Existe-t-il un moyen d'y parvenir en utilisant Intentou en manifestant des paramètres, ou ma deuxième option, maintenir une LinkedListdes activités ouvertes est-elle la meilleure option? Ou existe-t-il une autre option que je néglige complètement?

skyler
la source

Réponses:

213

Je peux vous proposer une autre approche à mon humble avis plus robuste. Fondamentalement, vous devez diffuser un message de déconnexion à toutes vos activités devant rester sous un statut connecté. Vous pouvez donc utiliser le sendBroadcastet installer un BroadcastReceiverdans toutes vos activités. Quelque chose comme ça:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);

Le récepteur (activité sécurisée):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**snip **/
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.package.ACTION_LOGOUT");
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("onReceive","Logout in progress");
            //At this point you should start the login activity and finish this one
            finish();
        }
    }, intentFilter);
    //** snip **//
}
Francesco Laurita
la source
27
@Christopher, chaque activité s'inscrit pour la diffusion lors de sa création. Quand il passe en arrière-plan (c'est-à-dire qu'une nouvelle activité arrive en haut de la pile), son onStop () sera appelé, mais il peut toujours recevoir des diffusions. Vous devez juste vous assurer d'appeler unregisterReceiver () dans onDestroy () plutôt que dans onStop ().
Russell Davis
9
Est-ce que cela fonctionne si une activité quelque part dans la pile a été arrêtée par le système d'exploitation pour récupérer la mémoire? C'est à dire. le système considérera-t-il qu'il est vraiment terminé après l'envoi de la diffusion ci-dessus et ne le recréera pas en appuyant sur le bouton de retour?
Jan Żankowski
5
Bien que cela semble être une solution élégante, il est important de noter que ce n'est pas synchrone.
Che Jami
34
Une bonne solution, mais au lieu d'utiliser un enregistrement du récepteur de diffusion comme décrit dans le code ci-dessus, vous devez utiliser un LocalBroadcastManager.getInstance (this) .registerReceiver (...) et LocalBroadcastManager.getInstance (this) .unregisterReceiver (..) . Sinon, votre application peut recevoir des intentions de toute autre application (problème de sécurité)
Uku Loskit
5
@Warlock Vous avez raison. Le piège de cette approche est lorsqu'une activité dans la pile arrière est détruite par le système (cela peut se produire pour diverses raisons, telles que le scénario de faible mémoire noté). Dans ce cas, l'instance d'activité ne sera pas là pour recevoir la diffusion. Mais le système continuera à naviguer à travers cette activité en la recréant. Cela peut être testé / reproduit en activant le paramètre développeur "Ne pas garder les activités"
Eric Schlenz
151

Il semble qu'un rite de passage qu'un nouveau programmeur Android passe une journée à rechercher ce problème et à lire tous ces threads StackOverflow. Je suis maintenant nouvellement initiée et je laisse ici trace de mon humble expérience pour aider un futur pèlerin.

Tout d'abord, il n'y a pas de moyen évident ou immédiat de le faire selon mes recherches (as of September 2012).Vous penseriez que vous pourriez simple startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)mais non .

Vous POUVEZ faire startActivity(new Intent(this, LoginActivity.class))avec FLAG_ACTIVITY_CLEAR_TOP- et cela amènera le framework à rechercher dans la pile, à trouver votre instance initiale précédente de LoginActivity, à la recréer et à effacer le reste de la pile (vers le haut). Et puisque la connexion est probablement au bas de la pile, vous avez maintenant une pile vide et le bouton Retour quitte simplement l'application.

MAIS - cela ne fonctionne que si vous avez précédemment laissé cette instance originale de LoginActivity en vie à la base de votre pile. Si, comme de nombreux programmeurs, vous avez choisi finish()cela LoginActivityune fois que l'utilisateur s'est connecté avec succès, alors ce n'est plus sur la base de la pile et la FLAG_ACTIVITY_CLEAR_TOPsémantique ne s'applique pas ... vous finissez par en créer un nouveau LoginActivityau-dessus de la pile existante. Ce qui n'est presque certainement PAS ce que vous voulez (comportement étrange où l'utilisateur peut `` revenir '' en arrière de sa connexion à un écran précédent).

Donc , si vous avez déjà finish()« d la LoginActivity, vous devez poursuivre un mécanisme pour la suppression de votre pile, puis commencer un nouveau LoginActivity. Il semble que la réponse de @doreamonce fil soit la meilleure solution (au moins à mon humble œil):

https://stackoverflow.com/a/9580057/614880

Je soupçonne fortement que les implications délicates de savoir si vous laissez LoginActivity en vie causent beaucoup de confusion.

Bonne chance.

Mike Repass
la source
5
Bonne réponse. L'astuce FLAG_ACTIVITY_CLEAR_TOP, que la plupart des gens conseillent d'utiliser, ne fonctionne tout simplement pas si vous avez terminé la LoginActivity.
Konsumierer
Merci pour la réponse. Un autre scénario est comme quand il y a une session (par exemple comme fb) même si nous n'appelons pas d'activité de connexion, il n'y a donc aucun point d'activité de connexion dans la pile. Alors l'approche mentionnée ci-dessus est inutile.
Prashanth Debbadwar
118

METTRE À JOUR

la super finishAffinity()méthode aidera à réduire le code mais à atteindre le même résultat. Il terminera l'activité en cours ainsi que toutes les activités de la pile, utilisez getActivity().finishAffinity()si vous êtes dans un fragment.

finishAffinity(); 
startActivity(new Intent(mActivity, LoginActivity.class));

RÉPONSE ORIGINALE

Supposons que LoginActivity -> HomeActivity -> ... -> SettingsActivity indicatif d'appelOut ():

void signOut() {
    Intent intent = new Intent(this, HomeActivity.class);
    intent.putExtra("finish", true);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // To clean up all activities
    startActivity(intent);
    finish();
}

AccueilActivité:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    boolean finish = getIntent().getBooleanExtra("finish", false);
    if (finish) {
        startActivity(new Intent(mContext, LoginActivity.class));
        finish();
        return;
    }
    initializeView();
}

Cela fonctionne pour moi, j'espère que cela vous sera utile aussi. :)

thanhbinh84
la source
1
Je pense que votre solution suppose que l'utilisateur cliquera sur SignOut et reviendra à une seule activité (HomeActivity). Et si vous avez 10 activités sur la pile?
IgorGanapolsky
11
Si vous avez 10 activités au-dessus de HomeActivity, l'indicateur FLAG_ACTIVITY_CLEAR_TOP vous aidera à toutes les nettoyer.
thanhbinh84
1
Cela ne peut fonctionner que si au démarrage de HomeActivity, vous obtenez votre OnCreate of HomeActivity. Le simple fait de commencer l'activité à la maison ne la recréera pas nécessairement à moins qu'elle ne soit déjà terminée ou détruite. Si HomeActivity n'a pas besoin d'être recréé, OnCreate ne sera pas appelé et après votre déconnexion, vous serez assis sur votre activité à domicile.
topwik
1
C'est une solution possible et qui implique beaucoup moins de codage d'une fonctionnalité simple (pour l'utilisateur) telle que la déconnexion.
Subin Sebastian
2
L'indicateur Intent.FLAG_ACTIVITY_CLEAR_TOP aide à nettoyer toutes les activités, y compris HomeActivity, ainsi la méthode onCreate () sera appelée lorsque vous redémarrerez cette activité.
thanhbinh84
73

Si vous utilisez l'API 11 ou une version ultérieure, vous pouvez essayer ceci: - cela FLAG_ACTIVITY_CLEAR_TASKsemble résoudre exactement le problème que vous rencontrez. Évidemment, la foule pré-API 11 devrait utiliser une combinaison de vérification de toutes les activités, comme le suggère @doreamon, ou une autre ruse.

(Notez également: pour l'utiliser, vous devez passer FLAG_ACTIVITY_NEW_TASK)

Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("finish", true); // if you are checking for this in your other Activities
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 
                Intent.FLAG_ACTIVITY_CLEAR_TASK |
                Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
xbakesx
la source
1
Travailler comme un charme. Merci beaucoup! Comme je développe sur min API 14, c'est la seule chose à implémenter.
AndyB
1
Je pense que nous n'avons pas besoin de FLAG_ACTIVITY_CLEAR_TOP lors de l'utilisation de cette solution pour LoginActivity.
Bharat Dodeja
Je pense que finish();fera juste le travail pour éviter de revenir en arrière après la déconnexion. Quel est le besoin de définir des drapeaux?
Pankaj
Cela crée une exception. L'appel de startActivity () à l'extérieur d'un contexte d'activité nécessite l'indicateur FLAG_ACTIVITY_NEW_TASK
Aman
31

J'ai aussi passé quelques heures là-dessus ... et je suis d'accord pour dire que FLAG_ACTIVITY_CLEAR_TOP ressemble à ce que vous voudriez: effacer toute la pile, à l'exception de l'activité en cours de lancement, donc le bouton Retour quitte l'application. Pourtant, comme Mike Repass l'a mentionné, FLAG_ACTIVITY_CLEAR_TOP ne fonctionne que lorsque l'activité que vous lancez est déjà dans la pile; quand l'activité n'est pas là, le drapeau ne fait rien.

Que faire? Placez l'activité en cours de lancement dans la pile avec FLAG_ACTIVITY_NEW_TASK , ce qui fait de cette activité le début d'une nouvelle tâche sur la pile d'historique. Ajoutez ensuite l'indicateur FLAG_ACTIVITY_CLEAR_TOP.

Maintenant, lorsque FLAG_ACTIVITY_CLEAR_TOP va chercher la nouvelle activité dans la pile, elle sera là et sera relevée avant que tout le reste ne soit effacé.

Voici ma fonction de déconnexion; le paramètre View est le bouton auquel la fonction est attachée.

public void onLogoutClick(final View view) {
    Intent i = new Intent(this, Splash.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(i);
    finish();
}
christinac
la source
1
Ne fonctionnera pas sur l'API <= 10 car il FLAG_ACTIVITY_CLEAR_TASKn'a pas encore été ajouté
Youans
1
@christinac Vous parlez de FLAG_ACTIVITY_CLEAR_TOP et votre extrait de code a FLAG_ACTIVITY_CLEAR_TASK; qui est valable alors?
Marian Paździoch
10

Beaucoup de réponses. Peut-être que celui-ci aidera également

Intent intent = new Intent(activity, SignInActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
this.finish();

Version Kotlin-

Intent(this, SignInActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also { startActivity(it) }
finish()
Gulshan
la source
4

Utilisez-le, il devrait vous être utile. Réponse xbakesx légèrement modifiée.

Intent intent = new Intent(this, LoginActivity.class);
if(Build.VERSION.SDK_INT >= 11) {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
startActivity(intent);
Mohamed Ibrahim
la source
4

La solution acceptée n'est pas correcte, elle a des problèmes car l'utilisation d'un récepteur de diffusion n'est pas une bonne idée pour ce problème. Si votre activité a déjà appelé la méthode onDestroy (), vous n'aurez pas de récepteur. La meilleure solution consiste à avoir une valeur booléenne sur vos préférences partagées et à la vérifier dans la méthode onCreate () de votre activité. S'il ne doit pas être appelé lorsque l'utilisateur n'est pas connecté, terminez l'activité. Voici un exemple de code pour cela. Si simple et fonctionne pour toutes les conditions.

protected void onResume() {
  super.onResume();
  if (isAuthRequired()) {
    checkAuthStatus();
  }
}

private void checkAuthStatus() {
  //check your shared pref value for login in this method
  if (checkIfSharedPrefLoginValueIsTrue()) {
    finish();
  }
}

boolean isAuthRequired() {
  return true;
}
Yekmer Simsek
la source
1
Cela fait des années, mais je crois que j'ai fait les deux. Chaque activité a étendu LoggedInActivity, qui a vérifié l'état de connexion de l'utilisateur dans onCreate (). LoggedInActivity a également écouté la diffusion "déconnexion de l'utilisateur" et a fait tout ce qu'il fallait pour y répondre.
skyler
2
plus probablement, vous devriez mettre le checkAuthStatus dans la onResume()méthode. Parce que lorsque vous appuyez sur le bouton de retour, l'activité est plus susceptible de reprendre au lieu d'être créée.
maysi
1
@maysi Merci pour la suggestion, oui, ce bouton de retour fonctionnera aussi correctement, j'ai mis à jour l'entrée
Yekmer Simsek
4

Parfois finish()ne fonctionne pas

J'ai résolu ce problème avec

finishAffinity()

AJay
la source
3

Je suggérerais une approche différente de cette question. Ce n'est peut-être pas le plus efficace, mais je pense que c'est le plus facile à appliquer et nécessite très peu de code. L'écriture du code suivant dans votre première activité (activité de connexion, dans mon cas) ne permettra pas à l'utilisateur de revenir aux activités précédemment lancées après la déconnexion.

@Override
public void onBackPressed() {
    // disable going back to the MainActivity
    moveTaskToBack(true);
}

Je suppose que LoginActivity est terminé juste après la connexion de l'utilisateur, de sorte qu'il ne peut pas y revenir plus tard en appuyant sur le bouton de retour. À la place, l'utilisateur doit appuyer sur un bouton de déconnexion dans l'application pour se déconnecter correctement. Ce que ce bouton de déconnexion implémenterait est une intention simple comme suit:

Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();

Toutes les suggestions sont les bienvenues.

Néant
la source
2

La réponse choisie est intelligente et délicate. Voici comment je l'ai fait:

LoginActivity est l'activité racine de la tâche, définissez android: noHistory = "true" dans Manifest.xml; Supposons que vous souhaitiez vous déconnecter de SettingsActivity, vous pouvez le faire comme ci-dessous:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);
t-gao
la source
2

Voici la solution que j'ai trouvée dans mon application.

Dans mon LoginActivity, après avoir traité avec succès une connexion, je démarre la suivante différemment selon le niveau de l'API.

Intent i = new Intent(this, MainActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    startActivity(i);
    finish();
} else {
    startActivityForResult(i, REQUEST_LOGIN_GINGERBREAD);
}

Ensuite, dans la méthode onActivityForResult de mon LoginActivity:

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
        requestCode == REQUEST_LOGIN_GINGERBREAD &&
        resultCode == Activity.RESULT_CANCELED) {
    moveTaskToBack(true);
}

Enfin, après avoir traité une déconnexion dans toute autre activité:

Intent i = new Intent(this, LoginActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);

Lorsque sur Gingerbread, le fait si j'appuie sur le bouton de retour de MainActivity, la LoginActivity est immédiatement masquée. Sur Honeycomb et versions ultérieures, je viens de terminer la LoginActivity après avoir traité une connexion et elle est correctement recréée après avoir traité une déconnexion.

Seastland
la source
Ça ne fonctionne pas pour moi. Dans mon cas, j'ai utilisé la fragmentactivité. La méthode de résultat d'activité n'est pas appelée.
Mohamed Ibrahim
1

Cela a fonctionné pour moi:

     // After logout redirect user to Loing Activity
    Intent i = new Intent(_context, MainActivity.class);
    // Closing all the Activities
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

    // Add new Flag to start new Activity
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    // Staring Login Activity
    _context.startActivity(i);
Preetansh
la source
Pourriez-vous élaborer davantage votre réponse en ajoutant un peu plus de description sur la solution que vous proposez?
abarisone
0

Commencez votre activité avec StartActivityForResult et pendant que vous vous déconnectez, définissez votre résultat et selon votre résultat, terminez votre activité

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivityForResult(intent, BACK_SCREEN);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case BACK_SCREEN:
        if (resultCode == REFRESH) {
            setResult(REFRESH);
            finish();
        }
        break;
    }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        AlertDialog alertDialog = builder.create();

        alertDialog
                .setTitle((String) getResources().getText(R.string.home));
        alertDialog.setMessage((String) getResources().getText(
                R.string.gotoHome));
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {

                        setResult(REFRESH);
                        finish();
                    }

                });

        alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {
                    }
                });
        alertDialog.show();
        return true;
    } else
        return super.onKeyDown(keyCode, event);

}
Eby
la source
0

La solution fournie par @doreamon fonctionne bien pour tous les cas sauf un:

Si Après la connexion, l'utilisateur de l'écran Killing Login a accédé directement à un écran du milieu. Par exemple, dans un flux de A-> B-> C, naviguez comme: Connexion -> B -> C -> Appuyez sur le raccourci vers la page d'accueil. L'utilisation de FLAG_ACTIVITY_CLEAR_TOP efface uniquement l'activité C, car la maison (A) n'est pas dans l'historique de la pile. Appuyer sur Retour sur un écran nous ramènera à B.

Pour résoudre ce problème, nous pouvons conserver une pile d'activités (Arraylist) et lorsque la maison est pressée, nous devons tuer toutes les activités de cette pile.

Surendra Kumar
la source
0

Cela est possible en gérant un indicateur dans SharedPreferences ou dans Application Activity.

Au démarrage de l'application (sur l'écran de démarrage), définissez le drapeau = false; Lors de l'événement Click de déconnexion, définissez simplement le drapeau sur true et dans OnResume () de chaque activité, vérifiez si le drapeau est vrai, puis appelez finish ().

Il fonctionne comme un charme :)

MrDumb
la source
0

en cliquant sur Déconnexion, vous pouvez appeler cela

private void GoToPreviousActivity() {
    setResult(REQUEST_CODE_LOGOUT);
    this.finish();
}

onActivityResult () de l'activité précédente, appelez à nouveau ce code ci-dessus jusqu'à ce que vous ayez terminé toutes les activités.

Mak
la source
1
Cela signifie-t-il qu'il doit parcourir toutes les activités de manière linéaire, au lieu de diffuser le finish () d'un coup?
IgorGanapolsky
@Igor G. oui, c'est la façon alternative de finir de manière séquentielle
Mak
-1

Une option consiste à demander à chaque activité onCreate de vérifier l'état de connexion et de terminer () si elle n'est pas connectée. Je n'aime pas cette option, car le bouton de retour sera toujours disponible pour une utilisation, en revenant à la fermeture des activités.

Ce que vous voulez faire, c'est appeler logout () et finish () sur vos méthodes onStop () ou onPause (). Cela forcera Android à appeler onCreate () lorsque l'activité sera réactivée car il ne l'aura plus dans sa pile d'activité. Faites ensuite ce que vous dites, dans onCreate (), vérifiez l'état de connexion et transférez-le à l'écran de connexion s'il n'est pas connecté.

Une autre chose que vous pouvez faire est de vérifier l'état de connexion dans onResume () et, si vous n'êtes pas connecté, de terminer () et de lancer l'activité de connexion.

Ricardo Villamil
la source
Je préfère ne pas me déconnecter à chaque pause ou arrêt d'activité. De plus, l'application lance la déconnexion ou la connexion, donc je n'ai pas vraiment besoin de vérifier si je suis connecté.
skyler
@Ricardo: votre solution ne nécessite-t-elle pas un BroadcastReceiver?
IgorGanapolsky