Al logout, cancellare lo stack della cronologia delle attività, impedendo al pulsante "indietro" di aprire le attività di solo accesso


235

Tutte le attività nella mia applicazione richiedono che un utente abbia effettuato l'accesso per visualizzare. Gli utenti possono disconnettersi da quasi tutte le attività. Questo è un requisito dell'applicazione. In qualsiasi momento se l'utente si disconnette, desidero inviare l'utente al Login Activity. A questo punto desidero che questa attività si trovi nella parte inferiore dello stack della cronologia, in modo che premendo il pulsante "indietro" si riporti l'utente alla schermata principale di Android.

Ho visto questa domanda porre alcuni posti diversi, tutti hanno risposto con risposte simili (che traccerò qui), ma voglio metterlo qui per raccogliere feedback.

Ho provato ad aprire l'attività di accesso impostando i suoi Intentflag su ciò FLAG_ACTIVITY_CLEAR_TOPche sembra fare come indicato nella documentazione, ma non raggiungo il mio obiettivo di posizionare l'attività di accesso in fondo allo stack della cronologia e impedire all'utente di tornare indietro alle attività di accesso precedentemente visualizzate. Ho anche provato a utilizzare android:launchMode="singleTop"l'attività di accesso nel manifest, ma questo non raggiunge neanche il mio obiettivo (e sembra non avere alcun effetto comunque).

Credo di dover cancellare lo stack della cronologia o terminare tutte le attività precedentemente aperte.

Un'opzione è fare in modo che ogni attività onCreatecontrolli lo stato di accesso e, finish()se non è stato effettuato l'accesso. Questa opzione non mi piace, poiché il pulsante Indietro sarà ancora disponibile per l'uso, tornando indietro quando le attività si chiudono.

L'opzione successiva è quella di mantenere una serie LinkedListdi riferimenti a tutte le attività aperte che sono staticamente accessibili da qualsiasi luogo (magari utilizzando riferimenti deboli). Al logout accederò a questo elenco e passerò in rassegna tutte le attività precedentemente aperte, invocando finish()ognuna. Probabilmente inizierò presto a implementare questo metodo.

Preferirei usare qualche Intenttrucco con la bandiera per realizzare questo, comunque. Sarei felice di scoprire che posso soddisfare i requisiti della mia applicazione senza dover utilizzare nessuno dei due metodi che ho descritto sopra.

C'è un modo per ottenere questo risultato usando Intento manifest le impostazioni, o è la mia seconda opzione, mantenendo una LinkedListdelle attività aperte l'opzione migliore? O c'è un'altra opzione che sto completamente trascurando?

Risposte:


213

Posso suggerirti un altro approccio IMHO più robusto. Fondamentalmente è necessario trasmettere un messaggio di disconnessione a tutte le attività che devono rimanere in uno stato di accesso. Quindi puoi usare sendBroadcaste installare a BroadcastReceiverin tutte le tue attività. Qualcosa come questo:

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

Il destinatario (attività protetta):

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 **//
}

27
@Christopher, ogni attività si registra per la trasmissione quando viene creata. Quando passa in background (ovvero, una nuova attività arriva in cima allo stack), il suo onStop () verrà chiamato, ma può comunque ricevere trasmissioni. Devi solo assicurarti di chiamare unregisterReceiver () in onDestroy () anziché in onStop ().
Russell Davis,

9
Funziona se un'attività da qualche parte nello stack è stata chiusa dal sistema operativo per recuperare memoria? Vale a dire. il sistema lo considererà veramente finito dopo l'invio della trasmissione di cui sopra e non lo ricrea quando si preme il pulsante Indietro?
Jan Żankowski il

5
Anche se questa sembra una soluzione elegante, è importante notare che questa non è sincrona.
Che Jami,

34
Una buona soluzione, ma invece di utilizzare una registrazione del ricevitore Broadcast come descritto nel codice sopra, è necessario utilizzare LocalBroadcastManager.getInstance (this) .registerReceiver (...) e LocalBroadcastManager.getInstance (this) .unregisterReceiver (..) . In caso contrario, l'applicazione può ricevere intenti da qualsiasi altra applicazione (problemi di sicurezza)
Uku Loskit,

