Из каких базовых компонент состоит приложение?
Базовые компоненты андроид приложения: Activity, Service, BroadcastReceiver, ContentProvider. Каждый из базовых компонент объявляется в андроид манифесте и может являться точкой входа в приложение.
Activity представляет UI и функциональность, видимые пользователю.
Service используется для исполнения долгих операций, которые не требуют взаимодействия с пользователем.
BroadcastReceiver принимает и обрабатывает броадкаст ивенты, отправленные внутри приложения или из других приложений.
ContentProvider используется для обмена данными с другими приложениями.
Каждый из компонентов подробнее рассмотрим в будущем.
#Components
Базовые компоненты андроид приложения: Activity, Service, BroadcastReceiver, ContentProvider. Каждый из базовых компонент объявляется в андроид манифесте и может являться точкой входа в приложение.
Activity представляет UI и функциональность, видимые пользователю.
Service используется для исполнения долгих операций, которые не требуют взаимодействия с пользователем.
BroadcastReceiver принимает и обрабатывает броадкаст ивенты, отправленные внутри приложения или из других приложений.
ContentProvider используется для обмена данными с другими приложениями.
Каждый из компонентов подробнее рассмотрим в будущем.
#Components
Может ли приложение быть запущено в нескольких процессах?
Короткий ответ – Да.
Но для начала давайте разберемся чем процесс отличается от потока.
Процесс – это сущность на уровне ОС. Каждая программа может быть запущена в одном или нескольких процессах.
Поток – это сущность на уровне Runtime Environment.
Программа может выполняться в нескольких процессах. В каждом из процессов может быть создано несколько потоков (Java Threads).
Вернемся к андроиду. В предыдущем посте мы рассмотрели базовые компоненты и узнали, что каждый из компонентов может быть точкой входа в приложение. По умолчанию компоненты приложения работают в дефолтном процессе, но каждый из компонентов
Важно: При старте нового процесса создается инстанс приложения и снова вызывается
Подробнее про процессы и треды читайте в документации.
#System
Короткий ответ – Да.
Но для начала давайте разберемся чем процесс отличается от потока.
Процесс – это сущность на уровне ОС. Каждая программа может быть запущена в одном или нескольких процессах.
Поток – это сущность на уровне Runtime Environment.
Программа может выполняться в нескольких процессах. В каждом из процессов может быть создано несколько потоков (Java Threads).
Вернемся к андроиду. В предыдущем посте мы рассмотрели базовые компоненты и узнали, что каждый из компонентов может быть точкой входа в приложение. По умолчанию компоненты приложения работают в дефолтном процессе, но каждый из компонентов
<activity>
, <service>
, <receiver>
, <provider>
может иметь поле android:process="<name>"
в андроид манифесте. Кастомное значение этого поля явно говорит системе в каком процессе будет работать компонент.Важно: При старте нового процесса создается инстанс приложения и снова вызывается
Application.onCreate()
. Если в этом методе вы инициализируете модули или библиотеки, которые используются только в главном процессе, то не забывайте проверять, в каком процессе вы находитесь в момент инициализации.Подробнее про процессы и треды читайте в документации.
#System
Приоритеты процессов
Процессы делятся по приоритету на 4 вида, в порядке убывания:
1. Видимый активный процесс (Foreground process). В этом процессе работает приложение, с которым взаимодействует пользователь. Процесс находится в этом состоянии в следующих случаях:
-
- Во время выполнения
- Запущен
2. Видимый процесс. Пользователь знает о приложении, запущенном в этом процессе, но не взаимодействует с ним:
-
-
-
3. Background Service. Процесс с сервисом, запущенным командой startService().
4. Закэшированный процесс. К ним относятся пустые процессы, используемые при старте приложений, и процессы, в которых все активити в
Система убивает процессы в порядке возрастания их приоритета для восстановления ресурсов. Подробнее
#Lifecycle
#System
Процессы делятся по приоритету на 4 вида, в порядке убывания:
1. Видимый активный процесс (Foreground process). В этом процессе работает приложение, с которым взаимодействует пользователь. Процесс находится в этом состоянии в следующих случаях:
-
Activity
, у которой вызван onResume()
, но еще не вызван onPause()
- Во время выполнения
BroadcastReceiver.onReceive()
- Запущен
Service
и выполняется один из колбэков: onCreate()
, onStart()
, onDestroy()
2. Видимый процесс. Пользователь знает о приложении, запущенном в этом процессе, но не взаимодействует с ним:
-
Activity
у которой вызван onPause()
, но еще не вызван onStop()
.-
Service
, который запущен через Service.startForeground(). В этом случае пользователь видит нотификацию, связанную с сервисом.-
Service
, выполняющий функцию, о которой пользователь осведомлен. Например input method service.3. Background Service. Процесс с сервисом, запущенным командой startService().
4. Закэшированный процесс. К ним относятся пустые процессы, используемые при старте приложений, и процессы, в которых все активити в
onStop()
состоянии.Система убивает процессы в порядке возрастания их приоритета для восстановления ресурсов. Подробнее
#Lifecycle
#System
Activity: Что такое и как запустить?
Activity – основной компонент в андроиде. Активити – это экран, то что видит и с чем взаимодействует пользователь. Может содержать несколько View и фрагментов. Так же как и другие базовые компоненты, может быть точкой входа в приложение.
Активити с MAIN Intent Filter в манифесте запускается при старте приложения, когда пользователь кликает на иконку приложения.
#Activity
#Components
Activity – основной компонент в андроиде. Активити – это экран, то что видит и с чем взаимодействует пользователь. Может содержать несколько View и фрагментов. Так же как и другие базовые компоненты, может быть точкой входа в приложение.
Активити с MAIN Intent Filter в манифесте запускается при старте приложения, когда пользователь кликает на иконку приложения.
#Activity
#Components
Activity: Жизненный цикл
Жизненный цикл активити состоит из шести основных коллбэков, вызываемых системой. В порядке вызова:
onCreate() вызывается когда активити создается. В
onStart() вызывается когда активити отрисована и видима пользователю.
onResume() вызывается перед тем как активити станет доступна для взаимодействия с пользователем.
onPause() – метод симметричный
onStop() – метод симметричный
onDestroy() – метод симметричный
Несколько примеров:
1. Пользователь жмет Home. В этом случае активити больше не видна, поэтому вызываются
2. Пользователь переходит на другую активити внутри приложения (реализуется вызовом
3. Вызов
4. Пользователь поворачивает телефон и ориентация активити меняется. Активити пересоздается и вызываются коллбеки
Это базовые методы активити. Более подробно они описаны в документации.
В полный жизненный цикл также входят
#Activity
#Lifecycle
Жизненный цикл активити состоит из шести основных коллбэков, вызываемых системой. В порядке вызова:
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()
и другие методы, которые разберем в будущем.#Activity
#Lifecycle
Activity: Как пережить поворот экрана?
При повороте экрана активити уничтожается и создается заново. Вызываются коллбэки
Чтобы сохранить состояние активити, вы должны переопределить метод onSaveInstanceState() и положить данные в Bundle.
При реинициализации активити,
Система вызывает
onSaveInstanceState() вызывается после
onRestoreInstanceState() вызывается после
#Activity
#Lifecycle
#State
При повороте экрана активити уничтожается и создается заново. Вызываются коллбэки
onPause()
, onStop()
, onSaveInstanceState()
, onDestroy()
– onCreate()
, onStart()
, onRestoreInstanceState()
, onResume()
.Чтобы сохранить состояние активити, вы должны переопределить метод onSaveInstanceState() и положить данные в Bundle.
При реинициализации активити,
Bundle
с сохраненным состоянием передается в onCreate()
и в onRestoreInstanceState().Система вызывает
onSaveInstanceState()
и onRestoreInstanceState()
только в том случае, когда необходимо сохранить состояние, например при повороте экрана или при убийстве активити для освобождения памяти. Данные коллбэки не вызываются, если пользователь выходит из активити нажав Back или если активити убивается вызовом finish()
.onSaveInstanceState() вызывается после
onStop()
на версии API ≥ 28. На API < 28 этот коллбэк вызывается перед onStop()
и нет гарантий до или после onPause()
.onRestoreInstanceState() вызывается после
onStart()
.#Activity
#Lifecycle
#State
Нужно ли думать о сохранении состояния, если приложение поддерживает только портретную ориентацию?
– Да.
Поворот экрана – это частный случай Configuration Changes. Другой пример – изменение языка системы.
Кроме того, как было упомянуто постом выше, система может уничтожить активити, чтобы использовать занятые ей ресурсы. В этом случае состояние сохраняется и восстанавливается при пересоздании активити.
#State
– Да.
Поворот экрана – это частный случай Configuration Changes. Другой пример – изменение языка системы.
Кроме того, как было упомянуто постом выше, система может уничтожить активити, чтобы использовать занятые ей ресурсы. В этом случае состояние сохраняется и восстанавливается при пересоздании активити.
#State
Fragment
Fragment используется для отображения части UI на экране. Фрагмент создается внутри активити или внутри другого фрагмента.
Для создания и управления фрагментами используется FragmentManager.
Класс-наследник класса Fragment должен иметь дефолтный конструктор без параметров. Система использует этот конструктор при пересоздании фрагмента.
Начиная с API v28 системный класс Fragment – deprecated. Рекомендуется использовать Fragment из Support Library.
#Fragment
Fragment используется для отображения части UI на экране. Фрагмент создается внутри активити или внутри другого фрагмента.
Для создания и управления фрагментами используется FragmentManager.
Класс-наследник класса Fragment должен иметь дефолтный конструктор без параметров. Система использует этот конструктор при пересоздании фрагмента.
Начиная с API v28 системный класс Fragment – deprecated. Рекомендуется использовать Fragment из Support Library.
#Fragment
Расскажите про способы добавления и переключения фрагментов. Как работать с бэкстэком?
Для управления фрагментами используются два класса: FragmentManager и FragmentTransaction.
Для получения
Методы класса
add() добавляет фрагмент на активити или другой фрагмент. Принимает аргументами
Другой способ добавить фрагмент - определить в лэйауте с помощью тега
remove() - операция, обратная
replace() удаляет все фрагменты, добавленные методом
Эти операции не выполняются сразу же после вызова методов. Метод commit() завершает транзакцию и выполняет операции транзакции.
Метод addToBackStack() добавляет транзакцию в Back Stack. Это значит, что когда пользователь нажмет Back транзакция откатится.
Метод popBackStack() удаляет транзакцию с верхушки бэкстэка, возвращает
Документация.
#Fragment
Для управления фрагментами используются два класса: FragmentManager и FragmentTransaction.
Для получения
FragmentManager
используются метод активити getSupportFragmentManager()) или метод фрагмента getChildFragmentManager()).FragmentManager
начинает транзакцию и возвращает объект FragmentTransaction
вызовом метода beginTransaction().Методы класса
FragmentTransaction
, которые необходимо знать - add()
, remove()
и replace()
.add() добавляет фрагмент на активити или другой фрагмент. Принимает аргументами
containerViewId
, в который добавляется фрагмент, инстанс фрагмента, тег.Другой способ добавить фрагмент - определить в лэйауте с помощью тега
<fragment>
.remove() - операция, обратная
add()
. Удаляет фрагмент.replace() удаляет все фрагменты, добавленные методом
add()
в заданный контейнер, и добавляет переданный аргументом фрагмент в контейнер. Параметр tag
может быть null.Эти операции не выполняются сразу же после вызова методов. Метод commit() завершает транзакцию и выполняет операции транзакции.
Метод addToBackStack() добавляет транзакцию в Back Stack. Это значит, что когда пользователь нажмет Back транзакция откатится.
addToBackStack()
применяется ко всем операциям в транзакции. Например следующий код добавляет транзакцию из трех операций в бэкстэк:fragmentTransaction
.add(R.id.fragmentContainer1, fragment1)
.add(R.id.fragmentContainer2, fragment2)
.replace(R.id.fragmentContainer1, fragment3)
.addToBackStack("tag")
.commit()
Метод popBackStack() удаляет транзакцию с верхушки бэкстэка, возвращает
true
, если бэкстэк хранил хотя бы одну транзакцию.Документация.
#Fragment
Важен ли порядок, в котором добавляются операции в FragmentTransaction?
Документация описывает два случая, в которых порядок важен:
1. Метод commit() должен вызываться последним.
2. Если несколько фрагментов добавляются в один контейнер, то порядок добавления определяет порядок в котором фрагменты отрисовываются в иерархии
Есть еще третий случай. Если метод addToBackStack(String name) вызывается несколько раз на одной транзакции, то транзакция добавляется в бэкстэк один раз с тегом, который передается в последнем вызове метода.
Пример:
Создается одна транзакция с тегом
Поведение нескольких
#Fragment
Документация описывает два случая, в которых порядок важен:
1. Метод commit() должен вызываться последним.
2. Если несколько фрагментов добавляются в один контейнер, то порядок добавления определяет порядок в котором фрагменты отрисовываются в иерархии
view
.Есть еще третий случай. Если метод addToBackStack(String name) вызывается несколько раз на одной транзакции, то транзакция добавляется в бэкстэк один раз с тегом, который передается в последнем вызове метода.
Пример:
supportFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, Fragment1())
.addToBackStack("add1")
.add(R.id.fragmentContainer, Fragment2())
.addToBackStack("add2")
.commit()
Создается одна транзакция с тегом
add2
, которая добавляет сразу два фрагмента. При нажатии кнопки Back оба фрагмента будут удалены.Поведение нескольких
addToBackStack()
не задокументировано и может различаться на разных версиях ОС, так что не забудьте на собеседовании сказать, что вы бы так никогда делать не стали.#Fragment
Чем отличается tag в методах add() и addToBackStack()?
Тег фрагмента используется в методе findFragmentByTag().
Tag в методе addToBackStack() это на самом деле не
Этот вопрос возник на собеседованиях, потому что когда только появились фрагменты было популярно писать такой код:
Сначала добавляется фрагмент с одинаковым тегом фрагмента и именем транзакции. Потом, используя имя-тег, можно получить инстанс фрагмента по позиции в бэкстэке.
Удобно? Нет! Не надо так делать.
#Fragment
Tag
в методе add() присваивается фрагменту. Fragment.getTag() возвращает этот тег.Тег фрагмента используется в методе findFragmentByTag().
Tag в методе addToBackStack() это на самом деле не
tag
, а name
. Имя транзакции, которое присваивается объекту BackStackEntry и возвращается методом getName().Этот вопрос возник на собеседованиях, потому что когда только появились фрагменты было популярно писать такой код:
val tag = "home_fragment"
supportFragmentManager.beginTransaction()
.add(R.id.fragmentContainer, HomeFragment, tag)
.addToBackStack(tag)
.commit();
…
val lastEntryIndex = supportFragmentManager.backStackEntryCount - 1
val tag = supportFragmentManager.getBackStackEntryAt(lastEntryIndex).name
val topFragment = supportFragmentManager.findFragmentByTag(tag)
Сначала добавляется фрагмент с одинаковым тегом фрагмента и именем транзакции. Потом, используя имя-тег, можно получить инстанс фрагмента по позиции в бэкстэке.
Удобно? Нет! Не надо так делать.
#Fragment
Метод FragmentManager.commit() – синхронный или нет?
– Асинхронный.
Это значит, что транзакция не выполняется во время вызова метода. commit() добавляет транзакцию в очередь главного потока и транзакция выполняется при первой возможности.
Чтобы выполнить транзакцию синхронно, можно воспользоваться методом commitNow() вместо
#Fragment
– Асинхронный.
Это значит, что транзакция не выполняется во время вызова метода. commit() добавляет транзакцию в очередь главного потока и транзакция выполняется при первой возможности.
Чтобы выполнить транзакцию синхронно, можно воспользоваться методом commitNow() вместо
commit()
или вызвать executePendingTransactions() после метода commit()
.#Fragment