Fragment ตอนที่ 4 - Lifecycle ของ Fragment

อยู่ในระหว่างการปรับปรุง

Fragment ก็มี Lifecycle แบบเดียวกับ Activity แต่จะมีจำนวนเยอะกว่าและมีรูปแบบในการทำงานที่หลากหลายกว่า เพื่อให้รองรับการใช้งานในรูปแบบซับซ้อนที่ Activity ไม่สามารถทำได้

บทความในซีรีย์เดียวกัน

  • แยก Instance Creation และ View Creation ออกจากกัน
  • มี Attach และ Detach เพราะ Fragment ไม่จำเป็นต้องอยู่บน Activity ก็ยังคงมี Instance ในตัวมันเองได้อยู่
  • Lifecycle ของ Fragment จะทำงานสอดคล้องกับ Lifecycle ของ Activity โดยแบ่งเป็น 2 รูปแบบ คือ Fragment ที่แปะกับ Activity ตั้งแต่ต้น (กำหนดใน FragmentContainerView โดยตรง) กับ Fragment ที่แปะกับ Activity หลังจากที่ onResume แล้ว
  • ถ้าแปะ Fragment ใน Activity ตอนที่อยู่ในสถานะที่ไม่ถูกต้องจะทำให้เกิดปัญหา
  • เราสามารถเก็บ Instance ของ Fragment ไว้ในตัวแปรได้ แต่อย่าลืมว่าถ้า Fragment ถูก Recreate ค่าในตัวแปรก็จะยังเป็นของตัวเก่าอยู่
  •  Context ที่ Fragment เรียกใช้งานจะเป็น Context จาก Activity (เพราะ Fragment ไม่ได้สร้างมาจาก ContextWrapper แบบ Activity)


เนื่องจาก Fragment ถูกสร้างขึ้นมาเพื่อจัดการกับการแสดงการทำงานในแต่ละหน้าที่ทับซ้อนกันได้หลากหลาย โดยอยู่ภายใต้การทำงานของ Activity อีกทีหนึ่ง และ Fragment ก็สามารถผูกกับ Layout ได้ ดังนั้นจึงต้องมี Lifecycle ในตัวเองเหมือนกัน

Fragment นั้นจะมี Lifecycle ที่คล้ายกับ Activity อยู่บ้าง และก็จะมีบางอย่างแตกต่างกันนิดหน่อย แต่ผู้ที่หลงเข้าก็ควรจะเข้าใจการทำงานของมันเสียหน่อย เพื่อที่จะสามารถประยุกต์ใช้งานกับ Fragment ได้อย่างมีประสิทธิภาพ โดยเฉพาะการทำงานที่ต้องใช้ร่วมกับ Lifecycle

ถ้ายังจำกันได้ Lifecycle ของ Activity จะมีอยู่ด้วยกันหลักๆดังนี้ (ทวนอีกรอบกันลืม)

แต่ Lifecycle ของ Fragment จะมีดังนี้

เมื่อเปรียบเทียบกันก็จะประมาณนี้

กดดูภาพเต็มๆเอาเองละกันนะ

ก่อนอื่นเจ้าของบล็อกจะให้เปิดโปรเจคที่เคยทำไว้ในบทความก่อนหน้านี้ขึ้นมา โดยให้เปิดไฟล์ OneFragment.java ขึ้นมา

import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; public class OneFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_one, container, false); Button btn_close = (Button)rootView.findViewById(R.id.btn_close); btn_close.setOnClickListener(new OnClickListener() { public void onClick(View v) { getFragmentManager().popBackStack(); } }); return rootView; } }


ทำการ Override Method ที่เป็น Lifecycle ให้ครบ ถ้าเอาง่ายๆก็ให้คลิกพื้นที่นอก onCreateView แต่อยู่ใน OneFragment แล้วเลือกไปที่ Source > Override/Implement Methods…

ติ๊กเลือกเฉพาะ Method ที่เกี่ยวข้องกับ Lifecycle ให้ครบซะ (ตามที่พิมพ์ไว้ในข้างบน) แล้วกดปุ่ม OK

ตอนนี้โค๊ดใน OneFragment ก็จะกลายเป็นดังนี้

import android.app.Activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; public class OneFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_one, container, false); Button btn_close = (Button)rootView.findViewById(R.id.btn_close); btn_close.setOnClickListener(new OnClickListener() { public void onClick(View v) { getFragmentManager().popBackStack(); } }); return rootView; } public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } public void onAttach(Activity activity) { super.onAttach(activity); } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } public void onDestroy() { super.onDestroy(); } public void onDestroyView() { super.onDestroyView(); } public void onDetach() { super.onDetach(); } public void onPause() { super.onPause(); } public void onResume() { super.onResume(); } public void onStart() { super.onStart(); } public void onStop() { super.onStop(); } }


จากนั้นก็ใช้วิธียิง Log แบบโง่ๆเพื่อดูการทำงานของ Lifecycle โดยจะให้ Log แสดงข้อความเมื่อ Method นั้นๆทำงาน

import android.app.Activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; public class OneFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.i("Check", "OnCreateView"); View rootView = inflater.inflate(R.layout.fragment_one, container, false); Button btn_close = (Button)rootView.findViewById(R.id.btn_close); btn_close.setOnClickListener(new OnClickListener() { public void onClick(View v) { getFragmentManager().popBackStack(); } }); return rootView; } public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Log.i("Check", "onActivityCreated"); } public void onAttach(Activity activity) { super.onAttach(activity); Log.i("Check", "onAttach"); } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("Check", "onCreate"); } public void onDestroy() { super.onDestroy(); Log.i("Check", "onDestroy"); } public void onDestroyView() { super.onDestroyView(); Log.i("Check", "onDestroyView"); } public void onDetach() { super.onDetach(); Log.i("Check", "onDetach"); } public void onPause() { super.onPause(); Log.i("Check", "onPause"); } public void onResume() { super.onResume(); Log.i("Check", "onResume"); } public void onStart() { super.onStart(); Log.i("Check", "onStart"); } public void onStop() { super.onStop(); Log.i("Check", "onStop"); } }


จากนั้นให้ลองรันทดสอบดู โดยให้ดูที่หน้าต่าง LogCat ประกอบกับการทดสอบไปด้วย โดยให้ทดสอบตามนี้

1. กดปุ่ม One เพื่อเพิ่ม OneFragment บนหน้าจอ แล้วกดปุ่ม Back เพื่อปิด

2. กดปุ่ม One แล้วกดปุ่ม Close เพื่อปิด

3. กดปุ่ม One, กดปุ่ม Two และกดปุ่ม Three แล้วกด Back เพื่อปิดทีละอัน

4. กดปุ่ม One, กดปุ่ม Two และกดปุ่ม Three แล้วกด Close เพื่อปิดทีละอัน

5. กดปุ่ม Two, กดปุ่ม One และกดปุ่ม Three แล้วกด Close เพื่อปิดทีละอัน

6. กดปุ่ม One แล้วกดปุ่ม Home จากนั้นกดเปิดแอพฯเพื่อกลับเข้ามาใหม่

7. กดปุ่ม One แล้วกดปุ่มล็อคหน้าจอ จากนั้นก็เปิดหน้าจอเพื่อกลับเข้ามาใหม่

8. กดปุ่ม One แล้วกดปุ่ม Home จากนั้นไล่เปิดแอพฯอื่นๆไปเรื่อยๆแล้วกลับมาเปิดแอพฯใหม่

9. กดปุ่ม One แล้วหมุนหน้าจอจากแนวตั้งเป็นแนวนอน หรือแนวนอนเป็นแนวตั้ง

ให้ทำตามด้วยนะ ถ้าแอบลักไก่ไม่ทำแล้วอ่านอย่างเดียวจะจับตีมือหักเลย

จากข้อที่ 1 และข้อที่ 2 จะได้ผลลัพธ์ที่เหมือนกัน คือ เมื่อเปิด OneFragment เข้าไปบนหน้าจอจะเกิด Event ดังนี้

* onAttach > onCreate > onCreateView > onActivityCreated > onStart > onResume

และเมื่อกดปุ่ม Back หรือ Close เพื่อปิด OneFragment ทิ้ง ก็จะเกิด Event ดังนี้

* onPause > onStop > onDestroyView > onDestroy > onDetach

จากข้อที่ 3 และข้อที่ 4 จะได้ผลลัพธ์ที่เหมือนกัน และคล้ายกับข้อที่ 1 และ 2 เมื่อ OneFragment ถูกแสดงขึ้นมาก็จะเกิด Event ดังนี้

* onAttach > onCreate > onCreateView > onActivityCreated > onStart > onResume

แต่ทว่าจะมีการแสดง TwoFragment ทับแทนที่ OneFragment (Replace แบบมี BackStack) ก็จะทำให้เกิด Event กับ OneFragment ดังนี้

* onPause > onStop > onDestroyView

และเมื่อแสดง ThreeFragment มาทับแทนที่ TwoFragment ตรงนี้จะไม่มีอะไรเกิดขึ้นกับ OneFragment เพราะว่า OnFragment โดนแทนที่ตั้งแต่ TwoFragment แล้ว

และเมื่อกดปุ่ม Back หรือ Close เพื่อปิด ThreeFragment ก็จะยังไม่มีอะไรเกิดขึ้น เพราะที่จะแสดงขึ้นมาคือ TwoFragment อยู่ แต่เมื่อกดปุ่ม Back หรือ Close เพื่อปิด TwoFragment จะทำให้ OneFragment ถูกนำขึ้นมาแสดงทันที จึงทำให้เกิด Event ดังนี้

* onCreateView > onActivityCreated > onStart > onResume

และเมื่อกดปุ่ม Back หรือ Close เพื่อปิด OneFragment ก็จะเหมือนกับข้อที่ 1 และ 2 นั่นเอง คือ

* onPause > onStop > onDestroyView > onDestroy > onDetach

จากข้อที่ 5 จะไม่ต่างกับข้อที่ 3 และ 4 เลย เพียงแค่ว่าลำดับ Back Stack ของ Fragment ต่างกันเท่านั้น เพราะ OneFragment อยู่ใน Back Stack ชั้นที่ 2 (ของเดิมอยู่ชั้นล่างสุด)

จากข้อที่ 6 และข้อที่ 7 ตอนแรกที่กดเพื่อแสดง OneFragment ก็จะเหมือนกับข้ออื่นๆนั่นแหละ

* onAttach > onCreate > onCreateView > onActivityCreated > onStart > onResume

แต่พอกดปุ่ม Home เพื่อซ่อนแอพฯไว้หรือกดล็อคหน้าจอจะทำให้ OneFragment ที่แสดงอยู่ยังไม่หายไปแค่ถูกซ่อนไว้ จึงทำให้เกิด Event ดังนี้

* onPause > onStop

และเมื่อกลับเข้าแอพฯใหม่อีกครั้ง OneFragment ที่ซ่อนไว้ก็จะถูกเรียกกลับขึ้นมาแสดง

* onStart > onResume

และถ้ากดปิด OneFragment ทิ้งก็เหมือนกับที่ผ่านๆมาน่ะแหละ

จากข้อที่ 8 จะให้เปิด OneFragment ขึ้นมาก่อน ดังนั้น Event ก็เหมือนเดิมเลย

* onAttach > onCreate > onCreateView > onActivityCreated > onStart > onResume

และเมื่อกดปุ่ม Home เพื่อซ่อนแอพฯก็จะเกิด Event แบบข้อที่ 6 และ 7

* onPause > onStop

ทีนี้ที่เจ้าของบล็อกให้ลองก็คือลองเปิดแอพฯตัวอื่นๆไปเรื่อยๆ จน RAM เครื่องไม่พอ (เครื่อง RAM เยอะจะทดสอบได้ค่อนข้างยาก)

เมื่อ RAM หมดแอพฯที่ซ่อนไว้ก็จะถูกเรียกคืน RAM เพื่อให้ระบบนำไปใช้กับแอพฯที่กำลังทำงานอยู่ ดังนั้น Event ที่เก็บขึ้นกับ OneFragment ในเวลาที่แรมหมดแล้วโดนคืน RAM ไปก็น่าจะเป็นแบบนี้

* onPause > onStop > onDestroyView > onDestroy > onDetach

แต่เอาเข้าจริงกลับไม่ได้เป็นแบบนั้น

* onPause > onStop > onDestroyView > onDestroy > onDetach

เพราะ Event จะไม่เกิดขึ้นในเวลาที่โดนคืนแรม เพราะ Event จะไปเกิดขึ้นที่ Activity แทนเลย จึงไม่เกิด Event ในช่วงที่ Fragment โดนเคลียร์ RAM