5
@Warlock Hai ragione. La trappola di questo approccio è quando un'attività nel back stack viene distrutta dal sistema (può accadere per vari motivi, come lo scenario di memoria insufficiente). In tal caso, l'istanza Activity non sarà presente per ricevere la trasmissione. Ma il sistema tornerà indietro attraverso quell'attività ricreandola. Questo può essere testato / riprodotto
attivando

151

Sembra un rito di passaggio che un nuovo programmatore Android trascorra una giornata alla ricerca di questo problema e alla lettura di tutti questi thread StackOverflow. Sono stato appena iniziato e lascio qui traccia della mia umile esperienza per aiutare un futuro pellegrino.

In primo luogo, non esiste un modo ovvio o immediato per farlo secondo la mia ricerca. (as of September 2012).Penseresti di poter fare semplice startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)ma no .

Si può fare startActivity(new Intent(this, LoginActivity.class))con FLAG_ACTIVITY_CLEAR_TOP- e questo farà sì che il quadro per la ricerca in fondo alla pila, trova le informazioni in precedenza nell'istanza originale di LoginActivity, ricrearlo e cancellare il resto dello stack (verso l'alto). E poiché il login è presumibilmente nella parte inferiore dello stack, ora hai uno stack vuoto e il pulsante Indietro esce dall'applicazione.

MA - funziona solo se in precedenza hai lasciato viva l'istanza originale di LoginActivity alla base del tuo stack. Se, come molti programmatori, lo hai scelto una volta finish()che LoginActivityl'utente ha effettuato correttamente l'accesso, non è più sulla base dello stack e la FLAG_ACTIVITY_CLEAR_TOPsemantica non si applica ... finisci per crearne uno nuovo LoginActivitysopra lo stack esistente. Che quasi sicuramente NON è quello che vuoi (strano comportamento in cui l'utente può "tornare" fuori dal login in una schermata precedente).

Quindi, se avete in precedenza finish()'d la LoginActivity, è necessario perseguire un meccanismo per svuotare la pila e quindi di iniziare una nuova LoginActivity. Sembra che la risposta di @doreamonquesto thread sia la soluzione migliore (almeno per il mio umile occhio):

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

Sospetto fortemente che le complicate implicazioni del fatto che si lasci vivo LoginActivity stiano causando molta confusione.

In bocca al lupo.


5
Buona risposta. Il trucco FLAG_ACTIVITY_CLEAR_TOP, che la maggior parte delle persone consiglia di utilizzare, non funziona se hai terminato LoginActivity.
Konsumierer,

Grazie per la risposta. Un altro scenario è come quando c'è una sessione (ad es. Come fb) anche se non chiamiamo attività di accesso quindi non c'è alcun punto di attività di accesso nello stack. Quindi l'approccio sopra menzionato è inutile.
Prashanth Debbadwar,

118

AGGIORNARE

il finishAffinity()metodo super aiuterà a ridurre il codice ma a ottenere lo stesso. Finirà l'attività corrente e tutte le attività nello stack, usare getActivity().finishAffinity()se ci si trova in un frammento.

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

RISPOSTA ORIGINALE

Supponiamo che 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();
}

HomeActivity:

@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();
}

Questo funziona per me, spero che sia utile anche per te. :)


1
Penso che la tua soluzione presupponga che l'utente faccia clic su Esci e torni a una sola attività (HomeActivity). E se hai 10 attività in pila?
IgorGanapolsky

11
Se hai 10 attività in cima a HomeActivity, il flag FLAG_ACTIVITY_CLEAR_TOP ti aiuterà a ripulirle tutte.
Thanhbinh84

1
Questo può funzionare solo se quando si avvia HomeActivity, si ottiene OnCreate of HomeActivity. Il semplice avvio dell'attività a casa non la ricrea necessariamente a meno che non sia già stata completata o distrutta. Se HomeActivity non deve essere ricreato, OnCreate non verrà chiamato e dopo aver effettuato il logout sarai seduto sulla tua attività a casa.
topwik

