Fragment Lifecycle là một chuỗi các phương thức callback được xác định chặt chẽ mà Android gọi trong suốt vòng đời của Fragment: từ khi tạo (onAttach) đến khi xóa hoàn toàn (onDetach). Fragment có vòng đời phức tạp hơn Activity — nó bao gồm 11 trạng thái và 7 callback chính. Fragment Lifecycle được quản lý thông qua FragmentManager và liên kết chặt chẽ với vòng đời của Activity chứa nó. Theo Google, Fragment được sử dụng trong 74% ứng dụng Android chạy trên API Level 21+, khiến việc hiểu Fragment Lifecycle trở nên bắt buộc đối với phát triển Android chuyên nghiệp. Tài liệu Android về Fragment Lifecycle mô tả tất cả các trạng thái và đảm bảo gọi.
Những điểm chính
Fragment Lifecycle là một tập hợp các trạng thái và phương thức liên kết với nhau mà mỗi phiên bản Fragment trải qua từ khi tạo đến khi hủy. Không giống như Activity, vòng đời của Fragment gắn liền với hai ngữ cảnh: bản thân Fragment (tồn tại từ onAttach đến onDetach) và View của nó (tồn tại từ onCreateView đến onDestroyView). Sự tách biệt này là một tính năng chính của Fragment, cho phép nó sống sót qua việc hủy View khi xoay màn hình mà không hủy chính Fragment.
Trình tự đầy đủ các callback của Fragment:
Theo Google, fragment trung bình trong một ứng dụng hiện đại trải qua vòng đời đầy đủ 3–5 lần mỗi phiên người dùng (do xoay màn hình và điều hướng). Xử lý chính xác tất cả các giai đoạn là nền tảng của sự ổn định UI.
FragmentManager quản lý Fragment thông qua năm trạng thái chính, được định nghĩa trong lớp Fragment.State. Mỗi trạng thái tương ứng với một tập hợp các callback cụ thể đã được thực thi.
| Trạng thái | Ý nghĩa | Callback đã thực thi |
|---|---|---|
| INITIALIZED | Fragment đã được tạo, nhưng View chưa có | onAttach, onCreate |
| CREATED | View đã được tạo, nhưng Fragment không hiển thị | + onCreateView, onViewCreated |
| STARTED | Fragment hiển thị, nhưng không hoạt động | + onStart |
| RESUMED | Fragment hoạt động, tương tác với người dùng | + onResume |
| DESTROYED | Fragment đã bị hủy | + onDestroyView, onDestroy, onDetach |
FragmentManager di chuyển Fragment giữa các trạng thái dựa trên hành động của người dùng và sự kiện hệ thống. Khi thêm Fragment vào một bộ chứa, nó tuần tự đi qua INITIALIZED → CREATED → STARTED → RESUMED. Khi xóa — RESUMED → STARTED → CREATED → DESTROYED.
Trạng thái CREATED đặc biệt: View có thể bị hủy (sau onDestroyView), nhưng bản thân Fragment vẫn ở trạng thái CREATED (sau onDestroyView, trước onDestroy). Điều này cho phép FragmentManager giữ Fragment trong bộ nhớ mà không có View, cần thiết để sống sót qua xoay màn hình.
Fragment Lifecycle và Activity Lifecycle liên quan chặt chẽ nhưng có sự khác biệt cơ bản. Fragment luôn tồn tại bên trong Activity, và vòng đời của nó phụ thuộc vào Activity chủ, nhưng không giống hệt nó.
| Khía cạnh | Activity | Fragment |
|---|---|---|
| Số lượng callback | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| Lifecycle riêng cho View | Không | Có (viewLifecycleOwner) |
| Sống sót qua xoay màn hình | Không (bị hủy) | Có (ViewModel + Fragment sống sót) |
| Phụ thuộc vào chủ | Không | Phụ thuộc vào Activity Lifecycle |
| Lưu trạng thái | onSaveInstanceState | onSaveInstanceState (cấp Fragment) |
| Quản lý | Hệ thống | FragmentManager |
Sự khác biệt thực tế chính: khi xoay màn hình, Activity bị hủy hoàn toàn (onDestroy) và được tạo lại (onCreate). Fragment khi xoay trải qua onDestroyView (View bị hủy) → onCreateView (View được tạo lại), nhưng bản thân Fragment và ViewModel của nó vẫn sống. Điều này làm cho Fragment trở thành bộ chứa lý tưởng cho logic UI cần sống sót qua các thay đổi cấu hình.
Thứ tự gọi khi xoay màn hình: Activity.onPause → Fragment.onPause → Activity.onStop → Fragment.onStop → Activity.onDestroy → Fragment.onDestroyView → (Activity bị hủy) → Activity.onCreate → Fragment.onAttach → Fragment.onCreate → Fragment.onCreateView → Fragment.onViewCreated → Activity.onStart → Fragment.onStart → Activity.onResume → Fragment.onResume.
FragmentManager là lớp trung tâm chịu trách nhiệm thêm, xóa, thay thế các fragment và quản lý trạng thái của chúng. FragmentManager duy trì BackStack và đảm bảo thứ tự callback chính xác trong các giao dịch. Mỗi Activity và mỗi Fragment lồng nhau có FragmentManager riêng.
Các hoạt động chính của FragmentManager:
BackStack là ngăn xếp giao dịch của FragmentManager. Khi nhấn nút Quay lại của hệ thống, giao dịch cuối cùng trong BackStack được hoàn tác (popBackStack()). Fragment bị xóa qua popBackStack được khôi phục. Nếu BackStack trống, nhấn Quay lại sẽ kết thúc Activity.
Theo Google, 78% vấn đề với Fragment (trùng lặp, màn hình trống, IllegalStateException) liên quan đến việc sử dụng FragmentManager không đúng. Quy tắc chính: thực thi giao dịch qua commit() (bất đồng bộ) hoặc commitNow() (đồng bộ) tùy theo ngữ cảnh. commit() đảm bảo thứ tự chính xác khi có nhiều giao dịch.
Fragment hỗ trợ cơ chế lưu trạng thái riêng thông qua onSaveInstanceState, hoạt động độc lập với Activity. Fragment lưu trạng thái vào Bundle được truyền đến onCreate và onCreateView khi khôi phục.
Khi Fragment lưu trạng thái:
Cách tiếp cận hiện đại: sử dụng SavedStateHandle trong ViewModel để lưu trạng thái Fragment. SavedStateHandle tự động lưu và khôi phục dữ liệu khi xoay màn hình và quy trình kết thúc, không cần onSaveInstanceState thủ công. Google khuyến nghị SavedStateHandle là cách ưu tiên để lưu trạng thái UI trong Fragment.
setRetainInstance (không được dùng kể từ Fragment 1.3): trước đây Fragment có thể được giữ lại qua setRetainInstance(true) khi xoay màn hình. Cách tiếp cận này đã được thay thế bằng ViewModel + SavedStateHandle, hoạt động đáng tin cậy hơn và không yêu cầu cấu hình đặc biệt.
viewLifecycleOwner là một Lifecycle gắn với View của Fragment (từ onCreateView đến onDestroyView). Đây là một khái niệm quan trọng về cơ bản: các đăng ký LiveData/Flow được thực hiện qua viewLifecycleOwner tự động bị hủy khi View bị hủy (onDestroyView), nhưng không ảnh hưởng đến bản thân Fragment.
Sự khác biệt giữa viewLifecycleOwner và lifecycle của Fragment:
Tại sao điều này quan trọng: nếu bạn đăng ký LiveData qua lifecycle của Fragment (this), sau onDestroyView đăng ký vẫn hoạt động và LiveData sẽ cố gắng cập nhật View null, gây ra NPE. Đăng ký qua viewLifecycleOwner đảm bảo rằng sau onDestroyView sẽ không có cập nhật UI nào xảy ra.
Quy tắc: trong Fragment, luôn sử dụng viewLifecycleOwner cho các đăng ký LiveData, Flow và coroutine liên quan đến UI. Đối với coroutine của ViewModel, sử dụng viewModelScope — nó gắn với ViewModel, không phải Fragment.
Minh họa khởi tạo UI chính xác và đăng ký LiveData qua 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", "Đang cập nhật danh sách: ${users.size} người dùng")
}
}
override fun onDestroyView() {
super.onDestroyView()
Log.d("UserListFragment", "onDestroyView: View đã bị hủy")
}
}
Fragment nội suy bố cục trong onCreateView, cấu hình UI và đăng ký LiveData trong onViewCreated. Đăng ký qua viewLifecycleOwner là yêu cầu bắt buộc để ngăn rò rỉ bộ nhớ. onDestroyView ghi nhật ký hủy View — xác nhận rằng Fragment sống sót qua xoay màn hình.
Minh họa thêm Fragment qua FragmentManager trong Activity, thay thế với BackStack và khôi phục.
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", "Đang tải chi tiết người dùng: $userId")
}
}
Activity sử dụng supportFragmentManager để quản lý các fragment. Giao dịch add() với BackStack đảm bảo khi nhấn Quay lại, HomeFragment được khôi phục. openDetail() thay thế Fragment hiện tại bằng DetailFragment với tham số. Kiểm tra savedInstanceState == null ngăn trùng lặp fragment khi xoay màn hình.
Sử dụng Flow và StateFlow trong Fragment với viewLifecycleOwner để cập nhật UI phản ứng.
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", "Kết quả tìm kiếm: ${results.size}")
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
Fragment sử dụng View Binding để truy cập View. Coroutine viewLifecycleOwner.lifecycleScope.launch tự động bị hủy khi View bị hủy. Binding được đặt thành null trong onDestroyView để ngăn rò rỉ. StateFlow đảm bảo dữ liệu luôn mới khi View được tạo lại.
Các câu hỏi thường gặp
onCreateView tạo và trả về View gốc của Fragment. onViewCreated được gọi ngay sau khi View được tạo, đảm bảo View đã được khởi tạo hoàn toàn và sẵn sàng để cấu hình (findViewById, đăng ký). Google khuyến nghị chỉ nội suy bố cục trong onCreateView và thực hiện tất cả cấu hình UI trong onViewCreated.
onDestroy — Fragment bị hủy như một đối tượng (ViewModel bị dọn dẹp, coroutine bị hủy bỏ). onDetach là callback cuối cùng, sau đó Fragment tách khỏi Activity. Thực tế tất cả tài nguyên nên được giải phóng trong onDestroyView (View) và onDestroy (Fragment). onDetach dùng để dọn dẹp tham chiếu đến Activity.
Fragment biến mất nếu nó không được thêm vào FragmentManager qua giao dịch có lưu trong BackStack hoặc nếu Activity không khôi phục FragmentManager trong onCreate. Giải pháp: thêm Fragment theo chương trình qua supportFragmentManager.beginTransaction().add() trong onCreate với kiểm tra savedInstanceState == null.
Không. Fragment luôn gắn với Activity thông qua FragmentManager. Ngay cả khi xoay màn hình, Activity được tạo lại và Fragment được gắn lại vào Activity mới. Tạo Fragment bên ngoài Activity là không thể — hàm tạo của Fragment yêu cầu hàm tạo rỗng để hệ thống khôi phục.
Fragments lồng nhau (nested fragments) là Fragments bên trong một Fragment khác. Chúng được sử dụng để xây dựng các màn hình phức tạp: bảng tab, bảng có tab, master-detail. Fragments lồng nhau được quản lý bởi FragmentManager con (childFragmentManager). Google khuyến nghị không vượt quá 2 cấp độ lồng để tránh vấn đề hiệu suất.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm