Wyczyść cały stos historii i rozpocznij nową aktywność na Androidzie

332

Czy można rozpocząć działanie na stosie, czyszcząc całą historię przed nim?

Sytuacja

Mam stos aktywności, który albo A-> B-> C lub B-> C (ekran A wybiera token użytkowników, ale wielu użytkowników ma tylko jeden token).

Na ekranie C użytkownik może podjąć działanie, które powoduje, że ekran B jest nieważny, więc aplikacja chce przenieść go na ekran A, niezależnie od tego, czy znajduje się już na stosie. Ekran A powinien być zatem jedynym elementem na stosie w mojej aplikacji.

Notatki

Jest wiele innych podobnych pytań, ale nie znalazłem niczego, co odpowiadałoby dokładnie na to pytanie. Próbowałem zadzwonić getParent().finish()- zawsze powoduje to wyjątek wskaźnika zerowego. FLAG_ACTIVITY_CLEAR_TOPdziała tylko wtedy, gdy aktywność jest już na stosie.

Casebash
źródło

Odpowiedzi:

658

Na poziomie API 11 dodano nową flagę intencji właśnie w tym celu: Intent.FLAG_ACTIVITY_CLEAR_TASK

Aby to wyjaśnić, użyj tego:

Jawa

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);


Kotlin

intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK


Niestety dla API lvl <= 10, nie znalazłem jeszcze czystego rozwiązania tego problemu. Rozwiązanie „DontHackAndroidLikeThis” to rzeczywiście czysta hackery. Nie powinieneś tego robić. :)

Edycja: Zgodnie z komentarzem @ Ben Pearson , dla API <= 10 można teraz używać klasy IntentCompat do tego samego. Można użyć IntentCompat.FLAG_ACTIVITY_CLEAR_TASKflagi do wyczyszczenia zadania. Możesz więc obsługiwać także 11-letni interfejs API.

Akos Cz
źródło
23
Aby to wyjaśnić, użyj tego: intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
user123321,
2
bez zamiaru.FLAG_ACTIVITY_NEW_TASK aplikacja czasami po prostu zamyka się na
Androidzie
22
IntentCompat ma również flagę do wyczyszczenia zadania, więc możesz obsługiwać pre API 11 poziomu - developer.android.com/reference/android/support/v4/content/…
Ben Pearson
10
IntentCompat.FLAG_ACTIVITY_CLEAR_TASK jest ignorowany na urządzeniach z poziomem API <10. developer.android.com/reference/android/support/v4/content/…
David
7
Flaga IntentCompat ma jedynie na celu uniknięcie awarii, ale nie robi nic, jak mówi @David.
Sloy
49

Przypadek 1: Tylko dwie czynności A i B:

Tutaj przepływ działań to A-> B.Kliknięcie przycisku B z B musimy zamknąć aplikację, a następnie uruchamiając działanie B z A po prostu wywołaj polecenie zakończenia (), co uniemożliwi Androidowi zapisywanie działania A w Backstack.eg dla działania A jest Loding / Splash ekran aplikacji.

Intent newIntent = new Intent(A.this, B.class);
startActivity(newIntent);
finish();

Przypadek 2: Więcej niż dwa działania:

Jeśli istnieje przepływ taki jak A-> B-> C-> D-> B i po kliknięciu przycisku Wstecz w działaniu B, gdy pochodzi on z działania D. W takim przypadku powinniśmy użyć.

Intent newIntent = new Intent(D.this,B.class);
newIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(newIntent);

Tutaj działanie B zostanie uruchomione z backstacka, a nie z nowej instancji z powodu Intent.FLAG_ACTIVITY_CLEAR_TOP i Intent.FLAG_ACTIVITY_NEW_TASK usuwa stos i czyni go najwyższym, więc po naciśnięciu przycisku Wstecz cała aplikacja zostanie zakończona.

Monish George
źródło
2
To zadziałało dla mnie. Włączyłem WSZYSTKIE działania do tych flag. W tych działaniach przyciski powrotu działają idealnie, przechodząc do poprzedniej czynności, aw głównej aktywności z intencją intent = nowa intencja (Intent.ACTION_MAIN); intent.addCategory (Intent.CATEGORY_HOME); intent.addFlags (Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK); startActivity (cel); koniec(); Cała aplikacja jest zamknięta, wciąż w pamięci, ale nie jest aktywna, a jeśli uruchomisz ponownie, aplikacja przejdzie do ekranu powitalnego :)
Rako
To powinna być najlepsza odpowiedź. Jeśli ktoś ma ze mną taki sam scenariusz: A-> B-> C-> D-> E -> (B) Od E-> B powinien dać wynik: A-> B
Shem Alexis Chavez
39

Z nowszą wersją Androida> = użyj interfejsu API 16 finishAffinity()

podejście jest odpowiednie dla> = API 16.

Intent mIntent = new Intent(mContext,MainActivity.class);
finishAffinity();
startActivity(mIntent);
  • Jest to to samo, co rozpoczęcie nowej aktywności i wyczyszczenie całego stosu.
  • LUB Uruchom ponownie do MainActivity / FirstActivity.
karan
źródło
1
To załatwiło sprawę, flagi nie działały dla mnie na 4.xx i to działało idealnie! Dzięki
Jonathan Aste,
1
To wydaje się być prawidłową odpowiedzią, jeśli Twoim celem jest zakończenie wszystkich działań poniżej i włączenie bieżącej aktywności i rozpoczęcie nowej aktywności w ich własnym zadaniu.
ToBe
24

Spędziłem nad tym także kilka godzin ... i zgadzam się, że FLAG_ACTIVITY_CLEAR_TOP brzmi tak, jak chcesz: wyczyść cały stos, z wyjątkiem uruchomionej aktywności, więc przycisk Wstecz wychodzi z aplikacji. Jednak, jak wspomniał Mike Repass, FLAG_ACTIVITY_CLEAR_TOP działa tylko wtedy, gdy aktywność, którą uruchamiasz, znajduje się już na stosie; gdy działania nie ma, flaga nic nie robi.

Co robić? Umieść uruchamiane działanie w stosie za pomocą FLAG_ACTIVITY_NEW_TASK, co powoduje, że działanie to rozpoczyna nowe zadanie na stosie historii. Następnie dodaj flagę FLAG_ACTIVITY_CLEAR_TOP.

Teraz, gdy FLAG_ACTIVITY_CLEAR_TOP pójdzie znaleźć nową aktywność na stosie, będzie tam i zostanie podniesiona, zanim wszystko inne zostanie wyczyszczone.

Oto moja funkcja wylogowania; parametr View to przycisk, do którego dołączona jest funkcja.

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();
}
użytkownik 2895402
źródło
1
masz na myśli CLEAR_TASK zamiast CLEAR_TOP?
Andy,
14

Nie powinieneś zmieniać stosu. Przycisk Wstecz Androida powinien działać jak w przeglądarce internetowej.

Mogę wymyślić sposób, aby to zrobić, ale to dość hack.

  • Wykonaj swoje działania singleTask, dodając je do AndroidManifest przykładu:

    <activity android:name=".activities.A"
              android:label="@string/A_title"
              android:launchMode="singleTask"/>
    
    <activity android:name=".activities.B"
              android:label="@string/B_title"
              android:launchMode="singleTask"/>
  • Rozszerz, Applicationktóra będzie zawierać logikę, gdzie iść.

Przykład:

public class DontHackAndroidLikeThis extends Application {

  private Stack<Activity> classes = new Stack<Activity>();

  public Activity getBackActivity() {
    return classes.pop();
  }

  public void addBackActivity(Activity activity) {
    classes.push(activity);
  }
}

Od A do B:

DontHackAndroidLikeThis app = (DontHackAndroidLikeThis) getApplication();
app.addBackActivity(A.class); 
startActivity(this, B.class);

Od B do C:

DontHackAndroidLikeThis app = (DontHackAndroidLikeThis) getApplication();
app.addBackActivity(B.class); 
startActivity(this, C.class);

W C:

If ( shouldNotGoBackToB() ) {
  DontHackAndroidLikeThis app = (DontHackAndroidLikeThis) getApplication();
  app.pop();
}

i przytrzymaj przycisk powrotu do pop()stosu.

Jeszcze raz nie powinieneś tego robić :)

Makaron
źródło
W końcu postanawiam pozostawić stos nienaruszony i po prostu powiedzieć użytkownikowi, że jego obecny ekran jest nieprawidłowy
Casebash
1
Bardzo frustrujące jest to, że Android nie pozwala nam już zarządzać stosami aktywności w ten sposób. Kusiłoby mnie, aby użyć tego rozwiązania w moich przyszłych aplikacjach na Androida.
Cefron
4
Żeby było jasne, dlaczego nie należy tego używać: to dobry sposób na tworzenie wycieków pamięci. W pewnym momencie system operacyjny może zdecydować o zabiciu działań w tle, ale od momentu Applicationwystąpienia ich system operacyjny nie będzie w stanie zwolnić pamięci RAM pozostałej po zniszczonych działaniach.
Vit Khudenko,
@Arhimed Czy są jeszcze inne problemy? Wyciek pamięci można naprawić, utrzymując tylko słabe referencje.
Navin
1
@Navin tak, przecieków można uniknąć przy słabych referencjach, ale jeśli po GC nie będzie referencji na żywo, całe podejście jest bezużyteczne. Jeszcze raz - nie rób tego, to jest złe podejście do Androida.
Vit Khudenko
12

Natychmiast po rozpoczęciu nowej aktywności za pomocą startActivityupewnij się, że dzwonisz, finish()aby bieżąca aktywność nie została ułożona za nową.

Keith Maurino
źródło
+1 Dobre rozwiązanie, aby zapobiec umieszczeniu dokładnie jednego działania w danej sytuacji na stosie historii.
marsbear
27
nie działa, jeśli masz więcej niż jedną działalność w stosie wykończenie będzie po prostu usunąć poprzednią działalność, ale nie innych ....
Necronet
5

Spróbuj tego:

Intent logout_intent = new Intent(DashboardActivity.this, LoginActivity.class);
logout_intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
logout_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
logout_intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(logout_intent);
finish();
Mohammad
źródło
4

Zaawansowane wielokrotnego użytku Kotlin:

Możesz ustawić flagę bezpośrednio za pomocą metody setter. W Kotlin orjest zamiennikiem Java bitowej lub |.

intent.flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK

Jeśli planujesz używać tego regularnie, utwórz funkcję rozszerzenia Intent

fun Intent.clearStack() {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}

Następnie możesz bezpośrednio wywołać tę funkcję przed rozpoczęciem zamiaru

intent.clearStack()

Jeśli potrzebujesz opcji dodania dodatkowych flag w innych sytuacjach, dodaj opcjonalny parametr do funkcji rozszerzenia.

fun Intent.clearStack(additionalFlags: Int = 0) {
    flags = additionalFlags or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
Gibolt
źródło
2
Intent i = new Intent(MainPoliticalLogin.this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
Neeraj Gupta
źródło
2

Wypróbuj poniższy kod,

Intent intent = new Intent(ManageProfileActivity.this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|
                Intent.FLAG_ACTIVITY_CLEAR_TASK| 
                Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
shashikant yadav
źródło
jeśli korzystam z tej aktywności, to ponownie zaktualizowałem api, ale wcześniej istniejące wszystkie statck zostały wyczyszczone
Harsha
2

Dla mnie żadna z powyższych metod nie działa.

Po prostu zrób to, aby wyczyścić wszystkie poprzednie działania :

finishAffinity() // if you are in fragment use activity.finishAffinity()
Intent intent = new Intent(this, DestActivity.class); // with all flags you want
startActivity(intent)
Amir Hossein Ghasemi
źródło
-1

Czasami emulator Androida może nie połączyć się z narzędziem DDMS zaćmienia i poprosić o ręczne uruchomienie adb. W takim przypadku możesz uruchomić lub zatrzymać adb za pomocą wiersza polecenia.

RajeshkumarG
źródło
1
Czasami emulator Androida może nie połączyć się z narzędziem DDMS zaćmienia i poprosić o ręczne uruchomienie adb. W takim przypadku możesz uruchomić lub zatrzymać adb za pomocą wiersza polecenia. Intent i = new Intent (OldActivity.this, NewActivity.class); // ustaw nowe zadanie i wyczyść flagi i.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) startActivity (i);
RajeshkumarG
-2

Znalazłem zbyt prosty hack, po prostu dodaj ten nowy element AndroidManifestjako:

<activity android:name=".activityName"
          android:label="@string/app_name"
          android:noHistory="true"/>

android:noHistoryskasuje niechcianych aktywności ze stosu.

Tauseef
źródło
2
To podejście może powodować problemy w Androidzie 6.0+, jeśli poprosisz o uprawnienia w tym działaniu.
Witalij A