onPause là một phương thức vòng đời Android được gọi khi một Activity mất tiêu điểm đầu vào nhưng vẫn hiển thị một phần trên màn hình. Hệ thống gọi onPause trước khi một Activity mới xuất hiện ở nền trước, khi mở hộp thoại, khi nhấn nút ứng dụng gần đây hoặc khi có cuộc gọi đến. Phương thức này là điểm cuối cùng được đảm bảo để lưu dữ liệu người dùng, vì sau onStop và onDestroy, hệ thống có thể kết thúc tiến trình mà không cần gọi thêm. Trong onPause, nhà phát triển lưu bản nháp, tạm dừng hoạt ảnh, giải phóng camera và ghi trạng thái UI hiện tại vào SharedPreferences. Để biết thêm chi tiết về vòng đời Activity đầy đủ, hãy đọc bài viết Activity Lifecycle.
Các điểm chính
onPause là phương thức thứ tư của vòng đời Activity, được gọi khi màn hình mất tiêu điểm đầu vào nhưng vẫn hiển thị một phần cho người dùng. Đây là trạng thái chuyển tiếp giữa ứng dụng đang chạy tích cực và bị ẩn. Hệ thống gọi onPause trong các kịch bản sau: mở một Activity khác (màn hình mới che một phần màn hình hiện tại), xuất hiện cửa sổ hộp thoại (Dialog, PopupWindow, Snackbar không kích hoạt onPause, nhưng DialogFragment thì có), nhấn nút ứng dụng gần đây, cuộc gọi đến, nhấn nút nguồn để khóa màn hình.
Nhiệm vụ chính của onPause là chuẩn bị ứng dụng cho khả năng bị ẩn hoặc bị hủy. Đây là điểm cuối cùng trong vòng đời mà nhà phát triển có thể chắc chắn rằng mã của họ sẽ được thực thi trước khi hệ thống tiếp tục chuyển đổi sang thành phần khác. Sau onPause, hệ thống gọi onStop (nếu Activity bị ẩn hoàn toàn), sau đó tiến trình có thể bị kết thúc bất cứ lúc nào mà không cần thông báo thêm.
Theo tài liệu của Android Developers (2025), onPause phải nhẹ và nhanh nhất có thể. Trong khi onPause chưa trả lại quyền điều khiển, hệ thống không thể khởi động Activity tiếp theo — điều này có nghĩa là người dùng thấy độ trễ trong quá trình chuyển đổi màn hình. Google khuyến nghị hoàn thành onPause trong ít hơn 100 mili giây và tất cả các thao tác dài (lưu vào cơ sở dữ liệu, ghi vào đĩa) phải được thực hiện bất đồng bộ thông qua coroutines hoặc apply().
Trong một Activity, phương thức onPause được gọi mỗi khi màn hình không còn hoạt động nhưng có thể tiếp tục hiển thị một phần. Một ví dụ điển hình: người dùng mở ứng dụng Bản đồ, nhấn Chia sẻ vị trí và một hộp thoại chọn ứng dụng hệ thống xuất hiện trên Bản đồ. Activity Bản đồ nhận onPause nhưng vẫn hiển thị bên dưới hộp thoại. Khi hộp thoại đóng lại, Bản đồ nhận onResume mà không cần gọi onStart (màn hình không bị ẩn hoàn toàn).
class NoteEditorActivity : AppCompatActivity() {
private var binding: ActivityNoteEditorBinding? = null
private val prefs by lazy {
getSharedPreferences("note_drafts", Context.MODE_PRIVATE)
}
override fun onPause() {
super.onPause()
// Lưu bản nháp ghi chú — bất đồng bộ
prefs.edit()
.putString("draft_title", binding?.titleInput?.text.toString())
.putString("draft_body", binding?.bodyInput?.text.toString())
.putLong("draft_timestamp", System.currentTimeMillis())
.apply()
// Tạm dừng video
binding?.videoPlayer?.pause()
// Giải phóng tài nguyên độc quyền
releaseCamera()
releaseAudioFocus()
}
override fun onResume() {
super.onResume()
// Khôi phục bản nháp
binding?.titleInput?.setText(prefs.getString("draft_title", ""))
binding?.bodyInput?.setText(prefs.getString("draft_body", ""))
acquireCamera()
acquireAudioFocus()
}
}
Ví dụ NoteEditorActivity minh họa cách xử lý onPause đúng cách: lưu bản nháp vào SharedPreferences qua apply(), tạm dừng tệp video, giải phóng camera và tiêu điểm âm thanh. Mỗi lần gọi đều nhẹ và nhanh, không chặn luồng UI đủ lâu để gây ra ANR. Hãy chú ý đến thứ tự: super.onPause() được gọi ở dòng đầu tiên — điều này đảm bảo logic hệ thống thực thi ngay cả khi xảy ra ngoại lệ trong mã người dùng.
onPause là điểm cuối cùng mà nhà phát triển có thể lưu dữ liệu người dùng một cách đáng tin cậy trước khi ứng dụng bị ẩn hoặc bị hệ thống kết thúc. Sau onStop, hệ thống có thể kết thúc tiến trình nếu thiếu bộ nhớ mà không gọi onDestroy. Phương thức onSaveInstanceState() được gọi sau onPause, nhưng Bundle của nó không dành cho lưu trữ dài hạn — nó chỉ tồn tại đến onCreate tiếp theo.
SharedPreferences với apply() bất đồng bộ là cách tối ưu để lưu một lượng nhỏ dữ liệu trong onPause. Không giống như commit(), ghi dữ liệu đồng bộ vào đĩa và trả về boolean, apply() lưu dữ liệu ngay vào bộ nhớ và lên lịch ghi bất đồng bộ vào đĩa. Thao tác này mất ít hơn 1 mili giây trên luồng UI so với 10–100 mili giây của commit().
override fun onPause() {
super.onPause()
// ❌ Tệ: ghi đồng bộ chặn luồng
// prefs.edit().putInt("score", score).commit()
// ✅ Tốt: ghi bất đồng bộ
prefs.edit().putInt("score", score).apply()
// Đối với đối tượng phức tạp — lưu cache trong ViewModel
viewModel.saveState()
}
Đối với dữ liệu có cấu trúc (SQLite qua Room) trong onPause, hãy sử dụng coroutines với lifecycleScope. ViewModelScope tự động hủy coroutine khi ViewModel bị hủy, ngăn ghi vào cơ sở dữ liệu đã đóng. Ghi qua Room với coroutines mất 5–15 mili giây và không chặn luồng UI.
// Trong ViewModel:
fun saveDraft(title: String, body: String) {
viewModelScope.launch(Dispatchers.IO) {
noteDao.insert(NoteDraft(title = title, body = body))
}
}
// Trong Activity.onPause:
viewModel.saveDraft(
binding?.titleInput?.text.toString(),
binding?.bodyInput?.text.toString()
)
onPause trong Fragment được gọi khi Fragment không còn hoạt động nhưng có thể vẫn hiển thị. Điều này xảy ra khi: một Fragment được thay thế bằng Fragment khác qua FragmentTransaction; Fragment không còn là trang hiện tại trong ViewPager; Activity chứa Fragment nhận onPause. Sự tương tác giữa onPause của Activity và onPause của Fragment có thứ bậc chặt chẽ: Activity nhận onPause trước, sau đó đến tất cả các Fragment của nó.
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()
}
}
}
Chi tiết cụ thể khi làm việc với bản đồ trong onPause: Google Maps và Yandex Maps tiêu thụ tài nguyên GPU đáng kể ở chế độ theo dõi chủ động. Khi mất tiêu điểm, nên tắt hoạt ảnh bản đồ và giảm tần suất cập nhật điểm đánh dấu, và khi lấy lại tiêu điểm, khôi phục đầy đủ chức năng. Điều này cải thiện hiệu suất và giảm mức tiêu thụ điện khi chuyển đổi giữa các màn hình.
Một trong những nhầm lẫn phổ biến nhất ở các nhà phát triển Android mới bắt đầu là không hiểu sự khác biệt giữa onPause và onStop. Hãy xem xét từng kịch bản và xác định phương thức chính xác.
| Kịch bản | onPause | onStop |
|---|---|---|
| Mở cửa sổ hộp thoại | Được gọi | Không được gọi |
| Mở Activity mới (không trong suốt) | Được gọi | Được gọi |
| Nhấn nút Home | Được gọi | Được gọi |
| Khóa màn hình | Được gọi | Được gọi |
| Cuộc gọi đến | Được gọi | Được gọi |
| Activity trong suốt ở trên | Được gọi | Không được gọi |
| Màn hình chia đôi (nửa màn hình) | Được gọi | Không được gọi |
| PiP (Hình trong hình) | Được gọi | Không được gọi |
Quy tắc chính: onPause được gọi khi mất tiêu điểm, onStop chỉ được gọi khi mất hoàn toàn khả năng hiển thị. Nếu Activity vẫn hiển thị (dù chỉ một phần), onStop không được gọi. Điều này cực kỳ quan trọng đối với chế độ Màn hình chia đôi, PiP và Activity trong suốt — ở đây onPause/onResume hoạt động, nhưng onStart/onStop thì không.
onPause là phương thức vòng đời quan trọng nhất về mặt thời gian vì nó chặn việc hiển thị Activity tiếp theo. Hệ thống chờ onPause của Activity hiện tại hoàn thành trước khi hiển thị Activity mới. Nếu onPause mất hơn 100 mili giây, người dùng nhận thấy độ trễ chuyển đổi; nếu mất hơn 5 giây, hệ thống hiển thị ANR.
Hướng dẫn Hiệu suất Android của Google (2025) đưa ra các khuyến nghị sau cho onPause: không thực hiện yêu cầu mạng — chúng nên bị hủy hoặc chuyển sang WorkManager; không ghi tệp lớn vào đĩa — sử dụng BufferedWriter trên luồng nền; không thực hiện truy vấn SQL phức tạp — các thao tác Room phải bất đồng bộ qua coroutines; tránh tạo đối tượng mới — thu gom rác trong onPause làm trầm trọng thêm độ trễ; sử dụng apply() thay vì commit() cho SharedPreferences.
override fun onPause() {
super.onPause()
// ❌ Tệ: yêu cầu HTTP chặn UI
// val response = api.syncSave(data).execute()
// ❌ Tệ: ghi đồng bộ vào tệp
// FileOutputStream(file).write(data)
// ✅ Tốt: lưu bất đồng bộ
lifecycleScope.launch {
withContext(Dispatchers.IO) {
api.saveData(data)
fileDao.write(data)
}
}
// ✅ Tốt: ghi nhẹ vào SharedPreferences
prefs.edit().putString("key", value).apply()
}
Hồ sơ onPause thông qua Android Studio Profiler (dấu vết CPU) hiển thị thời gian thực thi chính xác. Nếu onPause mất hơn 100 ms, Profiler đánh dấu phương thức bằng màu vàng và hơn 500 ms bằng màu đỏ. Trong các dự án thương mại tại IT Sectr, chúng tôi sử dụng các bài kiểm tra Macrobenchmark tự động kiểm tra thời gian chuyển đổi giữa các Activity và báo hiệu sự suy giảm hiệu suất trong đường ống CI.
Ngay cả các nhà phát triển giàu kinh nghiệm cũng mắc lỗi trong onPause. Hãy xem xét năm vấn đề điển hình và giải pháp của chúng.
Gọi Room DAO với truy vấn đồng bộ (.executeAsObservable() không có coroutines) trong onPause chặn luồng UI trong 10–50 ms. Nếu GC hoặc tranh chấp ghi xảy ra cùng lúc, độ trễ có thể lên tới 200–500 ms. Giải pháp: sử dụng coroutines với Dispatchers.IO hoặc apply() cho SharedPreferences.
onPause không phải là nơi để đăng ký trình lắng nghe. Nếu bạn đăng ký BroadcastReceiver trong onPause, nó sẽ vẫn hoạt động khi Activity không còn hiển thị. Việc đăng ký chỉ nên thực hiện trong onStart/onResume và trong onPause/onStop — chỉ hủy đăng ký. Ngoại lệ là các API dựa trên Intent yêu cầu đăng ký trước khi gọi.
Nếu xảy ra ngoại lệ chưa được xử lý trong onPause, hệ thống không gọi onStop và onDestroy. Activity bị kẹt ở trạng thái không xác định và onResume khi quay lại có thể không khôi phục đúng cách các tài nguyên đã giải phóng. Giải pháp: bọc các thao tác quan trọng trong try/catch với ghi nhật ký qua Log.e().
Không cần lưu trong onPause dữ liệu có thể dễ dàng khôi phục. Ví dụ: kết quả yêu cầu API được lưu vào bộ nhớ đệm trong Room hoặc DataStore tại thời điểm lấy, không phải trong onPause. Chỉ lưu những gì người dùng nhập thủ công và không thể khôi phục tự động — văn bản trong trường, mục đã chọn, vị trí cuộn.
super.onPause() nên được gọi, nhưng không giống như onCreate, việc bỏ qua nó không gây ra sự cố ngay lập tức. Hệ thống tha thứ cho việc thiếu super trong onPause, nhưng máy trạng thái nội bộ chuyển sang trạng thái không chính xác. Lần gọi onResume tiếp theo có thể không khôi phục được tiêu điểm đầu vào, khiến Activity bị đóng băng. Luôn gọi super.onPause() càng sớm càng tốt.
Các câu hỏi thường gặp
Gọi finish() trong onPause sẽ kết thúc Activity ngay sau khi trả về từ phương thức. Đây là một kịch bản hợp lệ nếu màn hình cần được đóng khi mất tiêu điểm (ví dụ: màn hình xác thực khi thu nhỏ ứng dụng). Tuy nhiên, finish() kích hoạt chu trình kết thúc đầy đủ: onStop onDestroy, điều này thêm độ trễ vào quá trình chuyển đổi. Chỉ sử dụng finish() trong onPause khi thực sự cần thiết.
onPause dùng để lưu dữ liệu cần tồn tại sau khi kết thúc tiến trình (bản nháp trong SharedPreferences/Room). onSaveInstanceState dùng để lưu trạng thái UI tạm thời chỉ cần đến onCreate tiếp theo (vị trí cuộn, tab đã chọn). Bundle của onSaveInstanceState không được bảo toàn khi ứng dụng kết thúc hoàn toàn — nó chỉ tồn tại trong bộ nhớ. Dữ liệu onPause được lưu vào đĩa và tồn tại sau khi khởi động lại.
Không được khuyến nghị. Mở hộp thoại hoặc cửa sổ bật lên trong onPause gây ra WindowLeakException nếu Activity đã kết thúc. Nếu cần hiển thị thông báo khi mất tiêu điểm, hãy sử dụng NotificationManager (thông báo hệ thống) — điều này an toàn và được người dùng mong đợi. Đối với các hành động trì hoãn, hãy sử dụng AlarmManager hoặc WorkManager.
onPause được đảm bảo gọi trước khi Activity ngừng hoạt động. onStop có thể không được gọi nếu hệ thống kết thúc tiến trình để giải phóng bộ nhớ — trong trường hợp này, onDestroy cũng không được gọi. onPause là phương thức duy nhất sau onResume luôn được gọi, bất kể lý do mất tiêu điểm. Do đó, tất cả dữ liệu quan trọng được lưu chính xác trong onPause.
Để kiểm tra onPause, sử dụng Robolectric hoặc FragmentScenario từ AndroidX Test. FragmentScenario.create() moveToState(State.STARTED) moveToState(State.RESUMED) moveToState(State.STARTED) gọi onPause tuần tự. Sau đó xác minh rằng dữ liệu đã được lưu trong SharedPreferences hoặc camera đã được giải phóng thông qua đối tượng giả. Robolectric 4.12+ hỗ trợ mô phỏng onPause/onResume mà không cần thiết bị vật lý.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm