Жизненный цикл Activity Stack (часть 1)

Пожалуй, самый популярный вопрос на собеседованиях на Android-разработчика звучит так: «расскажите нам про жизненный цикл Activity». На первый взгляд в этом нет ничего сложного, в каком только блоге об этом ещё не писали, и кандидат тут же начинает рисовать известную блок-схему и по ходу пояснять. Сферический life cycle в вакууме, которым изобилуют все уроки, действительно достаточно прост для понимания, но ведь activity — это только часть некой обобщающей их сущности. Сущность эта называется Activity Stack, и с его жизненным циклом мы сейчас постараемся разобраться.

Жизненный цикл Activity Stack (часть 2)

Имеется:
ActivityA, ActivityB, ActivityC
<activity android:name=".ActivityA">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
<activity android:name=".ActivityB" />
<activity android:name=".ActivityC" />

На layout'е каждой их Activity находится одна кнопка, которая делает обычный запуск следующей Activity:
Intent intent = new Intent(view.getContext(), ActivityB.class);
startActivity(intent);

То есть ActivityA запускает ActivityB, ActivityB запускает ActivityC, а ActivityC запускает снова ActiviyA.

Стэк Activity на то и стэк, что для него работает правило «последним пришёл — первым ушёл». По кнопке back текущее Activity уничтожается (onDestroy()), а предыдущее восстанавливается (onCreate() и/или onStart()), если уничтоженное не было корневым.

Для наглядности, лог A->B->C->back->back->back:
*** Стартуем приложение ***
I/ActivityManager(249): START {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 8672
D/ActivityA(28229): onCreate()
D/ActivityA(28229): onStart()
I/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityA: +234ms

*** Переход на ActivityB ***
I/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityB u=0} from pid 28229
D/ActivityB(28229): onCreate()
D/ActivityB(28229): onStart()
I/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityB: +135ms
D/ActivityA(28229): onStop()

*** Переход на ActivityC ***
I/ActivityManager(249): START {cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityC u=0} from pid 28229
D/ActivityC(28229): onCreate()
D/ActivityC(28229): onStart()
I/ActivityManager(249): Displayed com.habrahabr.ActivityStackLifeCycle/.ActivityC: +206ms
D/ActivityB(28229): onStop()

*** Жмём Back  ***
D/ActivityB(28229): onStart()
D/ActivityC(28229): onStop()
D/ActivityC(28229): onDestroy()

*** Жмём Back  ***
D/ActivityA(28229): onStart()
D/ActivityB(28229): onStop()
D/ActivityB(28229): onDestroy()

*** Жмём Back  ***
D/ActivityA(28229): onStop()
D/ActivityA(28229): onDestroy()

С этим поведением все знакомы, но защищён ли наш Activity Stack в том случае, если мы решили оставить приложение в фоновом режиме и запустить ресурсо-затратное стороннее приложение?

App1->A->B->C->Home->App2->back->App1:
*** Стартуем приложение ***
I/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 1195
I/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=1267 uid=10060 gids={1028}
D/ActivityA(1267): onCreate()
D/ActivityA(1267): onStart()

*** Жмём Home, запускаем ресурсо-затратное приложение, балуемся, ждём, пока процесс нашего приложения умрёт ***
<...>
I/ActivityManager(249): Process com.habrahabr.ActivityStackLifeCycle (pid 1267) has died.
<...>

*** Выходим из стороннего приложения, запускаем наше приложение снова ***
I/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityC: pid=1879 uid=10060 gids={1028}
D/ActivityC(1879): onCreate()
D/ActivityC(1879): onStart()

Из лога видим, что процесс нашего приложения (id=1267) был убит, но это не помешало Android'у при возврате в наше приложение создать новый (id=1879) и открыть последнюю ActivityC, на которой мы находились, когда свернули приложение.

Проделаем нечто подобное, только не будем дожидаться, пока стороннее приложение потребует памяти и убьёт наше приложение, а сделаем это сами через Application Manager кнопкой Force Stop.

App1->A->B->C->Home->App Manager->Force Stop->App1
*** Стартуем приложение ***
I/ActivityManager(249): START {flg=0x10000000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 32050
I/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=32090 uid=10060 gids={1028}
D/ActivityA(32090): onCreate()
D/ActivityA(32090): onStart()
<...>

*** Жмём Home, запускаем Application Manager, находим наше приложение и делаем ему Force Stop ***
I/ActivityManager(249): Force stopping package com.habrahabr.ActivityStackLifeCycle uid=10060
I/ActivityManager(249): Killing proc 32090:com.habrahabr.ActivityStackLifeCycle/u0a60: force stop
I/ActivityManager(249):   Force finishing activity ActivityRecord{419ba4b0 com.habrahabr.ActivityStackLifeCycle/.ActivityA}
I/ActivityManager(249):   Force finishing activity ActivityRecord{41c392b8 com.habrahabr.ActivityStackLifeCycle/.ActivityB}
I/ActivityManager(249):   Force finishing activity ActivityRecord{41ac4588 com.habrahabr.ActivityStackLifeCycle/.ActivityC}
<...>

*** Выходим из Application Manager, запускаем наше приложение снова ***
I/ActivityManager(249): START {flg=0x10104000 cmp=com.habrahabr.ActivityStackLifeCycle/.ActivityA u=0} from pid 337
I/ActivityManager(249): Start proc com.habrahabr.ActivityStackLifeCycle for activity com.habrahabr.ActivityStackLifeCycle/.ActivityA: pid=32378 uid=10060 gids={1028}
D/ActivityA(32378): onCreate()
D/ActivityA(32378): onStart()

Получаем совершенно другое поведение: Activity Stack потерян, и при запуске приложения после force stop'а мы оказываемся в корневом ActivityA.

Обращаем также внимание, что в обоих случаях, когда наш процесс уничтожался целиком, ни в одной Activity не был вызван метод onDestroy(), это несомненно стоит учитывать при разработке.

С поведением по умолчанию мы разобрались, но в арсенале Android'а есть инструменты, чтобы менять поведение под наши нужды. Их мы рассмотрим в следующей части.

P.S. Добавил свои комментарии в логи по просьбе Vest. Спасибо! Кажется, стало нагляднее.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 6

    0
    Статья хорошая. Как программисту мне это интересно. А еще я пользователь смартфона с маленькой памятью, поэтому я знаю, что в большинстве приложений работа с activity реализована неправильно, но разработчики приложений считают, что это нормально.
      0
      Гугл вполне четко говорит, что нет никакой гарантии что onDestroy будет вызван.
        +1
        Согласен, но статья не претендует на открытие тайн :)
        На блок-схеме гугл-девелопера есть стрелка уводящая от onDestroy() с подписью
        Apps with higher priority need memory
        Хотя другая, ведущая к onDestroy() подписана
        This activity is finishing or being destroyed by the system
        Достаточно двусмысленно выглядит и оставляет вопрос: «а что должно произойти, чтобы система уничтожила не финишированное вручную Activity с помощью onDestroy()?
        0
        Nexus S, Android 4.1.2. Поведение следующее:
        onDestroy A вызывается стабильно на каждом переходе A→B.
        onDestroy A вызывается стабильно при A → Home. Процесс при этом не обязательно умирает. Если подзагрузить систему и дождаться смерти процесса, то при перезапуске сохраняется activity stack, приложение помнит последнее запущенное активити.
        Что касается до Force stop, то он ожидаемо убивает процесс и обнуляет activity stack. Лог Force finishing activity ActivityRecord для каждого активити как-бы намекает, что запись удалена и информация о последнем запущенном activity утеряна.

        Как результат, предсказуемое поведение и остается только догадываться почему у нас отличается второй сценарий, где у Вас не вызывается onDestroy. Не стоит также забывать, что «Это Андроид, детка!» и прихоти всяких «оптимизаторов» от сторонних производителей всегда будут вносить свои коррективы.
          0
          Поведение с вызовом onDestroy достаточно странное, учитывая, что у меня та же модель с той же версией 4.1.2 :) Остальное же, действительно, предсказуемо.
          Никаких дополнительных флагов и параметров в манифесте, как я понимаю? Я бы тогда даже осмелился сказать, что он работает неверно, т.к. onDestroy не должен вызываться каждый раз, когда Activity исчезает с экрана, иначе не было бы смысла в onStop (который будет вызываться обязательно) и onRestart.
          Есть предположение, что стоит галочка в Developer options/Apps/Don't keep activities. Можете проверить для подтверждения теории? Самому интересно.
            +1
            Прошу пардону! Действительно, забыл что выставил don't keep activities для другого проекта. В таком случае я разделяю Ваше недоумение по поводу onDestroy. Без галочки процесс молча умирает даже не пискнув.

        Only users with full accounts can post comments. Log in, please.