Inactive — trạng thái chuyển tiếp trong vòng đời ứng dụng giữa Active và Background, trong đó ứng dụng hiển thị trên màn hình nhưng không nhận được sự kiện chạm. Chúng tôi giải thích cách Inactive xảy ra trên iOS và Android, phương thức delegate nào xử lý nó và cách xử lý chính xác các gián đoạn — cuộc gọi, thông báo và cử chỉ hệ thống.
Những điểm chính
Inactive là trạng thái trung gian trong vòng đời ứng dụng di động xảy ra trong quá trình chuyển tiếp giữa Active và Background. Ở trạng thái này, ứng dụng vẫn ở tiền cảnh và hiển thị cho người dùng, nhưng không nhận được sự kiện chạm, nhấn phím hoặc các sự kiện UI khác. Hệ thống chặn việc phân phối sự kiện đến ứng dụng, nhưng UI vẫn ở trên màn hình và không bị thu nhỏ.
Bản chất của Inactive là tạm thời. Trạng thái này kéo dài chính xác bằng thời gian gián đoạn hệ thống: từ 0,1 giây khi đóng nhanh Control Center đến vài giây trong cuộc gọi đến có màn hình cuộc gọi. Sau khi gián đoạn kết thúc, ứng dụng trở về Active hoặc chuyển sang Background nếu người dùng đã chuyển sang ứng dụng khác. Inactive là trạng thái duy nhất có thể chuyển tiếp theo cả hai hướng: quay lại Active hoặc tiếp tục sang Background.
Trên iOS, Inactive được quản lý tự động bởi hệ thống. Nhà phát triển không thể kéo dài hay rút ngắn thời gian ở Inactive — nó hoàn toàn được kiểm soát bởi UIApplication. Điều duy nhất nhà phát triển có thể làm là xử lý chính xác việc chuyển sang Inactive qua applicationWillResignActive và quay lại qua applicationDidBecomeActive. Trên Android, tương đương là onPause, mặc dù ngữ nghĩa khác nhau: onPause được gọi ngay cả khi một Activity bị che phủ một phần bởi thành phần khác.
Trên iOS, Inactive là một trạng thái riêng biệt trong vòng đời ứng dụng (một trong năm: Not Running, Active, Inactive, Background, Suspended). Trên Android không có tương đương trực tiếp — onPause báo hiệu rằng Activity đang mất tiêu điểm đầu vào nhưng có thể vẫn hiển thị (ví dụ: khi mở hộp thoại). Sự khác biệt chính: Inactive trên iOS là trạng thái toàn ứng dụng, trong khi onPause trên Android là trạng thái trên mỗi Activity. Trong đa cửa sổ Android, một Activity có thể ở onPause (không có tiêu điểm) trong khi Activity khác ở onResume (có tiêu điểm).
| Đặc điểm | iOS Inactive | Android onPause |
|---|---|---|
| UI hiển thị | Có | Có (một phần hoặc toàn bộ) |
| Sự kiện chạm | Không nhận | Không nhận |
| Thời gian | Cho đến khi gián đoạn kết thúc | Cho đến khi tiêu điểm quay lại hoặc vào nền |
| Trạng thái tiếp theo | Active hoặc Background | onResume hoặc onStop |
| Cấp độ | Ứng dụng (UIApplication) | Activity |
| Đa cửa sổ | Một scene hoạt động | Nhiều Activity ở onPause |
Inactive trên iOS xảy ra trong một số kịch bản được xác định chặt chẽ. Người dùng mở Control Center (vuốt xuống từ góc trên bên phải trên iPhone X+ hoặc vuốt lên trên các mẫu cũ). Người dùng mở Notification Center (vuốt xuống từ góc trên bên trái). Cuộc gọi đến — hệ thống hiển thị màn hình cuộc gọi trên ứng dụng. Quyền hệ thống được yêu cầu — định vị, micrô, máy ảnh, danh bạ. Trên iPad, Slide Over hoặc Split View được khởi chạy — scene hoạt động trở thành Inactive.
Trên Android, onPause (tương đương của Inactive) xảy ra trong một loạt các tình huống rộng hơn. Mở hộp thoại (AlertDialog, DialogFragment). Một Activity bị che phủ một phần bởi Activity khác (ví dụ: Activity trong suốt để xác thực). Xoay màn hình (Activity được tạo lại, trình tự: onPause → onStop → onDestroy → onCreate → onStart → onResume). Chế độ đa cửa sổ — cửa sổ không hoạt động nhận onPause. Mỗi sự kiện này đều yêu cầu tạm dừng các hoạt động tốn nhiều tài nguyên để bảo toàn pin và hiệu suất.
import UIKit
extension Notification.Name {
static let systemInterruptionBegan = Notification.Name("systemInterruptionBegan")
static let systemInterruptionEnded = Notification.Name("systemInterruptionEnded")
}
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillResignActive(_ application: UIApplication) {
// Ứng dụng chuyển sang Inactive — gián đoạn hệ thống
print("Gián đoạn: Control Center, cuộc gọi hoặc cảnh báo hệ thống")
// Tạm dừng các hoạt động nhạy cảm với thời gian
pauseVideoPlayback()
stopContinuousDataCollection()
hideSensitiveInformation()
// Thông báo cho các thành phần
NotificationCenter.default.post(name: .systemInterruptionBegan, object: nil)
}
// Quay lại từ Inactive sang Active
func applicationDidBecomeActive(_ application: UIApplication) {
resumeVideoPlayback()
restartDataCollection()
NotificationCenter.default.post(name: .systemInterruptionEnded, object: nil)
}
private func pauseVideoPlayback() {
// Tạm dừng video để âm thanh không bị chồng chéo
}
private func hideSensitiveInformation() {
// Ẩn dữ liệu nhạy cảm khi chụp màn hình
// Control Center/App Switcher chụp ảnh màn hình UI
}
}Mã cho thấy cách xử lý Inactive trong UIKit. applicationWillResignActive tạm dừng video, dừng thu thập dữ liệu và ẩn thông tin nhạy cảm. Điều này quan trọng vì khi Control Center hoặc App Switcher được mở, hệ thống chụp ảnh màn hình UI hiện tại — người dùng có thể thấy dữ liệu bí mật trong bản xem trước. NotificationCenter cho phép các thành phần ứng dụng đăng ký sự kiện gián đoạn.
Trên iOS, Inactive được xử lý bởi một cặp phương thức: applicationWillResignActive (chuyển sang Inactive) và applicationDidBecomeActive (quay lại từ Inactive). Các phương thức này là một phần của UIApplicationDelegate và được gọi cho mỗi lần chuyển tiếp qua Inactive. Kể từ iOS 13 và UISceneDelegate, sceneWillResignActive và sceneDidBecomeActive đã được thêm vào cho các kịch bản đa cửa sổ.
Trên iPad với iOS 13+, một ứng dụng có thể có nhiều scene (cửa sổ). Mỗi scene có vòng đời riêng. Một scene có thể trở thành Inactive (người dùng đã chuyển sang scene khác) trong khi scene khác vẫn Active. Đây là sự khác biệt quan trọng so với iPhone, nơi Inactive là trạng thái toàn cục cho toàn bộ ứng dụng. Khi phát triển cho iPad, bạn cần xử lý Inactive cho từng scene riêng biệt.
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Scene trở nên không hoạt động
func sceneWillResignActive(_ scene: UIScene) {
// Trên iPad scene này mất tiêu điểm, nhưng scene khác có thể hoạt động
print("Scene mất hoạt động")
// Tạm dừng các tác vụ của scene này
pauseSceneSpecificOperations()
}
// Scene trở nên hoạt động
func sceneDidBecomeActive(_ scene: UIScene) {
print("Scene đã trở nên hoạt động")
resumeSceneSpecificOperations()
}
private func pauseSceneSpecificOperations() {
// Tạm dừng các hoạt động cụ thể cho scene này
}
private func resumeSceneSpecificOperations() {
// Tiếp tục các hoạt động khi tiêu điểm quay lại
}
}
// AppDelegate vẫn là điểm vào, ủy quyền cho các scene
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)
}
}Mã cho thấy SceneDelegate để xử lý Inactive ở cấp scene. sceneWillResignActive được gọi khi một cửa sổ cụ thể mất tiêu điểm — điều này có thể xảy ra khi chuyển đổi giữa các cửa sổ trên iPad. AppDelegate cấu hình UISceneConfiguration để hỗ trợ đa cửa sổ. Mỗi scene có trạng thái độc lập và nhà phát triển phải xử lý chúng riêng biệt.
Trên Android, tương đương trực tiếp của Inactive trên iOS là phương thức onPause() của vòng đời Activity. Nó được gọi khi Activity mất tiêu điểm đầu vào nhưng có thể vẫn hiển thị. Các kịch bản điển hình: mở hộp thoại, khởi chạy Activity khác trong cùng ứng dụng, cuộc gọi đến, nhấn nút Home hoặc Recents. Trong onPause, nhà phát triển nên tạm dừng các hoạt động tốn nhiều tài nguyên — hoạt ảnh, phát video, làm việc với máy ảnh.
Một điểm khác biệt quan trọng của Android là onPause luôn xảy ra trước onStop, nhưng không ngược lại. Một Activity có thể nhận onPause mà không có onStop (ví dụ: khi mở Activity trong suốt). Ngoài ra, onPause có thể được gọi nhiều lần trong vòng đời của Activity — mỗi khi chuyển tiêu điểm. Không đặt logic một lần trong onPause — sử dụng onStop cho các hoạt động cuối cùng và onPause chỉ để tạm dừng các hành động tương tác.
class VideoPlayerActivity : AppCompatActivity() {
private var exoPlayer: ExoPlayer? = null
private var currentPosition: Long = 0L
override fun onPause() {
super.onPause()
// Ứng dụng mất tiêu điểm — tạm dừng video
exoPlayer?.let { player ->
if (player.isPlaying) {
currentPosition = player.currentPosition
player.pause()
}
}
// Ẩn dữ liệu nhạy cảm (GDPR/màn hình ngân hàng)
if (window.decorView.systemUiVisibility and
View.SYSTEM_UI_FLAG_SECURE == 0
) {
hideSensitiveOverlay()
}
}
override fun onResume() {
super.onResume()
// Tiêu điểm quay lại — tiếp tục phát
exoPlayer?.seekTo(currentPosition)
exoPlayer?.play()
showSensitiveOverlay()
}
private fun hideSensitiveOverlay() {
// Đặt màn hình đen lên trên dữ liệu tài chính
}
}Mã cho thấy cách xử lý onPause đúng cho trình phát video. ExoPlayer bị tạm dừng khi mất tiêu điểm và vị trí phát được lưu lại. Khi quay lại onResume, trình phát tiếp tục phát từ vị trí đã lưu. Ngoài ra, một mẫu ẩn dữ liệu nhạy cảm được hiển thị — quan trọng cho các ứng dụng tài chính và y tế yêu cầu bảo vệ khỏi ảnh chụp màn hình khi chuyển đổi.
Quy tắc đầu tiên — ẩn dữ liệu bí mật khi chuyển sang Inactive. Khi người dùng mở Control Center hoặc App Switcher, iOS chụp ảnh màn hình hiện tại. Trên Android, tương tự, hệ thống hiển thị bản xem trước của Activity cuối cùng trong Recents. Sử dụng UIApplication.shouldSnapshotSecureApp (iOS 16+) hoặc FLAG_SECURE (Android) để bảo vệ màn hình bí mật.
Quy tắc thứ hai — tạm dừng hoạt ảnh và phương tiện. Inactive không phải thời điểm tốt để phát video hoặc hoạt ảnh vì người dùng không thể nhìn thấy chúng. Hơn nữa, phát ở nền có thể gây chồng chéo âm thanh với âm thanh hệ thống (nhạc chuông, thông báo). Dừng AVPlayer, ExoPlayer và UIView.animate khi chuyển sang Inactive và tiếp tục khi quay lại Active.
Quy tắc thứ ba — chặn nhập dữ liệu. Nếu ứng dụng có biểu mẫu nhập hoặc bản nháp, khóa bàn phím và trường nhập khi chuyển sang Inactive. Điều này ngăn nhập vô tình khi quay lại và bảo vệ khỏi việc chặn dữ liệu qua lớp phủ hệ thống. Trên iOS, từ bỏ người trả lời đầu tiên (view.endEditing(true)), trên Android — xóa tiêu điểm (currentFocus?.clearFocus()).
Quy tắc thứ tư — không thực hiện các hoạt động dài trong applicationWillResignActive hoặc onPause. Các phương thức này phải hoàn thành trong tích tắc. Nếu bạn cần lưu lượng lớn dữ liệu, hãy bắt đầu lưu trong luồng nền và hoàn thành trong applicationDidEnterBackground hoặc onStop. iOS cho 5 giây để thực thi applicationWillResignActive, sau đó hệ thống có thể buộc đóng ứng dụng.
import UIKit
final class SecureOverlayManager {
private var blurView: UIVisualEffectView?
func showBlurOverlay() {
guard let window = UIApplication.shared.keyWindow,
blurView == nil
else { return }
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blur.frame = window.bounds
blur.autoresizingMask = [.flexibleWidth, .flexibleHeight]
window.addSubview(blur)
blurView = blur
}
func removeBlurOverlay() {
blurView?.removeFromSuperview()
blurView = nil
}
}
// Sử dụng trong AppDelegate
func applicationWillResignActive(_ application: UIApplication) {
SecureOverlayManager().showBlurOverlay()
}
func applicationDidBecomeActive(_ application: UIApplication) {
SecureOverlayManager().removeBlurOverlay()
}Mã cho thấy cách triển khai lớp phủ an toàn để bảo vệ dữ liệu khi chuyển sang Inactive. UIVisualEffectView với hiệu ứng làm mờ được đặt lên trên toàn bộ UI khi chuyển sang Inactive và được gỡ bỏ khi quay lại Active. Điều này đảm bảo dữ liệu bí mật sẽ không hiển thị trong ảnh chụp màn hình App Switcher và Control Center. Tương tự, bạn có thể sử dụng UIImageView với logo cho lớp phủ có thương hiệu.
Các câu hỏi thường gặp
Có. Inactive là trạng thái trung gian bắt buộc trước khi chuyển sang Background trên iOS. Ứng dụng không thể đi từ Active trực tiếp sang Background — đầu tiên nó trở thành Inactive, sau đó Background. Trên Android, tương tự, onPause luôn được gọi trước onStop. Điều này cho nhà phát triển cơ hội chuẩn bị dữ liệu để lưu trước khi hoàn toàn vào nền.
Có. Trên iPad, khi khởi chạy Slide Over hoặc Split View, scene hoạt động trở thành Inactive, mặc dù không có gián đoạn hệ thống nào xảy ra — người dùng chỉ đang tương tác với scene khác. Đây là tính năng đa cửa sổ của iPadOS. Trên iPhone, Inactive luôn được kích hoạt bởi gián đoạn hệ thống — cuộc gọi, thông báo, Control Center hoặc Notification Center.
Thông thường từ 0,1 đến 2 giây. Trong cuộc gọi đến có màn hình cuộc gọi — lên đến 30 giây (cho đến khi người dùng trả lời hoặc từ chối cuộc gọi). iOS không giới hạn thời gian ở Inactive một cách cưỡng chế, nhưng hệ thống có thể đóng ứng dụng nếu nó không phản hồi sự kiện (watchdog). Trên Android, onPause không có giới hạn thời gian, nhưng nên hoàn thành công việc trong 200 ms.
ScenePhase.inactive — giá trị enum ScenePhase được đặt khi scene ở tiền cảnh nhưng không nhận sự kiện. Trong SwiftUI, bạn có thể quan sát nó qua @Environment(\.scenePhase) và phản ứng qua onChange. Khi chuyển từ .active sang .inactive, tạm dừng bộ đếm thời gian và hoạt ảnh. Khi quay lại .active, tiếp tục chúng. Khi chuyển sang .background, lưu trạng thái.
Không, chỉ cho các ứng dụng xử lý dữ liệu bí mật: ngân hàng, y tế, doanh nghiệp và ứng dụng nhắn tin có trò chuyện riêng tư. Đối với trò chơi và ứng dụng giải trí, không cần ẩn UI. Tuy nhiên, tạm dừng trò chơi và âm thanh khi Inactive là thực hành tốt để tránh chồng chéo âm thanh với thông báo hệ thống. Apple khuyến nghị ẩn dữ liệu nhạy cảm nhưng không yêu cầu.
Tổng kế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