Task i Back Stack w Androidzie.

Nie raz pewnie spotkałeś się z pojęciem Tasks lub Back Stack podczas pisania aplikacji na system Android. A czy zagłębiałeś się w ten temat? Wiesz dokładnie jak to działa? Temat jest specyficzny i dość ważny. Temat jest bardziej zorientowany dla początkujących, ale myślę, że doświadczony programista będzie mógł również nauczyć się czegoś nowego. Zapraszam do lektury 🙂

Czym jest Task (zadanie) w Androidzie?

Mówiąc najprościej: zadaniem jest to zbiór aktywności zwykle powiązanych ze sobą – ale niekoniecznie. To, co widzisz na ekranie „Ostatnie aplikacje”, jest z kolei zbiorem takich zadań. Nie każda aktywność jest nowym zadaniem. Każde zadanie zawiera własny stos. W standardowej sytuacji każda aplikacja ma swoje własne zadanie i własny stos.

Ekran główny urządzenia jest miejscem początkowym dla większości zadań. Gdy użytkownik uruchamia aplikację, zadanie tej aplikacji pojawi się na pierwszym planie. Jeśli ta aplikacja nie miała żadnego zadania (aplikacja nie była używana), tworzone jest nowe zadanie, a główna aktywność tej aplikacji pojawia się w stosie na pierwszym miejscu. A jeżeli aplikacja już była używana i jest na stosie to jest przesuwana na wierzch, a inne aplikację/aktywności są spychane w stosie.

Gdy aplikacje są uruchomione w środowisku z wieloma oknami, system zarządza zadaniami oddzielnie dla każdego okna. Każde okno może mieć wiele zadań.

Czym jest Back Stack (stos)?

Wyobraź sobie taką sytuację. Otwierasz aplikację wiadomości i masz listę sms-ów – jedna aktywność. Gdy klikasz na konkretną wiadomość otwierasz kolejną aktywność. Ta nowa aktywność zostanie dodana do stosu (back stack). Jeśli użytkownik naciśnie przycisk wstecz, to nowa aktywność zostanie zakończona i zniknie ze stosu i zostanie wyświetlona ponownie aktywność z listą wiadomości.

Podczas gdy użytkownik przechodzi między aktywnościami, stos jest utrzymywany przez system Android, aby utrzymać kolejność aktywności. Ten stos pomaga w wyświetlaniu aktywności po kliknięciu przycisku wstecz. Stąd nazwa, Back Stack. Spójrz na poniższy diagram.

diagram backstack android

Diagram świetnie pokazuje w najprostszej formie stos. W pierwszej kolejności otwierasz zieloną aktywność, następnie ona otwiera niebieską, a potem pojawia się biała. Teraz użytkownik naciska przycisk „ostatnie aplikacje” i wybiera z powrotem zieloną aktywność. W tym momencie reszta aktywności – biała i niebieska cofają się w stosie – są na drugim planie. Aktywności nie są niszczone.

Teraz użytkownik naciska przycisk „wstecz” – zielona aktywność jest niszczona i przechodzi do białej  Z białej aktywności użytkownik uruchamia kolejną aktywność – niebieską. Teraz w stosie mamy dwie niebieskie aktywności. Jak się przekonasz w dalszej części Android pozwala na to. Ponownie użytkownik naciska przycisk „wstecz” i wraca do białej aktywności, a dalej do niebieskiej. W tym przypadku w stosie było tylko jedno zadanie. Kiedy wszystkie aktywności oraz zadania zostaną usunięte ze stosu użytkownik wróci do ekranu głównego, Gdy przyciśnie przycisk „ostatnie aplikację” zobaczysz pusty ekran. A teraz spójrz na ten diagram.

backstack tasks android

Może też tak się zdarzyć, że w stosie może być kilka zadań. W tasku 1 masz 3 aktywności, zielona aktywność może wywołać żółtą aktywność, ale z nowym zadaniem, a dalej żółta – czerwoną. Jeżeli zamykasz aktywność to trafisz z powrósłem do taska nr 1. Może też tak się zdarzyć, że będziesz w tasku 2 i naciśniesz przycisk „ekran główny”, a potem naciśniesz „ostatnie aplikacje” to możesz wrócić do aktywności, która znajdowała się w tasku 1. Wtedy task nr 2 znajdzie się w tle, i będzie czekać na swoją kolej – musisz wywołać aplikację/aktywność, która znajduję się w tasku 2. Proste? Na pewno 🙂

Wiele zadań może odbywać się jednocześnie w tle. Jeśli jednak użytkownik wykonuje jednocześnie wiele zadań w tle, system może rozpocząć niszczenie aktywności w tle (z powodu braku zasobów systemowych) w celu odzyskania pamięci, co spowoduje utratę stanów aktywności. Gdy aktywność A rozpoczyna aktywność B, aktywność A jest zatrzymywana, ale system zachowuje swój stan (taki jak pozycja przewijania i tekst wprowadzany do formularzy). Jeśli użytkownik naciśnie przycisk wstecz w czasie działania aktywności B, aktywność A zostanie wznowiona z przywróconym stanem – zakładając, że użytkownik nie wyczyści ostatnio otwartych aplikacji.

Jeśli aplikacja pozwala użytkownikom na rozpoczęcie określonej aktywności więcej niż jeden raz to nowa instancja tej aktywności jest tworzona i wysyłana na stos (zamiast wywołując już istniejącą aktywności ze stosu). W związku z tym jedna aktywność w Twojej aplikacji może być tworzone wiele razy (nawet z różnych zadaniach),

Definiowanie trybów uruchamiania aktywności

Tryby uruchamiania aktywności w Androidzie umożliwiają określenie, w jaki sposób nowa instancja aktywności jest powiązana z bieżącym zadaniem. Czyli możesz zdecydować czy dana aktywność ma się otwierać w nowym zadaniu czy korzystać z bieżącego zadania. Także możesz zdefiniować czy otwierana aktywność ma tworzyć nową instancję czy korzystać z już istniejącej instancji. Możesz zdefiniować różne tryby uruchamiania na dwa sposoby:

  • korzystając z pliku AndroidManifest w atrybucie <activity>
  • używając flag intencji

Więcej szczegółów na ten temat znajdziesz w kolejnym wpisie.

Czyszczenie stosu.

Tak jak wcześniej wspomniałem, system może wyczyścić stos jeśli będzie potrzebował pamięci, albo sam użytkownik wyczyści ten stos. System zachowuje się w ten sposób, ponieważ użytkownik może zapomnieć o tym, co robił w aplikacji wcześniej i najprawdopodobniej uruchomi ją ponownie w innym celu. Logikę tę można również zmienić za pomocą kilku atrybutów w manifeście:

  • alwaysRetainTaskState – jeśli ten atrybut jest ustawiony na true to użytkownik zawsze będzie powracać do zadania w jego ostatnim stanie. Stos nie zostanie oczyszczony. Jest to przydatne na przykład w aplikacji takich jak przeglądarka internetowa, w której występuje wiele stanów (na przykład wiele otwartych kart), których użytkownicy nie chcieliby stracić.
  • clearTaskOnLaunch – jeśli ten atrybut jest ustawiony na true to stos jest usuwany do głównej aktywności w zadaniu, gdy użytkownik opuszcza zadanie. Innymi słowy, użytkownik zawsze powraca do zadania w stanie początkowym, nawet po opuszczeniu zadania tylko na chwilę. Przykładem może być aplikacją wiadomości. Jeśli otwierasz aplikację wiadomości, pokazuje Ci się lista wiadomości, wchodzisz w daną wiadomość, teraz naciskając przycisk „ekran główny”, a następnie wejdziesz jeszcze raz do aplikacji wiadomości przez ikonę na liście aplikacji (nie ekran ostatnich otwartych aplikacji), zobaczysz ekran z listą, a nie z konkretną wiadomością,
  • finishOnTaskLaunch – ten atrybut jest podobny do clearTaskOnLaunch, ale działa na pojedynczej aktywności, a nie na całym zadaniu. Gdy jest ustawiony na true, aktywność pozostaje częścią zadania tylko dla bieżącej sesji. Jeśli użytkownik opuści i wróci do zadania (na przykład naciśnie przycisk ekran główny i ponownie naciśnie ikonę aplikacji), nie wyświetli ostatniej aktywności, tylko główną aktywność.
  • noHistory – jeśli ta wartość jest ustawiona na true, niszczy bieżącą aktywność i usuwa ją z stosu, gdy tylko inna aktywność zostaną przez nią uruchomione. Przydaje się przy Splash Screen,

