ทำไม Android Developer ควรรู้และเข้าใจใน Activity Lifecycle

Activity Lifecycle เป็นสิ่งที่นักพัฒนาแอนดรอยด์จะต้องรู้จักเป็นอย่างแรกๆเลยก็ว่าได้ เพราะมันเป็นพื้นฐานที่สำคัญมากๆสำหรับนักพัฒนาแอนดรอยด์ เรียกได้ว่าถ้าผู้ที่หลงเข้ามาอ่านคนไหนไม่รู้จัก จะโคตรบาปมากๆเลยก็ว่าได้

แต่ก็กลับพบว่ายังมีนักพัฒนาอีกหลายๆคนเช่นกันที่รู้จักแต่ยังไม่ค่อยเข้าใจในการทำงานของ Activity Lifecycle ซักเท่าไร ทั้งๆที่รู้ว่ามันสำคัญ เพราะว่า…

ก็มันไม่ค่อยเห็นภาพตอนใช้งานน่ะสิ!!

ในสมัยที่บล็อกพึ่งหัดเขียนแอนดรอยด์ใหม่ๆ (เมื่อสิบล้านปีที่แล้ว) หลังจาก Bind View ระหว่าง TextView ใน XML กับใน Java เป็น ก็ได้ทำความรู้จักกับ Activity Lifecycle นี่แหละครับ ซึ่งก็บอกเลยว่า

ไม่เข้าใจเฟ้ย!!

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

นั่นล่ะ ตอนที่ได้รู้จัก Activity Lifecycle และได้มานั่งเข้าใจจริงๆในทีหลัง (เวลาของทั้งสองช่วงนั้นก็ห่างกันพอสมควร) ก็เป็นแบบนั้นเลย อาจจะเพราะว่ายังไม่เห็นภาพว่า Activity Lifecycle มันมีประโยชน์อะไรในช่วงเริ่มแรกน่ะแหละ

ซึ่งไปค้นหาข้อมูลจากที่ไหนๆส่วนใหญ่ก็มักจะเริ่มจากภาพแบบนี้

ลองดูที่ onCreate ก่อนเลยนะครับ onCreate จะทำงานก็ต่อเมื่อ… onStart ก็จะ… และ onResume… แต่ onPause… และ onStop จบท้ายด้วย onDestroy

ประมาณนั้นล่ะฮะ…

แต่สุดท้ายเจ้าของบล็อกก็รู้สึกว่ามันค้างคาอยู่ในใจเรื่อยมาน่ะแหละครับ ดังนั้นบทความนี้เจ้าของบล็อกจึงขอพูดเป็นเรื่อง “ทำไม Activity Lifecycle ถึงโคตรสำคัญ” ละกันครับ

สำหรับผู้ที่หลงเข้ามาอ่านที่พึ่งทำความรู้จักกับ Activity Lifecycle ก็สามารถอ่านจากบทความนี้ก่อนแล้วไปอ่านบทความจากที่อื่นเพิ่มเติมหรือจะอ่านจากที่อื่นแล้วมาอ่านเพิ่มเติมจากที่นี่ก็ได้เหมือนกันครับ

งั้นก็เริ่มจาก…

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

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

ยกตัวอย่างเช่น ผู้ใช้เปิดดูวีดีโอจากในแอพอยู่ แล้วเกิดมีโทรศัพท์แทรกเข้ามากลางคัน จึงต้องกดรับสาย แอพวีดีโอตัวนั้นก็ควรจะหยุดชั่วคราวใช่มั้ยล่ะ? และพอวางสายก็จะกลับเข้ามาในแอพ ซึ่งแอพก็จะเล่นวีดีโอต่อ (หรือไม่ก็ได้) ต่อจากเดิม

ลองนึกภาพว่าผู้ใช้กำลังดูหนังจนถึงช่วงจุดไคล์แม็กของหนัง แต่กลับมีคนโทรเข้ามาคั่นจังหวะ ถ้าหนังยังคงเล่นต่อ กว่าจะคุยโทรศัพท์เสร็จและวางสายกลับมาดูต่อก็เลยช่วงนั้นไปแล้ว (ถึงแม้จะย้อนกลับมาดูใหม่ได้ก็เถอะ)

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

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

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

