Fragment Lifecycle คือลำดับของเมธอด callback ที่กำหนดไว้อย่างเคร่งครัดซึ่ง Android เรียกตลอดอายุของ Fragment: จากการสร้าง (onAttach) จนถึงการลบอย่างสมบูรณ์ (onDetach) Fragment มีวงจรชีวิตที่ซับซ้อนกว่า Activity — ประกอบด้วย 11 สถานะและ 7 callback หลัก 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 เอง
ลำดับ callback ของ Fragment โดยสมบูรณ์:
ตามข้อมูลของ Google เฉลี่ย fragment ในแอปพลิเคชันสมัยใหม่ต้องผ่านวงจรทั้งหมด 3–5 ครั้งต่อเซสชันผู้ใช้ (เนื่องจากการหมุนหน้าจอและการนำทาง) การจัดการที่ถูกต้องของทุกขั้นตอนคือรากฐานของความเสถียรของ UI
FragmentManager จัดการ Fragment ผ่านห้าสถานะหลัก ที่กำหนดในคลาส Fragment.State แต่ละสถานะสอดคล้องกับชุด callback เฉพาะที่ถูกดำเนินการ
| สถานะ | ความหมาย | Callback ที่ดำเนินการ |
|---|---|---|
| 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 |
|---|---|---|
| จำนวน callback | 7 (onCreate … onDestroy) | 11 (onAttach … onDetach) |
| Lifecycle แยกต่างหากสำหรับ View | ไม่มี | มี (viewLifecycleOwner) |
| อยู่รอดจากการหมุน | ไม่ (ถูกทำลาย) | ใช่ (ViewModel + Fragment อยู่รอด) |
| การพึ่งพาเจ้าบ้าน | ไม่ | ขึ้นอยู่กับ Activity Lifecycle |
| การบันทึกสถานะ | onSaveInstanceState | onSaveInstanceState (ระดับ Fragment) |
| การจัดการ | ระบบ | 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 คือคลาสกลางที่รับผิดชอบในการเพิ่ม ลบ เปลี่ยน fragments และจัดการสถานะของพวกมัน FragmentManager รักษา BackStack และรับประกันลำดับ callback ที่ถูกต้องระหว่างธุรกรรม แต่ละ 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 จะบันทึกและกู้คืนข้อมูลโดยอัตโนมัติเมื่อหมุนหน้าจอและกระบวนการตาย โดยไม่ต้องใช้ 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 จะพยายามอัปเดต View ที่เป็น null ทำให้เกิด NPE การสมัครผ่าน viewLifecycleOwner รับประกันว่าหลัง onDestroyView จะไม่มีการอัปเดต UI เกิดขึ้น
กฎ: ใน Fragment ให้ใช้ viewLifecycleOwner เสมอสำหรับการสมัครรับ LiveData Flow และ coroutine ที่เกี่ยวข้องกับ UI สำหรับ coroutine ของ ViewModel ให้ใช้ viewLifecycleOwner.lifecycleScope — มันผูกกับ 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 ขยายเลย์เอาต์ใน 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 เพื่อจัดการ fragments ธุรกรรม add() กับ BackStack รับประกันว่าเมื่อกดย้อนกลับ HomeFragment จะถูกกู้คืน openDetail() แทนที่ Fragment ปัจจุบันด้วย DetailFragment พร้อมอาร์กิวเมนต์ การตรวจสอบ savedInstanceState == null ป้องกันการทำซ้ำของ fragment เมื่อหมุนหน้าจอ
การใช้ 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 Coroutine viewLifecycleOwner.lifecycleScope.launch ถูกยกเลิกโดยอัตโนมัติเมื่อ View ถูกทำลาย Binding ถูกตั้งเป็น null ใน onDestroyView เพื่อป้องกันการรั่วไหล StateFlow รับประกันความสดของข้อมูลเมื่อ View ถูกสร้างใหม่
คำถามที่พบบ่อย
onCreateView สร้างและส่งคืน View หลักของ Fragment onViewCreated ถูกเรียกทันทีหลังจากการสร้าง View รับประกันว่า View ถูกเริ่มต้นอย่างสมบูรณ์และพร้อมสำหรับการกำหนดค่า (findViewById การสมัครรับ) Google แนะนำให้ขยายเลย์เอาต์ใน onCreateView เท่านั้น และทำการกำหนดค่า UI ทั้งหมดใน onViewCreated
onDestroy — Fragment ถูกทำลายเป็นออบเจ็กต์ (ViewModel ถูกล้าง Coroutine ถูกยกเลิก) onDetach คือ callback สุดท้าย หลังจากนั้น 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 (fragments ที่ซ้อนกัน) คือ Fragments ภายใน Fragment อื่น ใช้สำหรับสร้างหน้าจอที่ซับซ้อน: แผงแท็บ แผงที่มีแท็บ master-detail Nested fragments จัดการโดย FragmentManager ลูก (childFragmentManager) Google แนะนำไม่ให้เกิน 2 ระดับของการซ้อนเพื่อหลีกเลี่ยงปัญหาด้านประสิทธิภาพ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม