Пожалуй, самый популярный вопрос на собеседованиях на Android-разработчика звучит так: «расскажите нам про жизненный цикл Activity». На первый взгляд в этом нет ничего сложного, в каком только блоге об этом ещё не писали, и кандидат тут же начинает рисовать известную блок-схему и по ходу пояснять. Сферический life cycle в вакууме, которым изобилуют все уроки, действительно достаточно прост для понимания, но ведь activity — это только часть некой обобщающей их сущности. Сущность эта называется Activity Stack, и с его жизненным циклом мы сейчас постараемся разобраться.
Жизненный цикл Activity Stack (часть 2)
Имеется:
ActivityA, ActivityB, ActivityC
На layout'е каждой их Activity находится одна кнопка, которая делает обычный запуск следующей Activity:
То есть ActivityA запускает ActivityB, ActivityB запускает ActivityC, а ActivityC запускает снова ActiviyA.
Стэк Activity на то и стэк, что для него работает правило «последним пришёл — первым ушёл». По кнопке back текущее Activity уничтожается (
Для наглядности, лог A->B->C->back->back->back:
С этим поведением все знакомы, но защищён ли наш Activity Stack в том случае, если мы решили оставить приложение в фоновом режиме и запустить ресурсо-затратное стороннее приложение?
App1->A->B->C->Home->App2->back->App1:
Из лога видим, что процесс нашего приложения (
Проделаем нечто подобное, только не будем дожидаться, пока стороннее приложение потребует памяти и убьёт наше приложение, а сделаем это сами через Application Manager кнопкой Force Stop.
App1->A->B->C->Home->App Manager->Force Stop->App1
Получаем совершенно другое поведение: Activity Stack потерян, и при запуске приложения после force stop'а мы оказываемся в корневом ActivityA.
Обращаем также внимание, что в обоих случаях, когда наш процесс уничтожался целиком, ни в одной Activity не был вызван метод
С поведением по умолчанию мы разобрались, но в арсенале Android'а есть инструменты, чтобы менять поведение под наши нужды. Их мы рассмотрим в следующей части.
P.S. Добавил свои комментарии в логи по просьбе Vest. Спасибо! Кажется, стало нагляднее.
Жизненный цикл 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. Спасибо! Кажется, стало нагляднее.