ถ้าทำแบบนี้แอพของเจ้าของบล็อกอาจจะได้เข้าชิงรางวัลในตำแหน่งแอพที่ถูก Uninstall ได้ไวที่สุดก็เป็นได้…

น่าจะพอนึกภาพออกแล้วเนอะ? ว่าทำไมนักพัฒนาถึงต้องทำให้แอพทำงานตามสถานการณ์ต่างๆให้เหมาะสมด้วย

ทำยังไงให้แอพจัดการเรื่องเหล่านี้ได้สะดวกขึ้นล่ะ?

เมื่อรู้แล้วว่าแอพสามารถถูกการทำงานอื่นๆแทรกเข้ามาได้กลางคัน งั้นถ้าให้ Activity มี Event แบบนี้ล่ะ?

พอลองใส่โค้ดดูล่ะ

โค้ดซ้ำกันทุกอัน จนรู้สึกว่าการมี Event พวกนี้มันจัดการตามสถานการณ์ต่างๆได้ก็จริง แต่ก็ต้องมานั่งแปะโค้ดเดิมๆให้ครบทุกที่นี่สิ…

ก็นั่นล่ะฮะ ทำไมแอพแอนดรอยด์ถึงไม่ได้มี Event แบบนี้มาให้ใช้งาน เค้าจึงออกแบบ Lifecycle ขึ้นมาเพื่อให้จัดการกับสภาวะต่างของแอพได้ และไม่ต้องเขียนโค้ดซ้ำๆหลายๆที่

ซึ่งก็คือ onCreate, onStart, onResume, onPause, onStop, onRestart และ onDestroy นั่นเอง

  • onCreate จะทำงานควบคู่กับ onDestroy
  • onStart ทำงานควบคู่กับ onStop
  • onResume ทำงานควบคู่กับ onPause
  • onRestart ทำงานระหว่าง onStart กับ onStop

มันทำงานควบคู่กันยังไงน่ะ?

อันนี้ช่างมันก่อนละกันครับ ลองมาดู Lifecycle โดยปกติของแอพในเวลาที่กดเปิด/ปิดแอพกันก่อน

เมื่อเปิดแอพขึ้นมาก็จะเริ่มจาก onCreate > onStart > onResume แล้วผู้ใช้ก็จะใช้แอพตามปกติ แต่พอผู้ใช้กดปิดแอพ (กดปุ่ม Back) ก็จะกลายเป็น onPause > onStop > onDestroy

ทีนี้ลองเปลี่ยนจุดเริ่มต้นเป็นตอนที่แอพกำลังทำงานอยู่ (Running) แทนล่ะ? แล้วระหว่างที่แอพกำลังทำงานอยู่นั้นผู้ใช้เกิดกดปุ่ม Home เพื่อย่อแอพ หรือกดเปิดแอพตัวอื่นๆจาก Notification หรือกดปิดหน้าจอ แอพของผู้ที่หลงเข้ามาอ่านจะไม่ถูกปิด แต่ว่าจะถูกย่อแอพไว้แทน คือแอพตัวอื่นแสดงอยู่ ในขณะที่แอพของผู้ที่หลงเข้ามาอ่านก็ยังคงทำงานอยู่ และสามารถกลับมาทำงานต่อได้ทันทีเมื่อผู้ใช้กดเปิดขึ้นมาใหม่อีกครั้ง

แต่ระหว่างที่แอพนั้นย่ออยู่ล่ะ? ถ้าผู้ใช้ไปเปิดแอพอื่นๆหลายตัวหรือใช้งานหนักมากจน Memory ของเครื่องไม่พอในตอนนั้น?

สิ่งที่เกิดขึ้นคือแอพที่ถูกย่อไว้อยู่ก็จะเริ่มถูก “คืน Memory” เพื่อเอาไปหล่อเลี้ยงแอพที่ทำงานอยู่ครับ

ถ้าหนักสุดก็คือ Memory ถูกเอาไปใช้ในแอพอื่นจนหมดเกลี้ยงนี่แหละ แอพของผู้ที่หลงเข้ามาอ่านก็จะถูกปิดไปในทันที (ลาก่อน)

แต่ก็ไม่ได้หมายความว่าแอพของผู้ที่หลงเข้ามาอ่านจะถูกปิดไปเลยนะ เพราะก่อนที่มันจะถูกปิดลง ผู้ที่หลงเข้ามาอ่านสามารถเก็บค่าบางอย่างไว้ก่อนได้ พอเปิดขึ้นมาใหม่อีกครั้งก็คืนค่าที่เก็บไว้มาใช้งานต่อได้เลย ซึ่งอันนี้จะอยู่ในเรื่องของ Save/Restore Instance State แต่ยังไม่จำเป็นต้องรู้จักก็ได้ (ไว้ค่อยไปทำความรู้จักทีหลัง) และที่สำคัญคือเวลาผู้ใช้หมุนหน้าจอก็จะทำให้เกิดกรณีนี้เช่นกันครับ และถ้า Memory จะถูกเอาไปใช้งานจนหมดเกลี้ยง ก็จะทำให้แอพหรือ Activity ตัวนั้นๆถูกเคลียร์ทิ้งไปเลย

และถ้าระหว่างที่ผู้ใช้เปิดแอพอยู่แล้วเกิดมี Dialog จาก Activity อื่นแทรกขึ้นมากลางคันก็จะไม่จำเป็นต้องหยุดทำงาน เพราะว่า Dialog ไม่ได้แสดงเต็มหน้าจอจึงทำให้ยังเห็นด้านหลังเป็นแอพของผู้ที่หลงเข้ามาอ่านอยู่ และสามารถปิด Dialog และใช้งานแอพต่อได้ทันที

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

ยกตัวอย่างเช่น

จริงๆแล้วการใช้งานกล้องไม่ได้มีลำดับการทำงานแบบนี้หรอก แต่เอามาอธิบายแบบนี้ก็น่าจะเข้าใจได้ง่ายกว่าเนอะ

  • ถ้ามี Dialog จากที่อื่นแสดงแทรกขึ้นมา ก็จะหยุดภาพ Preview จากกล้อง และกลับมา Preview ต่อเมื่อ Dialog ถูกปิดลง
  • ถ้าสลับไปแอพอื่น ก็จะหยุด Preview ภาพและหยุดใช้งานกล้องชั่วขณะ เพื่อให้แอพอื่นๆสามารถใช้งานกล้องได้ และเมื่อสลับกลับมาก็จะเริ่มใช้งานกล้องใหม่และแสดงภาพ Preview ต่อ
  • ถ้าแอพถูกปิดก็จะเลิกใช้งานกล้องซะ มาเปิดคราวหน้าก็ตั้งค่าเพื่อใช้งานกล้องใหม่ทั้งหมด

ประมาณนี้แหละจ้า จะเห็นว่า Flow นี้สามารถทำให้แอพใช้งานกล้องในแต่ละสถานการณ์ได้อย่างเหมาะสม

ควรใส่คำสั่งอะไรตรงไหนบ้าง?

ปัญหาหลักของนักพัฒนาที่ไม่รู้ว่าจะใส่คำสั่งอะไรตรงไหนบ้าง ก็อาจจะเป็นส่วนหนึ่งที่ทำให้ไม่ได้จัดการ Activity ให้เป็นไปตาม Lifecycle ซักเท่าไรนัก ดังนั้นมาดูกันว่า Lifecycle ในแต่ละส่วนเนี่ย ควรเป็นคำสั่งแบบไหนบ้าง

onPause

  • หยุด Animation และ Action ต่างๆที่กำลังทำงานอยู่ชั่วขณะ
  • หยุดการใช้งานพวก Broadcast Receiver, เซ็นเซอร์ต่างๆที่เรียกใช้งานอยู่ หรือการทำงานที่ไม่จำเป็นต้องทำงาน ณ ตอนนั้น

