Перейти к содержимому

Как выглядит жизненный цикл activity

  • автор:

Activity: Жизненный цикл

Жизненный цикл активити состоит из шести основных коллбэков, вызываемых системой. В порядке вызова: onCreate() , onStart() , onResume() , onPause() , onStop() , onDestroy() .

onCreate() вызывается когда активити создается. В onCreate() вы должны вызвать метод setContentView().

onStart() вызывается когда активити отрисована и видима пользователю.

onResume() вызывается перед тем как активити станет доступна для взаимодействия с пользователем.

onPause() – метод симметричный onResume() . Пользователь больше не может взаимодействовать с активити, но активити частично видна пользователю. В этом состоянии UI активити может изменяться.

onStop() – метод симметричный onStart() . Вызывается, когда активити больше не видна пользователю.

onDestroy() – метод симметричный onCreate() . Вызывается перед тем, как активити будет уничтожена системой.

1. Пользователь жмет Home. В этом случае активити больше не видна, поэтому вызываются onPause() , onStop() . Пользователь открывает приложение снова — onStart() , onResume() .

2. Пользователь переходит на другую активити внутри приложения (реализуется вызовом startActivity() ) – onPause() , onStop() . Пользователь возвращается на предыдущую активити – onStart() , onResume() .

3. Вызов finish() – onPause() , onStop() , onDestroy() . Когда пользователь перейдет на эту активити снова, она будет создана с нуля без сохранения состояния.

4. Пользователь поворачивает телефон и ориентация активити меняется. Активити пересоздается и вызываются коллбеки onPause() , onStop() , onDestroy() , onCreate() , onStart() , onResume() с сохранением состояния.

Это базовые методы активити. Более подробно они описаны в документации.

В полный жизненный цикл также входят onRestart() , onSaveInstanceState() , onRestoreInstanceState() , onNewIntent() и другие методы, которые разберем в будущем.

Что такое жизненный цикл активности Android?

В моем предыдущем посте вы узнали, что Intents позволяет отправлять сообщения с одного компонента Android на другой. Ну, один из очень важных компонентов — это активность.

Действия являются фундаментальной частью разработки приложений для Android. И невозможно понять Деятельности, не понимая их жизненных циклов. В этом посте вы узнаете все о жизненном цикле деятельности.

Android SDK
Что такое Android Intents?

Жизненный цикл деятельности

Активность — это один экран в Android. Это как окно в настольном приложении или фрейм в программе Java. Активность позволяет вам разместить все ваши компоненты пользовательского интерфейса или виджеты вместе на экране.

Важно понимать, что у Activity есть жизненный цикл: то есть он может находиться в одном из нескольких разных состояний, в зависимости от того, что происходит с приложением и с взаимодействием с пользователем.

Методы жизненного цикла

Давайте более подробно рассмотрим жизненный цикл Android Activity. Каждый раз, когда изменяется состояние Activity, в классе Activity будет вызываться один из следующих методов жизненного цикла.

onCreate() : вызывается при первой инициализации действия. Вам необходимо реализовать этот метод для выполнения любой инициализации, специфичной для вашей Деятельности.

onStart() : это вызывается в первый раз, когда действие становится видимым для пользователя, поскольку действие готовится выйти на передний план и становится интерактивным. Как только этот обратный вызов завершится, будет вызван метод onResume() .

onResume() : когда действие переходит в это состояние, оно начинает взаимодействовать с пользователем. Активность продолжается в этом состоянии, пока что-то не сфокусируется на приложении или активности (например, на входящем звонке). Когда это происходит, onPause() метод onPause() .

onPause() : этот метод используется для приостановки операций, которые не должны выполняться, когда действие находится в состоянии паузы. Вызов этого метода означает, что пользователь покидает приложение. Например, в приложении музыкального проигрывателя входящий вызов приведет к переходу приложения в состояние паузы. Это должно отключить или приостановить воспроизведение текущей музыки. Когда пользователь возвращается в приложение, onResume() метод onResume() .

