web-dev-qa-db-fra.com

À la déconnexion, effacez la pile de l'historique des activités, en empêchant le bouton "Précédent" d'ouvrir des activités connectées uniquement.

Toutes les activités de mon application nécessitent qu'un utilisateur soit connecté pour pouvoir le consulter. Les utilisateurs peuvent se déconnecter de presque toutes les activités. Ceci est une exigence de l'application. À tout moment, si l'utilisateur se déconnecte, je souhaite l'envoyer à Login Activity. À ce stade, je souhaite que cette activité figure au bas de la pile d'historique, de sorte que l'utilisateur puisse cliquer sur le bouton "Précédent" pour revenir à l'écran d'accueil d'Android.

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

J'ai essayé d'ouvrir l'activité de connexion en définissant ses indicateurs Intent sur FLAG_ACTIVITY_CLEAR_TOP, ce qui semble être conforme à la description de la documentation, mais n'atteint pas mon objectif de placer l'activité de connexion au bas de la fenêtre. pile d’historique, et empêche l’utilisateur de revenir aux activités précédemment connectées. J'ai aussi essayé d'utiliser Android:launchMode="singleTop" pour l'activité de connexion dans le manifeste, mais cela n'atteint pas mon objectif (et semble n'avoir aucun effet de toute façon).

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

L'une des options consiste à vérifier le statut de connexion de chaque activité onCreate et à finish() si ce n'est pas connecté. Je n’apprécie pas cette option, car le bouton Précédent sera toujours disponible, vous permettant de revenir lorsque les activités se ferment.

L'option suivante consiste à maintenir une LinkedList de références à toutes les activités ouvertes accessible de manière statique de partout (en utilisant éventuellement des références faibles). À la déconnexion, je vais accéder à cette liste et parcourir toutes les activités précédemment ouvertes en appelant finish() pour chacune d’elles. Je vais probablement commencer à implémenter cette méthode bientôt.

Cependant, je préférerais utiliser une ruse de drapeau Intent pour accomplir cela. Je serais bien au-delà de constater que je peux répondre aux exigences de mon application sans avoir à utiliser l'une des deux méthodes que j'ai décrites ci-dessus.

Existe-t-il un moyen d'y parvenir en utilisant Intent ou les paramètres de manifeste, ou ma deuxième option consiste-t-elle à conserver une LinkedList des activités ouvertes comme la meilleure option? Ou y a-t-il une autre option que je néglige complètement?

227
skyler

Je peux vous suggérer une autre approche IMHO plus robuste. Fondamentalement, vous devez diffuser un message de déconnexion à toutes vos activités devant rester sous un statut connecté. Ainsi, vous pouvez utiliser le sendBroadcast et installer un BroadcastReceiver dans 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 destinataire (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 **//
}
207

Cela semble être un rite de passage qu'un nouveau programmeur Android consacre une journée à la recherche de ce problème et à la lecture de tous ces fils de discussion StackOverflow. Je suis maintenant nouvellement initié et je laisse ici trace de mon humble expérience pour aider un futur pèlerin.

Premièrement, il n’existe aucun moyen évident ou immédiat de le faire selon mes recherches (as of September 2012). On pourrait penser que vous pourriez utiliser un 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 ceci forcera le framework à chercher dans la pile, à retrouver votre instance initiale de LoginActivity, à la recréer et à effacer le reste de la pile (ascendante). Et comme Login se trouve probablement au bas de la pile, vous avez maintenant une pile vide et le bouton Précédent quitte l'application.

MAIS - cela ne fonctionne que si vous avez précédemment laissé vivante cette instance originale de LoginActivity à la base de votre pile. Si, comme beaucoup de programmeurs, vous avez choisi de finish() cette LoginActivity une fois que l'utilisateur s'est connecté, il n'est plus sur la base de la pile et la sémantique FLAG_ACTIVITY_CLEAR_TOP ne s'applique pas. .. vous créez un nouveau LoginActivity par-dessus la pile existante. Ce qui n’est certainement pas ce que vous voulez (comportement étrange dans lequel l’utilisateur peut "revenir" pour sortir de sa connexion à un écran précédent).

Ainsi, si vous avez précédemment finish() 'd LoginActivity, vous devez utiliser un mécanisme permettant de vider votre pile, puis de lancer un nouveau LoginActivity. Il semble que la réponse de @doreamon dans ce fil soit la meilleure solution (du moins à mon humble œil):

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

Je suspecte fortement que les implications délicates de savoir si vous laissez ou non ActiveActivity causent beaucoup de confusion.

Bonne chance.

147
Mike Repass

UPDATE

la méthode super finishAffinity() 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));

REPONSE ORIGINALE

Supposons que LoginActivity -> HomeActivity -> ... -> SettingsActivity call signOut ():

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 également. :)

111
thanhbinh84

Si vous utilisez l’API 11 ou une version plus récente, vous pouvez essayer ceci: FLAG_ACTIVITY_CLEAR_TASK - il semble que le problème que vous rencontrez se pose exactement. Il est évident que la foule antérieure à l’API 11 devrait utiliser une combinaison de vérification de toutes les activités, comme le suggère @doreamon, ou une autre supercherie.

(Remarque: pour utiliser ceci, 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();
_
69
xbakesx

J'ai également passé quelques heures là-dessus ... et je suis d'accord sur le fait que FLAG_ACTIVITY_CLEAR_TOP ressemble à ce que vous souhaitez: effacez la pile entière, à l'exception de l'activité en cours de lancement, de sorte que le bouton Précédent permet de quitter l'application. Cependant, comme Mike Repass 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 dans la pile de l'historique. Puis ajoutez l'indicateur FLAG_ACTIVITY_CLEAR_TOP.

Désormais, lorsque FLAG_ACTIVITY_CLEAR_TOP ira chercher la nouvelle activité dans la pile, elle sera là et sera extraite avant que tout le reste 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();
}
30
christinac

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

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

Utilisez ceci, cela 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);
3
Mohamed Ibrahim

La solution acceptée n'est pas correcte, cela pose des problèmes, car l'utilisation d'un récepteur de radiodiffusion n'est pas une bonne idée pour résoudre ce problème. Si votre activité a déjà appelé la méthode onDestroy (), vous ne recevrez pas de destinataire. 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;
}
3
Yekmer Simsek

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

Dans mon LoginActivity, après avoir traité avec succès une connexion, je commence la suivante différemment en fonction du 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 une 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 j'appuie sur le bouton Précédent de MainActivity, l’identifiant de connexion (LoginActivity) est immédiatement masqué. Sur Honeycomb et les versions ultérieures, je viens juste de terminer LoginActivity après le traitement d’un identifiant. Celui-ci est correctement recréé après le traitement d’une déconnexion.

1
seastland

Parfois, finish() ne fonctionne pas

J'ai résolu ce problème avec

finishAffinity()

1
AJay

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 le fichier Manifest.xml; Dites que vous souhaitez vous déconnecter de SettingsActivity, vous pouvez le faire comme suit:

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

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 qu'il 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 lancées précédemment 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 que l'utilisateur se soit connecté, afin qu'il ne puisse pas y revenir plus tard en appuyant sur le bouton Précédent. Au lieu de cela, l'utilisateur doit appuyer sur un bouton de déconnexion dans l'application pour se déconnecter correctement. Ce bouton de déconnexion implémenterait une intention simple, comme suit:

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

Toutes les suggestions sont les bienvenues.

1
Nil

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 ce code ci-dessus jusqu'à ce que toutes les activités soient terminées.

0
Mak

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);
0
Preetansh

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 l'indicateur = false; Lors de la déconnexion, cliquez sur l'événement et définissez l'indicateur sur true. Dans OnResume () de chaque activité, vérifiez si cet indicateur est défini sur true, puis appelez finish ().

Il fonctionne comme un charme :)

0
MrDumb

La solution fournie par @doreamon fonctionne très 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 central. par exemple. Dans un flux de A-> B-> C, naviguez comme suit: Login -> B -> C -> Appuyez sur le raccourci vers la maison. L'utilisation de FLAG_ACTIVITY_CLEAR_TOP n'efface que l'activité C, car le domicile (A) ne figure 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 vous appuyez sur home, vous devez supprimer toutes les activités de cette pile.

0
Surendra Kumar

Commencez votre activité avec StartActivityForResult et, lorsque vous vous déconnectez, définissez votre résultat et terminez votre activité en fonction de votre résultat.

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

}
0
Eby