11 November 2014

Let's Fragment - Lifecycle ของ Fragment

Updated on


        จากคร่าวก่อนที่ได้สอนถึงการใช้งาน Fragment เบื้องต้นแล้ว ทีนี้อยากจะขอแนะนำพื้นฐานอีกอย่างหนึ่งสำหรับ Fragment อีกตัวหนึ่ง นั่นก็คือ Lifecycle ของ Fragment นั่นเอง

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

        สำหรับบทความนี้ก็จะยังคงใช้โปรเจคเดิมที่เคยทำไว้ใน [Android Code] Let's Fragment - รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 1] และ [Android Code] Let's Fragment - รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 2] ดังนั้นถ้าผู้ที่หลงเข้ามาอ่านคนใดที่ไม่ยอมทำตามแล้วข้ามมาอ่านบทความนี้เลย ก็ขอให้กลับไปอ่านแล้วทำตามก่อนนะครับ XD

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

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

        • onCreate
        • onStart
        • onResume
        • onPause
        • onStop
        • onRestart
        • onDestroy

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

        • onAttach
        • onCreate
        • onCreateView
        • onActivityCreated
        • onStart
        • onResume
        • onPause
        • onStop
        • onDestroyView
        • onDestroy
        • onDetach

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


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

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

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 ก็จะกลายเป็นดังนี้

OneFragment.java
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 นั้นๆทำงาน

OneFragment.java
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 นะครับ



บทความที่เกี่ยวข้อง

        • Fragment Principle - มารู้จักกับ Fragment กันเถอะ~
        • Let's Fragment - เริ่มต้นง่ายๆกับ Fragment แบบพื้นฐาน
        • Let's Fragment - รู้จักกับ FragmentTransaction สำหรับการแสดง Fragment [ตอนที่ 2]
        • Let's Fragment - มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 1]
        • Let's Fragment - มาทำ View Pager กันเถิดพี่น้อง~ [ตอนที่ 2]