onStop() : этот метод вызывается, когда действие больше не отображается в приложении. Это может произойти, например, когда другая активность была загружена и занимает полный экран устройства. Когда вызывается этот метод, говорят, что Activity находится в остановленном состоянии. В этом состоянии система либо вызывает onRestart() чтобы вернуть интерактивность с Activity. Или он вызывает метод onDestroy() для уничтожения Activity.

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

onRestart() : onRestart() когда действие перезапускается после его остановки.

Начало деятельности

В большинстве случаев взаимодействия пользователя с приложением активная активность изменяется. Таким образом, приложение переходит между действиями много раз в течение своей жизни.

Необходимо связать действия вместе, когда одно действие должно начать другое действие. Чтобы запустить Activity, вы можете использовать startActivity() или startActivityForResult() . Вы должны передать намерение в любом случае.

Начало действия без ожидаемого результата

startActivity() используется, если вновь запущенному Activity не нужно возвращать результат.

В следующем фрагменте кода показано, как запустить другое действие с помощью этого метода:

Жизненный цикл приложения на Android

Жизненный цикл приложения в Android жёстко контролируется системой и зависит от нужд пользователя, доступных ресурсов и т. д. Например, пользователь хочет запустить браузер. Решение о запуске приложения принимает система. Хотя последнее слово и остаётся за системой, она подчиняется определённым заданным и логическим правилам, позволяющим определить, можно ли загрузить, приостановить приложение или прекратить его работу. Если в данный момент пользователь работает с определённым окном, система даёт приоритет соответствующему приложению. И наоборот, если окно невидимо и система решает, что работу приложения необходимо остановить, чтобы мобилизовать дополнительные ресурсы, будет прекращена работа приложения, имеющего более низкий приоритет. В Android ресурсы более ограниченны, поэтому Android более жёстко контролирует работу приложений.

Основные методы жизненного цикла приложения

  • protected void onCreate()
  • protected void onStart()
  • protected void onRestart()
  • protected void onResume()
  • protected void onPause()
  • protected void onStop()
  • protected void onDestroy()

У методов onCreate(), onStart(), onResume() вызов суперкласса должен происходить до вызова вашего кода. В методах onPause(), onStop(), onDestroy() суперкласс следует вызывать после вашего кода (не обращайте внимания на мои примеры).

onCreate()

Метод onCreate() вызывается при создании или перезапуска активности. Система может запускать и останавливать текущие окна в зависимости от происходящих событий. Внутри данного метода настраивают статический интерфейс активности. Инициализирует статические данные активности, связывают данные со списками и т.д. Связывает с необходимыми данными и ресурсами. Задаёт внешний вид через метод setContentView().

В этом методе загружайте пользовательский интерфейс, размещайте ссылки на свойства класса, связывайте данные с элементами управления, создавайте сервисы и потоки. Метод onCreate() принимает объект Bundle, содержащий состояние пользовательского интерфейса, сохранённое в последнем вызове обработчика onSaveInstanceState. Для восстановления графического интерфейса в его предыдущем состоянии нужно задействовать эту переменную: внутри onCreate() или переопределив метод onRestoreInstanceState().

Операции по инициализации, занимающие много времени, следует выполнять в фоновом процессе, а не с помощью метода onCreate(). В противном случае можно получить диалоговое окно ANR (Application Not Responding, приложение не отвечает).

В методе можно сделать проверку, запущено ли приложение впервые или восстановлено из памяти. Если значение переменной savedInstanceState будет null, приложение запускается первый раз:

 // Приложение запущено впервые или восстановлено из памяти? if ( savedInstanceState == null ) // приложение запущено впервые < currentBillTotal = 0.0; // инициализация суммы счета нулем // другой код >else // приложение восстановлено из памяти < // инициализация суммы счета сохранённой в памяти суммой currentBillTotal = savedInstanceState.getDouble(BILL_TOTAL); >

А значение переменной currentBillTotal можно сохранить в методе onSaveInstanceState():

 @Override protected void onSaveInstanceState(Bundle outState)

onStart()

За onCreate() всегда следует вызов onStart(), но перед onStart() не обязательно должен идти onCreate(), так как onStart() может вызываться и для возобновления работы приостановленного приложения (приложение останавливается методом onStop()). При вызове onStart() окно ещё не видно пользователю, но вскоре будет видно. Вызывается непосредственно перед тем, как активность становится видимой пользователю. Сопровождается вызовом метода onResume(), если активность получает передний план, или вызовом метода onStop(), если становится скрытой.

onResume()

Метод onResume() вызывается после onStart(), даже когда окно работает в приоритетном режиме и пользователь может его наблюдать. В этот момент пользователь взаимодействует с созданным вами окном. Приложение получает монопольные ресурсы. Запускает воспроизведение анимации, аудио и видео. Также может вызываться после onPause().

Имейте в виду, что система вызывает этот метод каждый раз, когда ваша активность идёт на переднем плане, в том числе, при первом создании. Таким образом, вы должны реализовать onResume() для инициализации компонентов, регистрации любых широковещательных приёмников или других процессов, которые вы освободили/приостановили в onPause() и выполнять любые другие инициализации, которые должны происходить, когда активность вновь активна.

Пытайтесь размещать относительно быстрый и легковесный код, чтобы ваше приложение оставалось отзывчивым при скрытии с экрана или выходе на передний план.

Вам не нужно перезагружать состояние пользовательского интерфейса внутри него, так как эти функции возложены на обработчики onCreate() и onRestoreInstanceState.

Например, после метода onPause(), в котором мы приостановили работу камеры (см. ниже) снова запускаем камеру:

 @Override public void onResume() < super.onResume(); // Get the Camera instance as the activity achieves full user focus if (mCamera == null) < initializeCamera(); // Local method to handle camera init >> 

onPause()

Когда пользователь решает перейти к работе с новым окном, система вызовет для прерываемого окна метод onPause(). По сути происходит свёртывание активности. Сохраняет незафиксированные данные. Деактивирует и выпускает монопольные ресурсы. Останавливает воспроизведение видео, аудио и анимацию. От onPause() можно перейти к вызову либо onResume(), либо onStop().

В этом методе необходимо остановить анимацию и другие действия, которые загружают процессор. Зафиксировать несохранённые данные, например, черновик письма, потому как после его выполнения работа активности может прерваться без предупреждения. Освободить системные ресурсы, например, обработку данных от GPS.

Пытайтесь размещать относительно быстрый и легковесный код, чтобы ваше приложение оставалось отзывчивым при скрытии с экрана или выходе на передний план.

Исходя из архитектуры своего приложения, вы также можете приостановить выполнение потоков, процессов или широковещательных приёмников, пока активность не появится на переднем плане.

Например, при работе с камерой метод используется следующим образом:

 @Override public void onPause() < super.onPause(); // Release the Camera because we don't need it when paused // and other activities might need to use it. if (mCamera != null) < mCamera.release() mCamera = null; >> 

В тоже время вы не должны использовать onPause() для хранения пользовательских изменений (таких, как персональные данные, введённые в форму) для постоянного хранения. Исключение допускается, когда вы уверены, что пользователи ожидают изменения, которые будут автоматически сохранены (например, при составлении электронной почты). Тем не менее, вы должны избегать выполнения интенсивной работы в onPause(), таких как запись в базе данных, так как это может замедлить переход к следующей активности (вместо него вы должны выполнять тяжелую нагрузку во время операции отключения onStop()).

Когда активность приостановлена, то все компоненты сохраняются в памяти и при возобновления нет необходимости повторно инициализировать их.

onStop()

Метод onStop() вызывается, когда окно становится невидимым для пользователя. Это может произойти при её уничтожении, или если была запущена другая активность (существующая или новая), перекрывшая окно текущей активности. Всегда сопровождает любой вызов метода onRestart(), если активность возвращается, чтобы взаимодействовать с пользователем, или метода onDestroy(), если эта активность уничтожается.

Когда ваша активность останавливается, объекты активности хранятся в памяти и восстанавливаются, когда активность возобновляет свою работу. Вам не нужно повторно инициализировать компоненты, которые были созданы ранее. Кроме того, система отслеживает текущее состояние для каждого представления, поэтому, если пользователь введёт текст в текстовое поле, то его содержание сохраняется и вам не нужно сохранять и восстанавливать его.

Примечание: Даже если система закрыла вашу активность, когда она была остановлена, она по-прежнему сохраняет состояние объектов, таких как текст в EditText в специальном объекте Bundle (в виде ключ-значение) и восстанавливает их, если пользователь переходит обратно к тому же экземпляру активности.

В этом методе можно сделать сложные операции по сохранению данных: для приостановки сложной анимации, потоков, отслеживания показаний датчиков, запросов к GPS, таймеров, сервисов или других процессов, которые нужны исключительно для обновления пользовательского интерфейса. Нет смысла потреблять ресурсы (такты центрального процессора или сетевой трафик) для обновления интерфейса, в то время как он не виден на экране. Примените методы onStart() или onRestart() для возобновления или повторного запуска этих процессов, когда активность опять станет видимой.

При нехватке памяти система может уничтожить скрытую активность, минуя метод onStop() с вызовом метода onDestroy().

onRestart()

Если окно возвращается в приоритетный режим после вызова onStop(), то в этом случае вызывается метод onRestart(). Т.е. вызывается после того, как активность была остановлена и снова была запущена пользователем. Всегда сопровождается вызовом метода onStart().

onRestart предшествует вызовам метода onStart() (кроме самого первого). Используйте его для специальных действий, которые должны выполняться только при повторном запуске активности в рамках «полноценного» состояния.

onDestroy()

Метод вызывается по окончании работы активности, при вызове метода finish() или в случае, когда система уничтожает этот экземпляр активности для освобождения ресурсов. Эти два сценария уничтожения можно определить вызовом метода isFinishing(). Вызывается перед уничтожением активности. Это последний запрос, который получает активность от системы. Если определённое окно находится в верхней позиции в стеке, но невидимо пользователю и система решает завершить это окно, вызывается метод onDestroy(). В этом случае метод удаляет все статические данные активности. Отдаёт все используемые ресурсы.

Так как все необходимые операции по освобождению ресурсов вы сделали в методе onStop(), то в этом методе вы можете подстраховаться и проверить ещё раз все неосвобождённые ресурсы.

На практике вам чаще всего придется сталкиваться с методами onCreate(), onResume() и onPause(). Метод onCreate() будет вызываться при создании пользовательского интерфейса для работы с окном. Данный метод позволит вам связывать данные с компонентами и подключать обработчики событий к компонентам пользовательского интерфейса. При помощи onPause() вы сможете сохранить важную информацию в базе данных вашего приложения. Это последний безопасный метод, который будет вызываться перед тем, как система завершит работу приложения. Метод onDestroy() не обязательно будет вызываться, поэтому не полагайтесь на этот метод при реализации критическом логики.

Пример

Для наглядности создадим пример, где для каждого метода жизненного цикла подготовим всплывающее сообщение, а также для подстраховки будем вести запись в журнале. Мы сможем воочию увидеть весь жизненный цикл.