แต่เมื่อ Activity มีการเปิดขึ้นมาเพื่อกลับมาทำงานใหม่อีกครั้งหลังจากโดนคืน RAM ไปหมด ก็จะเรียกเหล่า Fragment ที่เคยเปิดไว้ ดังนั้นก็จะเห็น OneFragment แสดงอยู่เหมือนเดิม แต่จะเกิด Event ดังนี้

* onAttach > onCreate > onCreateView > onActivityCreated > onStart > onResume

จึงสรุปได้ว่าช่วงที่โดนคืน RAM จะไม่เกิด Event ใดๆบน Fragment ให้เห็น แต่เมื่อแอพฯเปิดขึ้นมาอีกครั้ง Activity ก็จะดึง Fragment ที่เคยเปิดทิ้งไว้ขึ้นมาแสดงเองโดยอัตโนมัติ โดยที่จะเกิด Event บน Fragment เหมือนกับตอนสร้างใหม่ๆ

จากข้อที่ 9 เมื่อเกิดการหมุนหน้าจอขึ้น สิ่งที่เกิดขึ้นก็คือ Fragment จะถูกถอดออกไป แล้วสร้างขึ้นมาใหม่อีกครั้งเพื่อแสดงบนหน้าจอทิศทางนั้นๆ ดังนั้น Event ที่เกิดขึ้นก็จะเป็นดังนี้

* onPause > onStop > onDestroyView > onDestroy > onDetach > onAttach > onCreate > onCreateView > onActivityCreated > onStart > onResume

จึงสรุป Lifecycle ได้คร่าวๆดังนี้

onAttach : เมื่อ Activity เรียกใช้งาน Fragment นั้นๆ (ผูก Fragment เข้ากับ Activity)

onCreate : ทำการสร้าง Fragment ขึ้นมา (Initial)

onCreateView : กำหนด Layout ที่จะใช้กับ Fragment (ผูก Layout เข้ากับ Fragment)

onActivityCreated : Activity ถูกสร้างขึ้นและผูก Fragment เข้ากับ Activity เรียบร้อยแล้ว

onStart : Fragment แสดงขึ้นมาให้เห็นบนหน้าจอเรียบร้อยแล้ว (Visible)

onResume : Fragment พร้อมสำหรับ Interaction กับ User (Interactive)

onPause : Fragment หยุด Interaction กับ User (No Longer Interactive)

onStop : Fragment หยุดแสดงให้เห็นบนหน้าจอ (No Longer Visible)

onDestroyView : เคลียร์ Resource ที่เกี่ยวข้องกับการแสดงผล (Clean Up View’s Resource)

onDestroy : เคลียร์ข้อมูลทั้งหมดของ Fragment นั้นๆ (Cleanr Up Fragment’s State)

onDetach : Activity เลิกใช้งาน Fragment นั้นๆ (Fragment กับ Activity ไม่เชื่อมต่อกันแล้ว)

ในการเข้าใจถึง Lifecycle ของ Fragment ก็จะช่วยให้ผู้ที่หลงเข้ามาอ่านสามารถกำหนดคำสั่งต่างๆได้ถูกที่ถูกเวลาและถูกต้อง ซึ่งจะช่วยประยุกต์การทำงานร่วมกับ Fragment ได้หลากหลายยิ่งขึ้น

ซึ่งเรื่อง Lifecycle เป็นเรื่องที่ทำความเข้าใจได้ค่อยข้างยาก เมื่อเริ่มใช้งานในครั้งแรกๆ เสมือนตอนที่หัดเขียนใหม่ๆแล้วต้องมารู้จักกับ Activity Lifecycle นั่นเอง ซึ่งตอนศึกษาในช่วงแรกๆก็อาจจะไม่เข้าใจว่ามันจะต้องมี Lifecycle ไปทำอะไร จนได้เขียนไปเรื่อยๆก็ถึงจะรู้เองว่าทำไมถึงต้องมี Lifecycle ด้วย ซึ่ง Fragment Lifecycle ก็เหมือนกัน การจะเข้าใจการทำงานของมันได้ไม่ได้เกิดจากการอ่านเพียงอย่างเดียว แต่ต้องลองหัดเขียนไปเรื่อยๆประยุกต์ไปเรื่อยๆก็จะเข้าใจการทำงานของมันได้

ส่วนการประยุกต์ใช้ไว้จะทำให้ดูในเรื่องของ View Pager นะครับ