onStop

  • หยุดการใช้งานคำสั่งบางอย่างที่เกี่ยวข้องกับ Hardware ชั่วคราว
  • หยุดการทำงานบางอย่างที่ควรทำงานตอนแสดงหน้าจอให้ผู้ใช้เห็นเท่านั้น
  • ทั้ง 2 ข้อข้างบนนี้ Instance ยังคงอยู่ (คืนแค่ Resource บางส่วน) และกลับมาเรียกทำงานใหม่ได้

onDestroy

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

แล้ว onCreate, onStart และ onResume ล่ะ?

ทั้ง 3 อย่างนี้ก็จะเป็นการทำงานที่ตรงข้ามกับคู่ของตัวเองนั่นเอง อะไรที่ใน onPause หยุดชั่วคราว ก็สั่งให้มันกลับมาทำงานต่อใน onResume ซะ เป็นต้น

สรุป

Activity Lifecycle ถือว่าสำคัญมากๆสำหรับนักพัฒนา เรียกได้ว่ามันเป็นหนึ่งใน Fundamental ของ Android Developer ครับ ซึ่งเรื่อง Activity Lifecycle ก็เป็นหนึ่งในคำถามยอดฮิตที่เจ้าของบล็อกมักจะใช้เวลาสัมภาษณ์งานในตำแหน่ง Android Developer เลยล่ะ (ไม่รู้ว่าที่อื่นถามมั้ย) เพราะถ้าพื้นฐานดี ก็จะตอบคำถามตรงนี้ได้ และนอกจากนี้มันยังบอกไปถึงการวาง Work Flow ด้วย ก็ทำให้ไล่ถามในระหว่างสัมภาษณ์เพิ่มได้เรื่อยๆนั่นเอง ถ้าจัดการกับการทำงานตาม Activity Lifecycle ต่างๆได้ไม่ดีก็หมายความว่ามีพื้นฐานที่ไม่ดีและการวางโครงสร้างของแอพก็ทำได้ไม่ดีด้วยเช่นกัน

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

สำหรับ Lifecycle ของ Activity จะเห็นได้ว่ามีการแบ่งระดับออกเป็น 3 ระดับ คือ

  • หยุดชั่วคราว (Pause) และ กลับมาทำงานต่อ (Resume)
  • หยุดทำงาน (Stop) และ เริ่มทำงาน (Start)
  • ยกเลิกการทำงาน (Destroy) และ เตรียมการทำงาน (Create)

ซึ่ง

  • Pause จะเป็นสถานะที่ Activity หยุดทำงานแต่ว่า View ยังแสดงอยู่ (มองเห็นหน้าจอ)
  • Stop จะเป็นสถานะตอนที่ Activity ไม่ได้แสดงหน้าจอให้เห็นแล้ว
  • Destroy ก็จะเป็นตอนที่ Activity ถูกทำลายทิ้ง

การจะรู้ได้ว่าจะต้องจัดการในระดับไหนบ้างก็อาจจะบอกได้ยาก เพราะแต่ละแอพนั้นก็มี Work Flow ที่แตกต่างกันออกไป ดังนั้นแนะนำว่าให้ลองถามตัวเองก่อน ว่า Work Flow ของผู้ที่หลงเข้ามาอ่านเป็นยังไง คำสั่งอะไรที่ควรจัดการเพิ่มเติมใน Lifecycle และควรอยู่ในระดับใดบ้าง หรือพูดง่ายๆว่าขึ้นอยู่กับแอพที่จะทำนั่นแหละครับ และที่สำคัญคือต้องรู้ว่า Lifecycle ของ Activity มันมีกรณีแบบไหนบ้างนั่นเอง

นอกจากนี้ยังมีเรื่องของ Save/Restore Instance State ที่ผู้ที่หลงเข้ามาอ่านควรรู้จักด้วย ซึ่งมันไม่ใช่ Activity Lifecycle หรอก แต่ทว่ามันเป็นเรื่องที่ส่งผลต่อการทำงานของ Activity Lifecycle จึงควรรู้จักไว้ (เรียนรู้ได้ก็ดี) เพื่อที่จะรับมือกับสภาวะต่างๆของแอพได้ดียิ่งขึ้น

พอจะเริ่มเข้าใจมากขึ้นแล้วเนอะ ว่าทำไมมันถึงสำคัญ