Код для методов:

 package ru.alexanderklimov.lifecycle; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends ActionBarActivity < private String TAG = "Жизненный цикл"; private TextView mInfoTextView; @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mInfoTextView = (TextView) findViewById(R.id.textViewInfo); Toast.makeText(getApplicationContext(), "onCreate()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onCreate()"); >@Override protected void onStart() < super.onStart(); Toast.makeText(getApplicationContext(), "onStart()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onStart()"); >@Override protected void onResume() < super.onResume(); Toast.makeText(getApplicationContext(), "onResume()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onResume()"); >@Override protected void onPause() < super.onPause(); Toast.makeText(getApplicationContext(), "onPause()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onPause()"); >@Override protected void onStop() < super.onStop(); Toast.makeText(getApplicationContext(), "onStop()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onStop()"); >@Override protected void onRestart() < super.onRestart(); Toast.makeText(getApplicationContext(), "onRestart()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onRestart()"); >@Override protected void onDestroy() < super.onDestroy(); Toast.makeText(getApplicationContext(), "onDestroy()", Toast.LENGTH_SHORT).show(); Log.i(TAG, "onDestroy()"); >public void onClick(View v) < switch (v.getId()) < case R.id.buttonTouchMe: mInfoTextView.setText("Приложение уже было запущено!"); break; case R.id.buttonExit: finish(); break; default: break; >> > 

Запускайте проект и следите за сообщениями. Они будут всплывать в нужной последовательности, давая вам представление о жизненном цикле приложения. Также сообщения будут транслироваться в окно logcat на вкладке 6.Android в студии. Обратите внимание на следующий момент. Когда ваше приложение запущено, то нажмите на первую кнопку, чтобы изменить текст в TextView. Затем нажмите кнопку «Home» (не Back!), чтобы попасть на Домашний экран. После чего снова запустите ваше приложение. Вы увидите, что приложение не вызывает метод onCreate(), а текст в TextView будет свидетельствовать, что приложение не было закрыто, а только свёрнуто. Это очень важный момент, который нужно понять. Понимание этих вещей поможет вам правильно выстраивать логику приложения.

Памятка

onCreate() → onStart() → onResume()

Нажимаем кнопку Назад для выхода из приложения

onPause() → onStop() → onDestroy()

Нажата кнопка Домой

onPause() → onStop()

После нажатия кнопки Домой, когда приложение запущено из списка недавно открытых приложений или через значок

onRestart() → onStart() → onResume()

Когда запускается другое приложение из области уведомлений или открывается приложение Настройки

onPause() → onStop()

Нажата кнопка Назад в другом приложении или в Настройках и ваше приложение стало снова видимым.

onRestart() → onStart() → onResume()

Открывается диалоговое окно

onPause()

Диалоговое окно закрывается

onResume()

Кто-то звонит на телефон

onPause() → onResume()

Пользователь отвечает на звонок

onPause()
onResume()

Экран телефона гаснет

onPause() → onStop()

Экран снова включён

onRestart() → onStart() → onResume()

На китайских планшетах иногда наблюдал, когда какие-то методы не срабатывали.

При повороте активность проходит через цепочку различных состояний. Порядок следующий.

onPause()
onStop()
onDestroy()
onCreate()
onStart()
onResume()

Порядок вызовов

После onCreate()onStart()

После onRestart()onStart()

После onStart()onResume() или onStop()

После onResume()onPause()

После onPause()onResume() или onStop()

После onStop()onRestart() или onDestroy()

После onDestroy() — ничего

Нестыдные вопросы про жизненный цикл

Каждый разработчик сталкивался с вопросами про жизненный цикл Activity: что такое bind-сервис, как сохранить состояние интерфейса при повороте экрана и чем Fragment отличается от Activity.

У нас в FunCorp (см. источник) накопился список вопросов на похожие темы, но с определёнными нюансами. Некоторыми из них я и хочу с вами поделиться.

1. Все знают, что если открыть второе активити поверх первого и повернуть экран, то цепочка вызовов жизненного цикла будет выглядеть следующим образом:

FirstActivity: onPause SecondActivity: onCreate SecondActivity: onStart SecondActivity: onResume FirstActivity: onSaveInstanceState FirstActivity: onStop
SecondActivity: onPause SecondActivity: onSaveInstanceState SecondActivity: onStop SecondActivity: onCreate SecondActivity: onStart SecondActivity: onRestoreInstanceState SecondActivity: onResume
SecondActivity: onPause FirstActivity: onCreate FirstActivity: onStart FirstActivity: onRestoreInstanceState SecondActivity: onStop