Weryfikacja zadań i stosu.

Jeżeli chcesz zweryfikować jak się zachowuje dana aplikacja/aktywność w stosie oraz jak system zarządza zadaniami możesz to zrobić za pomocą narzędzia adb. W tym celu uruchom aplikację na telefonie, przejdź między różnymi aktywnościami, a następnie wywołaj polecenie:

adb shell dumpsys activity

Musisz odszukać sekcję ACTIVITY MANAGER ACTIVITIES i tam znajdziesz informacje o stosie i otwartych aktywnościach.

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
  Stack #5:
    Task id #100
      TaskRecord{c02a135 #100 A=net.myenv.myapplication U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=net.myenv.myapplication/.LaunchMode.LaunchModeActivity }
        Hist #0: ActivityRecord{52972ba u0 net.myenv.myapplication/.LaunchMode.LaunchModeActivity t100}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=net.myenv.myapplication/.LaunchMode.LaunchModeActivity }
          ProcessRecord{8c9e76c 27318:net.myenv.myapplication/u0a62}
    Running activities (most recent first):
      TaskRecord{c02a135 #100 A=net.myenv.myapplication U=0 sz=1}
        Run #0: ActivityRecord{52972ba u0 net.myenv.myapplication/.LaunchMode.LaunchModeActivity t100}
    mResumedActivity: ActivityRecord{52972ba u0 net.myenv.myapplication/.LaunchMode.LaunchModeActivity t100}
  Stack #0:
    Task id #95
      TaskRecord{987160f #95 I=com.android.launcher3/.Launcher U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10200000 cmp=com.android.launcher3/.Launcher }
        Hist #0: ActivityRecord{fa93a13 u0 com.android.launcher3/.Launcher t95}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10200000 cmp=com.android.launcher3/.Launcher }
          ProcessRecord{8897537 26608:com.android.launcher3/u0a9}
    Task id #94
      TaskRecord{5d7269c #94 A=com.android.systemui U=0 sz=1}
      Intent { act=com.android.systemui.recents.SHOW_RECENTS flg=0x10804000 cmp=com.android.systemui/.recents.RecentsActivity bnds=[70,949][1011,1890] }
        Hist #0: ActivityRecord{35b1f52 u0 com.android.systemui/.recents.RecentsActivity t94}
          Intent { act=com.android.systemui.recents.SHOW_RECENTS flg=0x10804000 cmp=com.android.systemui/.recents.RecentsActivity bnds=[70,949][1011,1890] }
          ProcessRecord{5a73c14 25765:com.android.systemui/u0a15}
    Running activities (most recent first):
      TaskRecord{987160f #95 I=com.android.launcher3/.Launcher U=0 sz=1}
        Run #1: ActivityRecord{fa93a13 u0 com.android.launcher3/.Launcher t95}
      TaskRecord{5d7269c #94 A=com.android.systemui U=0 sz=1}
        Run #0: ActivityRecord{35b1f52 u0 com.android.systemui/.recents.RecentsActivity t94}

Zawsze możesz zawęzić to do swojej aplikacji wydając polecenie

adb shell dumpsys activity package net.myenv.myapplication

Podsumowanie

Temat na pewno nie jest wyczerpany i będę go kontynuował w przyszłości. Poruszony wątek jest specyficzny i często nie jest konieczne dostosowywanie zachowania naszej aktywności, ale zdarzają się sytuacje gdy trzeba takie zachowania zastosować. Jeżeli masz w swojej aplikacji takie wymagania to odpal IDE i zacznij działać 🙂

Co dalej?

  • Polub stronę MYENV na Facebooku oraz śledź mnie na Twitterze
  • Zachęcam do komentowania i pisania propozycji tematów, o których chcesz przeczytać
  • Poleć ten wpis za pomocą poniższych przycisków. Będę Ci za to bardzo wdzięczny 🙂
  • Życzę Ci miłego dnia i miłego kodowania 🙂