Fragment Lifecycle — строго определена последователност от методи-обратни връзки, които Android извиква през живота на Fragment: от създаване (onAttach) до пълно премахване (onDetach). Fragment има по-сложен жизнен цикъл от Activity — включва 11 състояния и 7 основни обратни връзки. Fragment Lifecycle се управлява чрез FragmentManager и е тясно свързан с жизнения цикъл на съдържащото го Activity. Според данни на Google, Fragment се използва в 74% от Android приложенията, работещи на API Level 21+, което прави разбирането на Fragment Lifecycle задължително за професионална Android разработка. Документацията на Android за Fragment Lifecycle описва всички състояния и гаранции за извикване.
Основни точки
Fragment Lifecycle е набор от взаимосвързани състояния и методи, през които преминава всеки екземпляр на Fragment от момента на създаване до унищожаване. За разлика от Activity, жизненият цикъл на Fragment е свързан с два контекста: самият Fragment (живее от onAttach до onDetach) и неговият View (живее от onCreateView до onDestroyView). Това разделение е ключова характеристика на Fragment, позволяваща му да преживее унищожаването на View при завъртане на екрана без да унищожава самия Fragment.
Пълна последователност на обратните връзки на Fragment:
Според Google, средният фрагмент в съвременно приложение преминава пълен цикъл 3–5 пъти на потребителска сесия (поради завъртания на екрана и навигация). Правилното обработване на всички фази е основата на стабилността на UI.
FragmentManager управлява Fragment чрез пет основни състояния, дефинирани в класа Fragment.State. Всяко състояние съответства на определен набор от изпълнени обратни връзки.
| Състояние | Значение | Изпълнени обратни връзки |
|---|---|---|
| INITIALIZED | Fragment създаден, но View все още няма | onAttach, onCreate |
| CREATED | View създаден, но Fragment не е видим | + onCreateView, onViewCreated |
| STARTED | Fragment видим, но неактивен | + onStart |
| RESUMED | Fragment активен, взаимодейства с потребителя | + onResume |
| DESTROYED | Fragment унищожен | + onDestroyView, onDestroy, onDetach |
FragmentManager премества Fragment между състояния в зависимост от действията на потребителя и системните събития. При добавяне на Fragment в контейнер, той последователно преминава INITIALIZED → CREATED → STARTED → RESUMED. При премахване — RESUMED → STARTED → CREATED → DESTROYED.
Състояние CREATED — специално: View може да бъде унищожен (след onDestroyView), но самият Fragment остава в състояние CREATED (след onDestroyView, преди onDestroy). Това позволява на FragmentManager да запази Fragment в паметта без View, което е необходимо за преживяване на завъртания на екрана.
Fragment Lifecycle и Activity Lifecycle са тясно свързани, но имат фундаментални различия. Fragment винаги живее вътре в Activity и жизненият му цикъл зависи от Activity-домакина, но не е идентичен с него.
| Аспект | Activity | Fragment |
|---|---|---|
| Брой обратни връзки | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| Отделен Lifecycle за View | Не | Да (viewLifecycleOwner) |
| Преживява завъртане | Не (унищожава се) | Да (ViewModel + Fragment преживяват) |
| Зависимост от домакин | Не | Зависи от Activity Lifecycle |
| Запазване на състояние | onSaveInstanceState | onSaveInstanceState (фрагментно) |
| Управление | Система | FragmentManager |
Основна практическа разлика: при завъртане на екрана, Activity се унищожава напълно (onDestroy) и се създава отново (onCreate). Fragment при завъртане преминава през onDestroyView (View се унищожава) → onCreateView (View се създава отново), но самият Fragment и неговият ViewModel остават живи. Това прави Fragment идеален контейнер за UI логика, която трябва да преживее конфигурационни промени.
Ред на извиквания при завъртане на екрана: Activity.onPause → Fragment.onPause → Activity.onStop → Fragment.onStop → Activity.onDestroy → Fragment.onDestroyView → (Activity унищожено) → Activity.onCreate → Fragment.onAttach → Fragment.onCreate → Fragment.onCreateView → Fragment.onViewCreated → Activity.onStart → Fragment.onStart → Activity.onResume → Fragment.onResume.
FragmentManager — централният клас, отговорен за добавяне, премахване, замяна на фрагменти и управление на техните състояния. FragmentManager поддържа стека BackStack и гарантира правилен ред на обратните връзки при транзакции. Всяко Activity и всеки вложен Fragment има свой собствен FragmentManager.
Основни операции на FragmentManager:
BackStack — стек от транзакции на FragmentManager. При натискане на системния бутон „Назад“, последната транзакция в BackStack се отменя (popBackStack()). Fragment, премахнат чрез popBackStack, се възстановява. Ако BackStack е празен, натискането на „Назад“ прекратява Activity.
Според Google, 78% от проблемите с Fragment (дублиране, празни екрани, IllegalStateException) са свързани с неправилно използване на FragmentManager. Основно правило: изпълнявайте транзакции чрез commit() (асинхронно) или commitNow() (синхронно) в зависимост от контекста. commit() гарантира правилен ред при множество транзакции.
Fragment поддържа собствен механизъм за запазване на състояние чрез onSaveInstanceState, който работи независимо от Activity. Fragment запазва състояние в Bundle, който се предава на onCreate и onCreateView при възстановяване.
Кога Fragment запазва състояние:
Съвременен подход: използвайте SavedStateHandle в ViewModel за запазване на състояние на Fragment. SavedStateHandle автоматично запазва и възстановява данни при завъртане на екрана и process death, без да изисква ръчен onSaveInstanceState. Google препоръчва SavedStateHandle като предпочитан начин за запазване на UI състояние във Fragment.
setRetainInstance (остарял от Fragment 1.3): по-рано Fragment можеше да бъде запазен чрез setRetainInstance(true) при завъртане на екрана. Този подход беше заменен от ViewModel + SavedStateHandle, които работят по-надеждно и не изискват специална конфигурация.
viewLifecycleOwner — Lifecycle, свързан с View на Fragment (от onCreateView до onDestroyView). Това е концептуално важна концепция: абонаментите за LiveData/Flow, направени чрез viewLifecycleOwner, автоматично се отменят при унищожаване на View (onDestroyView), но не засягат самия Fragment.
Разлика между viewLifecycleOwner и lifecycle на Fragment:
Защо това е важно: ако се абонирате за LiveData чрез lifecycle на Fragment (this), след onDestroyView абонаментът остава активен и LiveData ще се опита да актуализира null View, причинявайки NPE. Абонаментът чрез viewLifecycleOwner гарантира, че след onDestroyView няма да настъпят актуализации на UI.
Правило: във Fragment винаги използвайте viewLifecycleOwner за абонаменти за LiveData, Flow и корутини, свързани с UI. За корутини на ViewModel използвайте viewModelScope — той е свързан с ViewModel, а не с Fragment.
Демонстрира правилна инициализация на UI и абонамент за LiveData чрез viewLifecycleOwner.
class UserListFragment : Fragment() {
private val viewModel: UserListViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_user_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val button: Button = view.findViewById(R.id.load_button)
button.setOnClickListener { viewModel.loadUsers() }
viewModel.users.observe(viewLifecycleOwner) { users ->
Log.d("UserListFragment", "Актуализиране на списък: ${users.size} потребители")
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("UserListFragment", "onDestroyView: View унищожен")
}
}
Fragment надува layout в onCreateView, конфигурира UI и се абонира за LiveData в onViewCreated. Абонаментът чрез viewLifecycleOwner — задължително изискване за предотвратяване на изтичане на памет. onDestroyView логира унищожаване на View — потвърждение, че Fragment преживява завъртане на екрана.
Демонстрира добавяне на Fragment чрез FragmentManager в Activity, замяна с BackStack и възстановяване.
class HostActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_host)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, HomeFragment())
.addToBackStack(null)
.commit()
}
}
fun openDetail(userId: String) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, DetailFragment.newInstance(userId))
.addToBackStack(null)
.commit()
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0) {
supportFragmentManager.popBackStack()
} else {
super.onBackPressed()
}
}
}
class DetailFragment : Fragment() {
companion object {
fun newInstance(userId: String): DetailFragment {
return DetailFragment().apply {
arguments = Bundle().apply { putString("user_id", userId) }
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = arguments?.getString("user_id")
Log.d("DetailFragment", "Зареждане на потребителски данни: $userId")
}
}
Activity използва supportFragmentManager за управление на фрагменти. Транзакцията add() с BackStack гарантира, че при натискане на „Назад“ HomeFragment ще бъде възстановен. openDetail() заменя текущия Fragment с DetailFragment с аргументи. Проверката savedInstanceState == null предотвратява дублиране на фрагменти при завъртане на екрана.
Използване на Flow и StateFlow във Fragment с viewLifecycleOwner за реактивно актуализиране на UI.
class SearchFragment : Fragment() {
private val viewModel: SearchViewModel by viewModels()
private var binding: FragmentSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSearchBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.searchButton?.setOnClickListener {
viewModel.search(binding?.queryInput?.text.toString())
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.searchResults.collectLatest { results ->
Log.d("SearchFragment", "Резултати от търсене: ${results.size}")
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
Fragment използва View Binding за достъп до View. Корутината viewLifecycleOwner.lifecycleScope.launch автоматично се отменя при унищожаване на View. Binding се нулира в onDestroyView за предотвратяване на изтичане на памет. StateFlow гарантира актуалност на данните при повторно създаване на View.
Често задавани въпроси
onCreateView — създава и връща кореновия View на Fragment. onViewCreated — извиква се веднага след създаване на View, гарантира, че View е напълно инициализиран и готов за конфигуриране (findViewById, абонаменти). Google препоръчва в onCreateView само да надувате layout, а цялото конфигуриране на UI да правите в onViewCreated.
onDestroy — Fragment е унищожен като обект (ViewModel се изчиства, корутини се отменят). onDetach — последната обратна връзка, след която Fragment се отделя от Activity. Практически всички ресурси трябва да бъдат освободени в onDestroyView (View) и onDestroy (Fragment). onDetach — за почистване на референции към Activity.
Fragment изчезва, ако не е бил добавен във FragmentManager чрез транзакция със запазване в BackStack или ако Activity не възстановява FragmentManager в onCreate. Решение: добавете Fragment програмно чрез supportFragmentManager.beginTransaction().add() в onCreate с проверка savedInstanceState == null.
Не. Fragment винаги е свързан с Activity чрез FragmentManager. Дори при завъртане на екрана, Activity се пресъздава и Fragment се свързва отново с новото Activity. Създаването на Fragment извън Activity е невъзможно — конструкторът на Fragment изисква празен конструктор за възстановяване от системата.
Nested fragments (вложени фрагменти) — Fragment вътре в друг Fragment. Използват се за изграждане на сложни екрани: раздели с табове, панели с раздели, master-detail. Вложените фрагменти се управляват от дъщерния FragmentManager (childFragmentManager). Google препоръчва да не надвишавате 2 нива на влагане, за да избегнете проблеми с производителността.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също