А что будет в случае, если второе активити прозрачное?

Решение

В случае с прозрачным верхним активити с точки зрения логики всё немного отличается. Именно потому, что оно прозрачное, после поворота необходимо восстановить содержимое и того активити, которое находится непосредственно под ним. Поэтому порядок вызовов будет немного отличаться:

FirstActivity: onPause SecondActivity: onCreate SecondActivity: onStart SecondActivity: onResume
SecondActivity: onPause SecondActivity: onSaveInstanceState SecondActivity: onStop SecondActivity: onCreate SecondActivity: onStart SecondActivity: onRestoreInstanceState SecondActivity: onResume FirstActivity: onSaveInstanceState FirstActivity: onStop FirstActivity: onCreate FirstActivity: onStart FirstActivity: onRestoreInstanceState FirstActivity: onResume FirstActivity: onPause

2. Ни одно приложение не обходится без динамического добавления вью, но иногда приходится перемещать одну и ту же вью между разными экранами. Можно ли один и тот же объект добавить одновременно в две разных активити? Что будет, если я создам её с контекстом Application и захочу добавлять одновременно в различные активити?

Зачем это нужно?

Существуют «не очень приятные» библиотеки, которые внутри кастомных вью держат важную бизнес-логику, и пересоздание этих вью в рамках каждого нового активити является плохим решением, т.к. хочется иметь один набор данных.

Решение

Ничего не мешает создать вью с контекстом Application. Она просто применит дефолтные стили, не относящиеся к какому-либо активити. Также без проблем можно перемещать эту вью между разными активити, но нужно следить, чтобы она была добавлена только в одного родителя.

private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) < . if (child.getParent() != null) < throw new IllegalStateException("The specified child already has a parent. " + "You must call removeView() on the child's parent first."); >. >

Можно, например, подписаться на ActivityLifecycleCallbacks, на onStop удалять (removeView) из текущего активити, на onStart добавлять в следующее открываемое (addView).

3. Фрагмент можно добавить через add и через replace. А в чём отличие между этими двумя вариантами с точки зрения порядка вызова методов жизненного цикла? В чём преимущества каждого из них?

Решение

Даже если вы добавляете фрагмент через replace, то это не значит, что он полностью заменяется. Это значит, что на этом месте в контейнере заменится его вью, следовательно, у текущего фрагмента будет вызвано onDestroyView, а при возврате назад будет снова вызван onCreateView.

Это довольно сильно меняет правила игры. Приходится детачить все контроллеры и классы, связанные с UI, именно в onDestroyView. Нужно чётко разделять получение данных, необходимых фрагменту, и заполнение вью (списков и т.д.), так как заполнение и разрушение вью будет происходить намного чаще, чем получение данных (чтение каких-то данных из БД).

Также появляются нюансы с восстановлением состояния: например, onSaveInstanceState иногда приходит после onDestroyView. К тому же стоит учитывать, что если в onViewStateRestored пришёл null, то это значит, что не нужно ничего восстанавливать, а не сбрасываться до дефолтного состояния.

Если говорить про удобства между add и replace, то replace экономнее по памяти, если у вас глубокая навигация (у нас глубина навигации юзера — один из продуктовых KPI). Также намного удобнее с replace управлять панелью инструментов, так как в onCreateView можно её переинфлейтить. Из плюсов add: меньше проблем с жизненным циклом, при возврате назад не пересоздаются вью и не нужно ничего заново заполнять.

4. Иногда всё ещё приходится работать напрямую с сервисами и даже с bind-сервисами. С одним из подобных сервисов взаимодействует активити (только одна активити). Она коннектится к сервису и передаёт в него данные. При повороте экрана наша активити разрушается, и мы обязаны отбайндится от этого сервиса. Но если нет ни одного соединения, то сервис разрушается и после поворота bind будет к совершенно другому сервису. Как сделать так, чтобы при повороте сервис оставался жить?