1
Questa è una possibile soluzione e implica una codifica molto minore di una funzione semplice (per l'utente) come il logout.
Sottomarino Sebastian

2
Il flag Intent.FLAG_ACTIVITY_CLEAR_TOP aiuta a ripulire tutte le attività tra cui HomeActivity, quindi il metodo onCreate () verrà chiamato quando si riavvia questa attività.
Thanhbinh84,

73

Se stai usando API 11 o successive puoi provare questo: FLAG_ACTIVITY_CLEAR_TASK- sembra che stia risolvendo esattamente il problema che stai riscontrando. Ovviamente la folla pre-API 11 avrebbe dovuto usare una combinazione di controllo di tutte le attività, come suggerisce @doreamon, o qualche altro trucco.

(Nota anche: per usare questo devi passare 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();

1
Funzionando come un incanto. Grazie mille! Mentre sviluppo su API 14 min, questa è l'unica cosa da implementare.
AndyB,

1
Penso che non abbiamo bisogno di FLAG_ACTIVITY_CLEAR_TOP quando si utilizza questa soluzione per LoginActivity.
Bharat Dodeja,

Penso che finish();farà semplicemente il lavoro per evitare di tornare indietro dopo il logout. Qual è la necessità di impostare flag?
Pankaj,

Questo crea un'eccezione La chiamata startActivity () dall'esterno di un contesto Activity richiede il flag FLAG_ACTIVITY_NEW_TASK
Aman

31

Ho trascorso alcune ore anche su questo ... e sono d'accordo sul fatto che FLAG_ACTIVITY_CLEAR_TOP suona come quello che vorresti: cancella l'intero stack, ad eccezione dell'attività avviata, quindi il pulsante Indietro esce dall'applicazione. Tuttavia, come menzionato da Mike Repass , FLAG_ACTIVITY_CLEAR_TOP funziona solo quando l'attività che stai lanciando è già nello stack; quando l'attività non c'è, la bandiera non fa nulla.

Cosa fare? Inserisci l'attività in fase di avvio nello stack con FLAG_ACTIVITY_NEW_TASK , che rende tale attività l'inizio di una nuova attività nello stack della cronologia. Quindi aggiungere il flag FLAG_ACTIVITY_CLEAR_TOP.

Ora, quando FLAG_ACTIVITY_CLEAR_TOP va a cercare la nuova attività nello stack, questa sarà lì e verrà tirata su prima che tutto il resto venga cancellato.

Ecco la mia funzione di logout; il parametro Visualizza è il pulsante a cui è collegata la funzione.

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

1
Non funzionerà su API <= 10 perché FLAG_ACTIVITY_CLEAR_TASKnon è stato ancora aggiunto
Youans

1
@christinac Stai parlando di FLAG_ACTIVITY_CLEAR_TOP e il tuo frammento di codice ha FLAG_ACTIVITY_CLEAR_TASK; quale è allora valido?
Marian Paździoch,

10

Molte risposte. Potrebbe essere questo anche aiutare-

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

Versione Kotlin

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

4

Usa questo dovrebbe esserti utile. Risposta xbakesx leggermente modificata.

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

4

La soluzione accettata non è corretta, ha problemi in quanto l'utilizzo di un ricevitore di trasmissione non è una buona idea per questo problema. Se la tua attività ha già chiamato il metodo onDestroy (), non otterrai il ricevitore. La soluzione migliore è avere un valore booleano nelle preferenze condivise e verificarlo nel metodo onCreate () della tua attività. Se non deve essere chiamato quando l'utente non ha effettuato l'accesso, termina l'attività. Ecco un codice di esempio per questo. Così semplice e funziona per ogni condizione.

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

1
Sono passati anni, ma credo di aver fatto entrambe le cose. Ogni attività ha esteso LoggedInActivity, che ha verificato lo stato di accesso dell'utente in onCreate (). LoggedInActivity ha anche ascoltato la trasmissione "utente disconnesso" e ha fatto tutto il necessario per rispondere a questo.
skyler,

2
molto probabilmente dovresti inserire checkAuthStatus nel onResume()metodo. Perché quando si preme il pulsante Indietro, è più probabile che l'attività venga ripresa anziché creata.
Maysi,

1
@maysi Grazie per il suggerimento, sì, anche in questo modo il pulsante Indietro funzionerà correttamente, ho aggiornato la voce
Yekmer Simsek,

4

A volte finish() non funziona

Ho risolto questo problema con

finishAffinity()


3

Suggerirei un approccio diverso a questa domanda. Forse non è il più efficiente, ma penso che sia il più semplice da applicare e richiede pochissimo codice. Scrivere il codice successivo nella prima attività (attività di accesso, nel mio caso) non consentirà all'utente di tornare alle attività avviate in precedenza dopo la disconnessione.

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

Suppongo che LoginActivity sia terminato subito dopo l'accesso dell'utente, in modo che non possa tornare ad esso in seguito premendo il pulsante Indietro. Invece, l'utente deve premere un pulsante di disconnessione all'interno dell'app per disconnettersi correttamente. Ciò che questo pulsante di disconnessione implementerebbe è un semplice intento come segue:

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

Tutti i suggerimenti sono benvenuti


2

La risposta selezionata è intelligente e complicata. Ecco come l'ho fatto:

LoginActivity è l'attività principale dell'attività, impostare android: noHistory = "true" su Manifest.xml; Supponi di voler uscire da SettingsActivity, puoi farlo come di seguito:

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

2

Ecco la soluzione che ho trovato nella mia app.

Nella mia LoginActivity, dopo aver elaborato correttamente un login, inizio il successivo in modo diverso a seconda del livello 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);
}

Quindi nel metodo onActivityForResult del mio LoginActivity:

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

Infine, dopo aver elaborato un logout in qualsiasi altra attività:

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

Quando su Gingerbread, lo rende così se premo il pulsante Indietro da MainActivity, LoginActivity viene immediatamente nascosto. Su Honeycomb e versioni successive, ho appena terminato LoginActivity dopo aver elaborato un login ed è stato ricreato correttamente dopo l'elaborazione di un logout.


Non funziona per me. Nel mio caso ho usato la frammentazione. Il metodo onactivityresult non viene chiamato.
Mohamed Ibrahim,

1

Questo ha funzionato per me:

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

Potresti per favore elaborare di più la tua risposta aggiungendo un po 'più di descrizione della soluzione che offri?
abarisone,

0

Inizia la tua attività con StartActivityForResult e mentre esegui il logout imposta il risultato e in base al risultato termina la tua attività

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

La soluzione fornita da @doreamon funziona perfettamente per tutti i casi tranne uno:

Se dopo l'accesso, l'utente della schermata di Killing Login è passato direttamente alla schermata centrale. ad es. In un flusso di A-> B-> C, navigare come: Login -> B -> C -> Premere il tasto di scelta rapida per tornare a casa. L'uso di FLAG_ACTIVITY_CLEAR_TOP cancella solo l'attività C, poiché Home (A) non è nella cronologia dello stack. Premendo Indietro su una schermata ci riporterà a B.

Per affrontare questo problema, possiamo tenere uno stack di attività (Arraylist) e quando viene premuto home, dobbiamo uccidere tutte le attività in questo stack.


0

È possibile gestendo un flag in SharedPreferences o in Attività applicazione.

All'avvio dell'app (nella schermata iniziale) impostare flag = false; All'evento Click Logout basta impostare il flag true e in OnResume () di ogni attività, verificare se il flag è true quindi chiamare finish ().

Esso funziona magicamente :)


0

facendo clic su Logout è possibile chiamare questo

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

onActivityResult () dell'attività precedente chiama nuovamente questo codice sopra finché non hai terminato tutte le attività.


1
Ciò significa che deve attraversare tutte le attività in modo lineare, invece di trasmettere il traguardo () tutto in una volta?
IgorGanapolsky

@Igor G. sì, è il modo alternativo di finire in sequenza
Mak

-1

Un'opzione è fare in modo che ogni attività onCreate controlli lo stato di accesso e finisca () se non è stato effettuato l'accesso. Questa opzione non mi piace, poiché il pulsante Indietro sarà ancora disponibile per l'uso, tornando indietro quando le attività si chiudono.

Quello che vuoi fare è chiamare logout () e finish () sui tuoi metodi onStop () o onPause (). Ciò costringerà Android a chiamare onCreate () quando l'attività viene ripristinata poiché non la avrà più nello stack delle sue attività. Quindi fai come dici, in onCreate () controlla lo stato di accesso e inoltra alla schermata di accesso se non hai effettuato l'accesso.

Un'altra cosa che potresti fare è controllare lo stato di accesso in onResume () e, se non hai effettuato l'accesso, finire () e avviare l'attività di accesso.


Preferirei non disconnettermi da ogni pausa o arresto di ogni attività. Inoltre, l'applicazione avvia il logout o il login, quindi non ho bisogno di verificare se il login è reale.
skyler,

@Ricardo: la tua soluzione non richiede un BroadcastReceiver?
IgorGanapolsky
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.