Активности не вызывают друг друга непосредственно. Вместо этого одна активность выражает намерение создать (или сделать что-то другое) активность
Намерение выражается в объекте Intent - объекте для запроса действия от другого компонента Android, например, для создания службы Service
Есть три основных способа использования намерений:
Создание новой активности:
val intent = Intent(this, MyNewActivity::class.java)
startActivity(intent)
Запуск службы
// Запуск Service
val intent = Intent(this, MyService::class.java)
intent.putExtra("action", "start_sync")
startService(intent)
// Остановка Service
stopService(intent)
Отправка широковещательного сообщения для его получения объектами BroadcastReceiver
// Отправка своего Broadcast
val intent = Intent("com.example.MY_PAGE_IS_LOADED")
sendBroadcast(intent)
К намерениям можно добавлять дополнительные данные с помощью метода putExtra
Также есть два типа намерений: явные и неявные
Явные (Explicit Intent) указывают на конкретный компонент по имени класса:
val intent = Intent(this, MyService::class.java)
Неявные (Implicit Intent) указывают только действие. Далее система сама находит подходящий компонент, например, открытие ссылки в браузере:
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(intent)
Само намерение состоит из:
component - имени конкретного компонента (для явных намерений)action - действия, которое нужно выполнить (например, ACTION_VIEW)data - URI данных для действияcategory - дополнительная информация о действииtype - явный тип данных намеренияextras - пары ключ-значение для передачи данных. Данные можно положить в намерение с помощью метода putExtra(key, value), а извлечь с помощью getStringExtra(key)flags - флаги управления поведением намеренияДля того, чтобы иметь возможность принимать неявные намерения от операционной системы, нужно указать их типы в манифесте AndroidManifest.xml. Например, можно ограничиться намерениями, действия которых заключаются в просмотре веб-контента по https://my_host
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="my_host" />
</intent-filter>
Такой блок называется фильтром намерений
Одной из архитектурных целей, которые разработчики Android преследовали во время разработки, - это предсказуемость навигации. Для этого на нижней навигационной панели каждого приложения есть три универсальные кнопки: кнопка “Назад”, кнопка “Домой” и кнопка “Недавние” (в более поздних версиях есть возможность заменить эти кнопки на управление свайпами)
Кнопка “Назад” необходима для того, чтобы возвращаться на предыдущий экран с интерфейсом, а, чтобы иметь такую возможность, нужно хранить их
Для этого в каждом приложении есть стек активностей - Back Stack
Когда нажимается иконка приложения в телефоне, вызывается стартовая активность, которая кладется на вершину стека. Если из нее перейти в другую, то она положиться на вершину, тем самым по кнопке “Назад” можно возвращаться к предыдущим активностям
При повторном открытии приложения стек восстанавливается, при этом одна активность может существовать в стеке несколько раз
Правила дополнения стека и снятия со стека для конкретной активности можно переопределить с помощью свойства launchMode в манифесте AndroidManifest.xml:
standard - при каждом запуске создается новый экземпляр активности, а активности может находиться в стеке несколько разsingleTop - если активность уже находится на вершине стека, то новый экземпляр не создаётся, вызывается метод onNewIntent(); если не на вершине, то создаётся новый экземплярsingleTask - разрешается только один экземпляр активности; если она существует, то вызывается onNewIntent(), а все активности поверх нее удаляютсяsingleInstance - разрешается иметь только эту активность в одной задаче, все остальные запущенные активности будут созданы в другой задаче. Под задачей подразумевается коллекция активностейИсточник: https://developer.android.com/guide/components/activities/tasks-and-back-stack
Вместе с режимом запуска можно использовать флаги, который используется при создании намерения и имеют больший приоритет, чем launchMode
FLAG_ACTIVITY_NEW_TASK - аналог singleTask, запускает в новом таскеFLAG_ACTIVITY_SINGLE_TOP - аналог singleTop, не создаёт новый экземпляр, если активности на вершинеFLAG_ACTIVITY_CLEAR_TOP - удаляет все активности поверх целевой, вызывает onNewIntent()Фрагмент (Fragment, androidx.fragment.app.Fragment) - модульная часть интерфейса внутри активности. Фрагмент имеет свой жизненный цикл и может быть переиспользован в разных активностей. Также несколько фрагментов могут находиться в одной активности одновременно
Фрагменты преимущественно созданы для того, чтобы изменять разметку в зависимости о размеров экрана устройства
Позднее начали использовать одну активность, которая просто меняет фрагменты. Из плюсов такого подхода можно выделить, что смена фрагментов тратит меньше времени, чем смена активностей на ~100 мс. Из недостатков то, что фрагменты не имеют своего стека активностей, поэтому навигация реализовывается силами разработчиков
У фрагмента такой жизненный цикл:
onAttach() - фрагмент прикрепляется к активностиonCreate() - фрагмент инициализируется, но без интерфейсаonCreateView() - здесь создаётся и возвращается представление (View) фрагмента. Обычно фрагмент представлен соответствующим XML-файлом, поэтому тут создается связывающий объект (binding), который представляет XML в виде объекта JVMonViewCreated() - представление создана, здесь инициализируется интерфейс, элементы и подписки на событияonStart() - фрагмент становится видимымonResume() - фрагмент активен, пользователь может с ним взаимодействоватьonPause() - фрагмент теряет фокусonStop() - фрагмент больше не виденonDestroyView() - представление фрагмента уничтожается, здесь освобождается связывающий объектonDestroy() - фрагмент уничтожаетсяonDetach() - фрагмент отсоединяется от активностиФрагмент создается как класс, наследующийся от Fragment:
class MyFragment : Fragment(R.layout.fragment_main)
Или вручную с созданием представления:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentMyBinding.inflate(inflater, container, false)
return binding.root
}
В само приложение фрагмент добавляется:
Статически через XML-файл:
<androidx.fragment.app.FragmentContainerView
android:name="com.example.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Динамически в коде:
supportFragmentManager.beginTransaction()
.replace(R.id.container, MyFragment())
.commit()
Здесь создается транзакция, которая позволяет условно атомарно изменить фрагмент. Для навигации между фрагментами у FragmentTransaction есть три основных метода:
add - добавляет фрагмент поверх существующего, оба живут одновременноremove - удаляет фрагментreplace - удаляет текущий фрагмент и добавляет новыйДругой метод, addToBackStack(), добавляет транзакцию в стек. При нажатии на кнопку “Назад” транзакция откатится и вернётся предыдущий фрагмент. Без этого нажатие кнопки “Назад” закроет активность
supportFragmentManager.beginTransaction()
.replace(R.id.container, DetailFragment())
.addToBackStack(null)
.commit()
Для передачи данных между фрагментами можно использовать объект Bundle:
companion object {
fun newInstance(name: String) = MyFragment().apply {
arguments = Bundle().apply { putString("key", name) }
}
}
Получить данные можно с помощью свойства arguments
val name = arguments?.getString("key")