Решение

Если вы знаете красивое решение, то напишите в комментариях. На ум приходит только нечто подобное:

@Override protected void onDestroy() < super.onDestroy(); ThreadsUtils.postOnUiThread(new Runnable() < @Override public void run() < unbindService(mConnection); >>); >

5. Недавно мы переделали навигацию внутри нашего приложения на Single Activity (с помощью одной из доступных библиотек). Раньше каждый экран приложения был отдельным активити, сейчас навигация работает на фрагментах. Проблема возврата к активити в середине стека решалась intent-флагами. Как можно вернуться к фрагменту в середине стека?

Решение

Да, решения из коробки FragmentManager не предоставляет. Cicerone делает внутри себя нечто подобное:

protected void backTo(BackTo command) < String key = command.getScreenKey(); if (key == null) < backToRoot(); >else < int index = localStackCopy.indexOf(key); int size = localStackCopy.size(); if (index != -1) < for (int i = 1; i < size - index; i++) < localStackCopy.pop(); >fragmentManager.popBackStack(key, 0); > else < backToUnexisting(command.getScreenKey()); >> >

6. Также недавно мы избавились от такого неэффективного и сложного компонента, как ViewPager, потому что логика взаимодействия с ним очень сложна, а поведение фрагментов непрогнозируемо в определённых кейсах. В некоторых фрагментах мы использовали Inner-фрагменты. Что будет при использовании фрагментов внутри элементов RecycleView?

Решение

В общем случае не будет ничего плохого. Фрагмент без проблем добавится и будет отображаться. Единственное, с чем мы столкнулись, — это нестыковки с его жизненным циклом. Реализация на ViewPager управляет жизненным циклом фрагментов посредством setUserVisibleHint, а RecycleView делает всё в лоб, не думая про фактическую видимость и доступность фрагментов.

7. Всё по той же причине перехода с ViewPager мы столкнулись с проблемой восстановления состояния. В случае с фрагментами это реализовывалось силами фреймворка: в нужных местах мы просто переопределяли onSaveInstanceState и сохраняли в Bundle все необходимые данные. При пересоздании ViewPager все фрагменты восстанавливались силами FragmentManager и возвращали свое состояние. Что делать в случае с RecycleView и его ViewHolder?

Решение

«Надо писать всё в базу и каждый раз читать из неё», — скажете вы. Или логика сохранения состояния должна быть снаружи, а список — это просто отображение. В идеальном мире так и есть. Но в нашем случае каждый элемент списка — это сложный экран со своей логикой. Поэтому пришлось изобрести свой велосипед в стиле «сделаем такую же логику, как во ViewPager и фрагменте»:

public class RecycleViewGalleryAdapter extends RecyclerView.Adapter implements GalleryAdapter < private static final String RV_STATE_KEY = "RV_STATE"; @Nullable private Bundle mSavedState; @Override public void onBindViewHolder(GalleryItemViewHolder holder, int position) < if (holder.isAttached()) < holder.detach(); >holder.attach(createArgs(position, getItemViewType(position))); restoreItemState(holder); > @Override public void saveState(Bundle bundle) < Bundle adapterState = new Bundle(); saveItemsState(adapterState); bundle.putBundle(RV_STATE_KEY, adapterState); >@Override public void restoreState(@Nullable Bundle bundle) < if (bundle == null) return; mSavedState = bundle.getBundle(RV_STATE_KEY); >private void restoreItemState(GalleryItemViewHolder holder) < if (mSavedState == null) < holder.restoreState(null); return; >String stateKey = String.valueOf(holder.getGalleryItemId()); Bundle state = mSavedState.getBundle(stateKey); if (state == null) < holder.restoreState(null); mSavedState = null; return; >holder.restoreState(state); mSavedState.remove(stateKey); > private void saveItemsState(Bundle outState) < GalleryItemHolder holder = getCurrentGalleryViewItem(); saveItemState(outState, (GalleryItemViewHolder) holder); >private void saveItemState(Bundle bundle, GalleryItemViewHolder holder) < Bundle itemState = new Bundle(); holder.saveState(itemState); bundle.putBundle(String.valueOf(holder.getGalleryItemId()), itemState); >>

