onPause เป็นเมธอดวงจรชีวิตของ Android ที่ถูกเรียกเมื่อ Activity สูญเสียโฟกัสการป้อนข้อมูล แต่ยังคงมองเห็นได้บางส่วนบนหน้าจอ ระบบเรียก onPause ก่อนที่ Activity ใหม่จะปรากฏในเบื้องหน้า เมื่อเปิดกล่องโต้ตอบ เมื่อกดปุ่มแอปล่าสุด หรือเมื่อมีการโทรเข้า เมธอดนี้เป็นจุดสุดท้ายที่รับประกันสำหรับการบันทึกข้อมูลผู้ใช้ เนื่องจากหลังจาก onStop และ onDestroy ระบบสามารถยุติกระบวนการโดยไม่ต้องเรียกเพิ่มเติม ภายใน onPause นักพัฒนาจะบันทึกแบบร่าง หยุดแอนิเมชัน ปล่อยกล้อง และเขียน สถานะ UI ปัจจุบันไปยัง SharedPreferences สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวงจรชีวิต Activity แบบเต็ม อ่านบทความ Activity Lifecycle
ประเด็นสำคัญ
onPause เป็นเมธอดที่สี่ของวงจรชีวิต Activity ซึ่งถูกเรียกเมื่อหน้าจอสูญเสียโฟกัสการป้อนข้อมูล แต่ยังคงมองเห็นได้บางส่วนสำหรับผู้ใช้ เป็นสถานะ «เปลี่ยนผ่าน» ระหว่างการทำงานของแอปอย่างแอคทีฟและการซ่อน ระบบเรียก onPause ในสถานการณ์ต่อไปนี้: การเปิด Activity อื่น (หน้าจอใหม่ครอบคลุมหน้าจอปัจจุบันบางส่วน), การปรากฏของกล่องโต้ตอบ (Dialog, PopupWindow, Snackbar ไม่เรียก onPause แต่ DialogFragment เรียก), การกดปุ่มแอปล่าสุด, การโทรเข้า, การกดปุ่มเปิดปิดเพื่อล็อกหน้าจอ
ภารกิจหลักของ onPause คือการเตรียมแอปสำหรับความเป็นไปได้ที่จะถูกซ่อนหรือถูกทำลาย นี่คือจุดสุดท้ายในวงจรชีวิตที่นักพัฒนาสามารถมั่นใจได้ว่าโค้ดของเขาจะถูกดำเนินการก่อนที่ระบบจะดำเนินการเปลี่ยนไปยังคอมโพเนนต์อื่น หลังจาก onPause ระบบเรียก onStop (หาก Activity ถูกซ่อนทั้งหมด) หลังจากนั้นกระบวนการสามารถถูกยุติได้ทุกเมื่อโดยไม่ต้องแจ้งเพิ่มเติม
ตามเอกสารของ Android Developers (2025) onPause ควรเบาและรวดเร็วที่สุดเท่าที่จะเป็นไปได้ ตราบใดที่ onPause ยังไม่คืนการควบคุม ระบบไม่สามารถเริ่ม Activity ถัดไปได้ — หมายความว่าผู้ใช้จะเห็นความล่าช้าในการเปลี่ยนหน้าจอ Google แนะนำให้เสร็จสิ้น onPause ภายใน น้อยกว่า 100 มิลลิวินาที และการดำเนินการที่ยาวนานทั้งหมด (บันทึกในฐานข้อมูล เขียนลงดิสก์) ควรดำเนินการแบบอะซิงโครนัสผ่าน coroutines หรือ apply()
ใน Activity เมธอด onPause ถูกเรียกทุกครั้งที่หน้าจอหยุดทำงานแต่สามารถแสดงผลต่อได้บางส่วน ตัวอย่างทั่วไป: ผู้ใช้เปิดแอปแผนที่ แตะแชร์ตำแหน่ง และกล่องโต้ตอบเลือกระบบปรากฏเหนือแผนที่ Activity แผนที่ได้รับ onPause แต่ยังคงมองเห็นได้ใต้กล่องโต้ตอบ เมื่อกล่องโต้ตอบปิด แผนที่ได้รับ onResume โดยไม่ต้องเรียก onStart (หน้าจอไม่ได้ถูกซ่อนทั้งหมด)
class NoteEditorActivity : AppCompatActivity() {
private var binding: ActivityNoteEditorBinding? = null
private val prefs by lazy {
getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
}
override fun onPause() {
super.onPause()
// บันทึกแบบร่างบันทึก — แบบอะซิงโครนัส
prefs.edit()
.putString("draft_title", binding?.titleInput?.text.toString())
.putString("draft_body", binding?.bodyInput?.text.toString())
.putLong("draft_timestamp", System.currentTimeMillis())
.apply()
// หยุดวิดีโอชั่วคราว
binding?.videoPlayer?.pause()
// ปล่อยทรัพยากรเฉพาะ
releaseCamera()
releaseAudioFocus()
}
override fun onResume() {
super.onResume()
// กู้คืนแบบร่าง
binding?.titleInput?.setText(prefs.getString("draft_title", ""))
binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
acquireCamera()
acquireAudioFocus()
}
}
ตัวอย่าง NoteEditorActivity แสดงการจัดการ onPause ที่ถูกต้อง: บันทึกแบบร่างใน SharedPreferences ผ่าน apply(), หยุดไฟล์วิดีโอชั่วคราว, ปล่อยกล้องและโฟกัสเสียง แต่ละการเรียกเบาและรวดเร็ว โดยไม่บล็อกเธรด UI นานพอที่จะทำให้เกิด ANR สังเกตลำดับ: super.onPause() ถูกเรียกในบรรทัดแรก — ซึ่งรับประกันว่าลอจิกของระบบจะทำงานแม้เกิดข้อยกเว้นในโค้ดผู้ใช้
onPause เป็นจุดสุดท้ายที่นักพัฒนาสามารถบันทึกข้อมูลผู้ใช้ได้อย่างน่าเชื่อถือก่อนที่แอปจะถูกซ่อนหรือถูกฆ่าโดยระบบ หลังจาก onStop ระบบสามารถยุติกระบวนการหากหน่วยความจำต่ำ โดยไม่ต้องเรียก onDestroy เมธอด onSaveInstanceState() ถูกเรียกหลังจาก onPause แต่ Bundle ของมันไม่ได้มีไว้สำหรับการจัดเก็บระยะยาว — มันมีชีวิตอยู่จนถึง onCreate ถัดไปเท่านั้น
SharedPreferences กับ apply() แบบอะซิงโครนัสเป็นวิธีที่เหมาะสมที่สุดในการบันทึกข้อมูลจำนวนเล็กน้อยใน onPause แตกต่างจาก commit() ซึ่งเขียนข้อมูลแบบซิงโครนัสลงดิสก์และส่งคืน boolean, apply() จะบันทึกข้อมูลในหน่วยความจำทันทีและกำหนดเวลาการเขียนแบบอะซิงโครนัสลงดิสก์ ซึ่งใช้เวลาน้อยกว่า 1 มิลลิวินาทีบนเธรด UI เทียบกับ 10–100 มิลลิวินาทีของ commit()
override fun onPause() {
super.onPause()
// ❌ ไม่ดี: การเขียนแบบซิงโครนัสบล็อกเธรด
// prefs.edit().putInt("score", score).commit()
// ✅ ดี: การเขียนแบบอะซิงโครนัส
prefs.edit().putInt("score", score).apply()
// สำหรับออบเจกต์ที่ซับซ้อน — การแคชใน ViewModel
viewModel.saveState()
}
สำหรับข้อมูลที่มีโครงสร้าง (SQLite ผ่าน Room) ใน onPause ให้ใช้ coroutines กับ lifecycleScope ViewModelScope จะยกเลิก coroutine โดยอัตโนมัติเมื่อ ViewModel ถูกทำลาย ซึ่งป้องกันการเขียนไปยังฐานข้อมูลที่ปิดแล้ว การเขียนผ่าน Room กับ coroutines ใช้เวลา 5–15 มิลลิวินาทีและไม่บล็อกเธรด UI
// ใน ViewModel:
fun saveDraft(title: String, body: String) {
viewModelScope.launch(Dispatchers.IO) {
noteDao.insert(NoteDraft(title = title, body = body))
}
}
// ใน Activity.onPause:
viewModel.saveDraft(
binding?.titleInput?.text.toString(),
binding?.bodyInput?.text.toString()
)
onPause ใน Fragment จะถูกเรียกเมื่อ Fragment หยุดทำงานแต่สามารถมองเห็นได้ต่อไป สิ่งนี้เกิดขึ้นเมื่อ: Fragment ถูกแทนที่ด้วย Fragment อื่นผ่าน FragmentTransaction; Fragment ไม่ใช่หน้าปัจจุบันใน ViewPager; Activity ที่มี Fragment ได้รับ onPause การโต้ตอบระหว่าง onPause ของ Activity และ onPause ของ Fragment เป็นลำดับชั้นอย่างเคร่งครัด: Activity ได้รับ onPause ก่อน จากนั้น Fragments ทั้งหมดของมัน
class MapFragment : Fragment() {
private var mapController: MapController? = null
override fun onPause() {
super.onPause()
mapController?.stopFollowMode()
binding?.mapContainer?.alpha = 0.7f
}
override fun onResume() {
super.onResume()
binding?.mapContainer?.alpha = 1.0f
if (isVisible) {
mapController?.startFollowMode()
}
}
}
รายละเอียดการทำงานกับแผนที่ใน onPause: Google Maps และ Yandex Maps ใช้ทรัพยากร GPU จำนวนมากในโหมดติดตามแบบแอคทีฟ เมื่อสูญเสียโฟกัส ควรปิดแอนิเมชันแผนที่และลดความถี่ในการอัปเดตมาร์กเกอร์ และเมื่อโฟกัสกลับมา ให้คืนค่าฟังก์ชันการทำงานเต็มรูปแบบ สิ่งนี้ช่วยปรับปรุงประสิทธิภาพและลดการใช้พลังงานเมื่อสลับระหว่างหน้าจอ
หนึ่งในความสับสนที่พบบ่อยที่สุดในหมู่นักพัฒนา Android มือใหม่คือการไม่เข้าใจความแตกต่างระหว่าง onPause และ onStop มาตรวจสอบแต่ละสถานการณ์และกำหนดเมธอดที่ถูกต้อง
| สถานการณ์ | onPause | onStop |
|---|---|---|
| การเปิดหน้าต่างกล่องโต้ตอบ | ถูกเรียก | ไม่ถูกเรียก |
| การเปิด Activity ใหม่ (ไม่โปร่งใส) | ถูกเรียก | ถูกเรียก |
| การกดปุ่มโฮม | ถูกเรียก | ถูกเรียก |
| การล็อกหน้าจอ | ถูกเรียก | ถูกเรียก |
| การโทรเข้า | ถูกเรียก | ถูกเรียก |
| Activity โปร่งใสทับด้านบน | ถูกเรียก | ไม่ถูกเรียก |
| Split Screen (ครึ่งหน้าจอ) | ถูกเรียก | ไม่ถูกเรียก |
| PiP (ภาพซ้อนภาพ) | ถูกเรียก | ไม่ถูกเรียก |
กฎหลัก: onPause ถูกเรียกเมื่อสูญเสียโฟกัสใดๆ, onStop ถูกเรียกเมื่อสูญเสียการมองเห็นโดยสมบูรณ์เท่านั้น หาก Activity ยังคงมองเห็นได้ (แม้บางส่วน) onStop จะไม่ถูกเรียก สิ่งนี้สำคัญอย่างยิ่งสำหรับโหมด Split Screen, PiP และ Activity โปร่งใส — ที่นี่ onPause/onResume ทำงาน แต่ onStart/onStop ไม่ทำงาน
onPause เป็นเมธอดวงจรชีวิตที่สำคัญที่สุดในแง่ของเวลา เพราะมันบล็อกการเรนเดอร์ของ Activity ถัดไป ระบบรอให้ onPause ของ Activity ปัจจุบันเสร็จสิ้นก่อนที่จะแสดง Activity ใหม่ หาก onPause ใช้เวลานานกว่า 100 มิลลิวินาที ผู้ใช้จะสังเกตเห็นความล่าช้าในการเปลี่ยน หากนานกว่า 5 วินาที ระบบจะแสดง ANR
คู่มือประสิทธิภาพ Android ของ Google (2025) ให้คำแนะนำต่อไปนี้สำหรับ onPause: อย่าทำคำขอเครือข่าย — ควรยกเลิกหรือย้ายไปยัง WorkManager; อย่าเขียนไฟล์ขนาดใหญ่ลงดิสก์ — ใช้ BufferedWriter ในเธรดพื้นหลัง; อย่าดำเนินการค้นหา SQL ที่ซับซ้อน — การดำเนินการ Room ควรเป็นแบบอะซิงโครนัสผ่าน coroutines; หลีกเลี่ยงการสร้างออบเจกต์ใหม่ — การเก็บขยะใน onPause ทำให้ความล่าช้าแย่ลง; ใช้ apply() แทน commit() สำหรับ SharedPreferences
override fun onPause() {
super.onPause()
// ❌ ไม่ดี: คำขอ HTTP บล็อก UI
// val response = api.syncSave(data).execute()
// ❌ ไม่ดี: การเขียนไฟล์แบบซิงโครนัส
// FileOutputStream(file).write(data)
// ✅ ดี: การบันทึกแบบอะซิงโครนัส
lifecycleScope.launch {
withContext(Dispatchers.IO) {
api.saveData(data)
fileDao.write(data)
}
}
// ✅ ดี: การเขียนเบาใน SharedPreferences
prefs.edit().putString("key", value).apply()
}
การสร้างโปรไฟล์ onPause ผ่าน Android Studio Profiler (ร่องรอย CPU) แสดงเวลาดำเนินการที่แน่นอน หาก onPause ใช้เวลามากกว่า 100 ms Profiler จะเน้นเมธอดเป็นสีเหลือง และมากกว่า 500 ms เป็นสีแดง ในโครงการเชิงพาณิชย์ของ IT Sectr เราใช้การทดสอบ Macrobenchmark ที่ตรวจสอบเวลาเปลี่ยนระหว่าง Activity โดยอัตโนมัติและส่งสัญญาณการลดลงของประสิทธิภาพในไปป์ไลน์ CI
แม้นักพัฒนาที่มีประสบการณ์ก็ทำผิดพลาดใน onPause มาตรวจสอบปัญหาทั่วไปห้าข้อและวิธีแก้ไข
การเรียก Room DAO ด้วยคำค้นหาแบบซิงโครนัส (.executeAsObservable() โดยไม่มี coroutines) ใน onPause บล็อกเธรด UI เป็นเวลา 10–50 ms หากเกิด GC หรือการแข่งขันเขียนในเวลาเดียวกัน ความล่าช้าอาจถึง 200–500 ms วิธีแก้: ใช้ coroutines กับ Dispatchers.IO หรือ apply() สำหรับ SharedPreferences
onPause ไม่ใช่ที่สำหรับลงทะเบียน listener หากคุณลงทะเบียน BroadcastReceiver ใน onPause มันจะยังคงทำงานเมื่อ Activity ไม่มองเห็นอีกต่อไป การลงทะเบียนควรทำเฉพาะใน onStart/onResume และใน onPause/onStop — เฉพาะการยกเลิกการลงทะเบียนเท่านั้น ข้อยกเว้นคือ API ที่ขับเคลื่อนด้วย Intent ซึ่งต้องการการลงทะเบียนก่อนการเรียก
หากเกิดข้อยกเว้นที่ไม่ถูกจัดการใน onPause ระบบจะไม่เรียก onStop และ onDestroy Activity จะค้างในสถานะที่ไม่แน่นอน และ onResume เมื่อกลับมาอาจไม่สามารถกู้คืนทรัพยากรที่ปล่อยไปแล้วได้อย่างถูกต้อง วิธีแก้: ห่อการดำเนินการที่สำคัญใน try/catch ด้วยการบันทึกผ่าน Log.e()
ไม่จำเป็นต้องบันทึกใน onPause ข้อมูลที่สามารถกู้คืนได้ง่าย ตัวอย่างเช่น ผลลัพธ์คำขอ API จะถูกแคชใน Room หรือ DataStore ในเวลาที่ได้รับ ไม่ใช่ใน onPause บันทึกเฉพาะสิ่งที่ผู้ใช้ป้อนด้วยตนเองและไม่สามารถกู้คืนได้โดยอัตโนมัติ — ข้อความในฟิลด์ รายการที่เลือก ตำแหน่งเลื่อน
ควรเรียก super.onPause() แต่แตกต่างจาก onCreate การละเว้นมันไม่ทำให้เกิดการขัดข้องทันที ระบบ «ให้อภัย» การขาด super ใน onPause แต่เครื่องสถานะภายในเข้าสู่สถานะที่ไม่ถูกต้อง การเรียก onResume ครั้งถัดไปอาจไม่สามารถกู้คืนโฟกัสการป้อนข้อมูล ทำให้ Activity «ค้าง» เรียก super.onPause() โดยเร็วที่สุดเสมอ
คำถามที่พบบ่อย
การเรียก finish() ใน onPause จะยุติ Activity ทันทีหลังจากกลับจากเมธอด นี่เป็นสถานการณ์ที่ถูกต้องหากจำเป็นต้องปิดหน้าจอเมื่อสูญเสียโฟกัส (เช่น หน้าจอตรวจสอบสิทธิ์เมื่อย่อแอป) อย่างไรก็ตาม finish() เริ่มวงจรการยุติเต็ม: onStop onDestroy ซึ่งเพิ่มความล่าช้าในการเปลี่ยน ใช้ finish() ใน onPause เฉพาะเมื่อจำเป็นจริงๆ
onPause ใช้สำหรับบันทึกข้อมูลที่ควรอยู่รอดหลังการยุติกระบวนการ (แบบร่างใน SharedPreferences/Room) onSaveInstanceState ใช้สำหรับบันทึกสถานะ UI ชั่วคราว ที่จำเป็นจนถึง onCreate ถัดไปเท่านั้น (ตำแหน่งเลื่อน แท็บที่เลือก) Bundle ของ onSaveInstanceState จะไม่ถูกเก็บรักษาเมื่อแอปถูกยุติโดยสมบูรณ์ — มันมีอยู่ในหน่วยความจำเท่านั้น ข้อมูล onPause ถูกบันทึกบนดิสก์และอยู่รอดหลังจากรีบูต
ไม่แนะนำ การเปิดกล่องโต้ตอบหรือป๊อปอัปใน onPause ทำให้เกิด WindowLeakException หาก Activity ถูกยุติไปแล้ว หากจำเป็นต้องแสดงการแจ้งเตือนเมื่อสูญเสียโฟกัส ให้ใช้ NotificationManager (การแจ้งเตือนระบบ) — ปลอดภัยและผู้ใช้คาดหวัง สำหรับการดำเนินการที่เลื่อนออกไป ให้ใช้ AlarmManager หรือ WorkManager
onPause รับประกันว่าจะถูกเรียกก่อนที่ Activity จะหยุดทำงาน onStop อาจไม่ถูกเรียกหากระบบฆ่ากระบวนการเพื่อเพิ่มหน่วยความจำ — ในกรณีนี้ onDestroy ก็ไม่ถูกเรียกเช่นกัน onPause เป็นเมธอดเดียวหลังจาก onResume ที่ถูกเรียกเสมอ โดยไม่ขึ้นกับสาเหตุของการสูญเสียโฟกัส ดังนั้น ข้อมูลสำคัญทั้งหมดจึงถูกบันทึกใน onPause
สำหรับการทดสอบ onPause ใช้ Robolectric หรือ FragmentScenario จาก AndroidX Test FragmentScenario.create() moveToState(State.STARTED) moveToState(State.RESUMED) moveToState(State.STARTED) เรียก onPause ตามลำดับ จากนั้นตรวจสอบว่าข้อมูลถูกบันทึกใน SharedPreferences หรือกล้องถูกปล่อยผ่านออบเจกต์จำลอง Robolectric 4.12+ รองรับการจำลอง onPause/onResume โดยไม่ต้องใช้อุปกรณ์จริง
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