На Fragment.onSaveInstanceState мы считываем состояние нужных нам холдеров и кладём их в Bundle. При пересоздании холдеров мы достаем сохранённый Bundle и на onBindViewHolder передаём найденные состояния внутрь холдеров:

@Override protected void onCreate(Bundle savedInstanceState)

8. Чем нам это грозит?

Решение

На самом деле, ничего плохого в этом нет. В том же RecycleView хранятся списки из элементов с одинаковыми id. Однако всё-таки есть небольшой нюанс:

@Override protected T findViewTraversal(@IdRes int id) < if (id == mID) < return (T) this; >final View[] where = mChildren; final int len = mChildrenCount; for (int i = 0; i < len; i++) < View v = where[i]; if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) < v = v.findViewById(id); if (v != null) < return (T) v; >> > return null; >

Стоит быть внимательнее, если у нас в иерархии есть элементы с одинаковыми id, т.к. возвращается всегда именно первый найденный элемент, и на разных уровнях вызова findViewById это могут быть разные объекты.

9. Вы падаете с TooLargeTransaction при повороте экрана (да, здесь по-прежнему косвенно виноват наш ViewPager). Как найти виновного?

Решение

Всё довольно просто: повесить ActivityLifecycleCallbacks на Application, ловить все onActivitySaveInstanceState и парсить всё, что лежит внутри Bundle. Там же можно достать и состояние всех вью и всех фрагментов внутри этого активити. Ниже пример, как мы достаём состояние фрагментов из Bundle:

/** * Tries to find saved [FragmentState] in bundle using 'android:support:fragments' key. */ fun Bundle.getFragmentsStateList(): List? < try < val fragmentManagerState: FragmentManagerState? = getParcelable("android:support:fragments") val active = fragmentManagerState?.mActive ?: return emptyList() return active.filter < it.mSavedFragmentState != null >.map < fragmentState ->FragmentBundle(fragmentState.mClassName, fragmentState.mSavedFragmentState) > > catch (throwable: Throwable) < Assert.fail(throwable) return null >> fun init() < application.registerActivityLifecycleCallbacks( object : SimpleActivityLifecycleCallback() < override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) < super.onActivitySaveInstanceState(activity, outState) outState?.let < ThreadsUtils.runOnMainThread < trackActivitySaveState(activity, outState) >> > >) > @MainThread private fun trackActivitySaveState(activity: Activity, outState: Bundle) < val sizeInBytes = outState.getSizeInBytes() val fragmentsInfos = outState.getFragmentsStateList()?.map < mapFragmentsSaveInstanceSaveInfo(it) >. >

Далее мы просто вычисляем размер Bundle и логируем его:

fun Bundle.getSizeInBytes(): Int < val parcel = Parcel.obtain() return try < parcel.writeValue(this) parcel.dataSize() >finally < parcel.recycle() >>

10. Предположим, у нас есть активити и набор зависимостей на нём. При определённых условиях нам нужно пересоздать набор этих зависимостей (например, по клику запустить какой-то эксперимент с другим UI). Как нам это реализовать?

Решение

Конечно, можно повозиться с флагами и сделать это каким-то «костыльным» перезапуском активити через запуск интента. Но на деле всё очень просто — у активити есть метод recreate.

Скорее всего, большая часть этих знаний вам и не пригодится, так как к каждому из них приходишь не от хорошей жизни. Однако некоторые из них хорошо демонстрируют, как человек умеет рассуждать и предлагать свои решения. Мы используем подобные вопросы на собеседованиях. Если у вас есть интересные задачи, которые вам предлагали решить на собеседованиях, или вы сами их ставите, напишите их в комментариях — интересно будет обсудить!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *