Active — حالت فعال چرخه حیات برنامه iOS که در آن برنامه در پیشزمینه قرار دارد، رویدادهای لمسی را دریافت میکند و با کاربر تعامل دارد. بررسی میکنیم که حالت Active چگونه کار میکند، کدام متدهای نماینده UIApplicationDelegate مسئول آن هستند و چگونه انتقال بین Active و Inactive را در Swift به درستی مدیریت کنیم.
اصلی
Active — حالتی از چرخه حیات برنامه موبایل که در آن برنامه در پیشزمینه قرار دارد، روی صفحه دستگاه نمایش داده میشود و به طور فعال با کاربر تعامل دارد. در این حالت برنامه تمام رویدادهای لمسی، فشار کلیدها، دادههای شتابسنج و ژیروسکوپ را دریافت میکند و همچنین دسترسی کامل به پردازنده گرافیکی برای رندرینگ رابط کاربری دارد.
در iOS حالت Active بخشی از مدل پنج حالته چرخه حیات است: Not Running → Inactive → Active → Inactive → Background → Suspended → Not Running. در Android معادل آن حالت Activity پس از فراخوانی onResume است، زمانی که Activity در بالای پشته قرار دارد و ورودی کاربر را دریافت میکند. Active تنها حالتی است که در آن UI کاملاً تعاملی است و به حرکات، اسکرول، کلیکها و انیمیشنها واکنش نشان میدهد.
سیستم به برنامه در حالت Active بالاترین اولویت پردازنده و RAM را میدهد. این بدان معناست که سیستم چنین برنامهای را در کمبود منابع خاتمه نمیدهد — ابتدا فرآیندهای پسزمینه و معلق تخلیه میشوند. با این حال برنامه باید از منابع به طور موثر استفاده کند تا باتری را خالی نکند و باعث throttling CPU نشود.
برای کاربر Active حالت عادی کار با برنامه است. کاربر رابط کاربری را میبیند، میتواند دکمهها را فشار دهد، فرمها را پر کند، فید را اسکرول کند. هر قطعی این حالت (تماس، اعلان، کشیدن به بالا برای Control Center) برنامه را به Inactive منتقل میکند، پس از آن میتواند به Active بازگردد یا به Background برود.
iOS از UIApplicationMain برای مدیریت حالت استفاده میکند. هنگام انتقال به Active سیستم applicationDidBecomeActive را فراخوانی میکند. برای SwiftUI مکانیسم مشابه — مشاهده scenePhase از طریق Environment است. Android از onResume به عنوان نشانگر فعالیت Activity در پیشزمینه استفاده میکند. هر دو رویکرد تضمین میکنند که برنامه در مورد تغییر حالت اعلان دریافت میکند و میتواند رفتار خود را تطبیق دهد.
| پلتفرم | متد/رویداد | Swift (UIKit) | SwiftUI | Android (Kotlin) |
|---|---|---|---|---|
| iOS | انتقال به Active | applicationDidBecomeActive | scenePhase == .active | — |
| iOS | خروج از Active | applicationWillResignActive | scenePhase == .inactive | — |
| Android | انتقال به Active | — | — | onResume() |
| Android | خروج از Active | — | — | onPause() |
در iOS حالت Active از طریق UIApplicationDelegate پردازش میشود. متد اصلی — applicationDidBecomeActive(_:). این متد در اولین راهاندازی برنامه و هنگام بازگشت از Inactive فراخوانی میشود. این متد مکان ایدهآلی برای از سرگیری وظایفی است که هنگام رفتن به Inactive متوقف شدهاند: راهاندازی انیمیشنها، از سرگیری تایمرها، راهاندازی مجدد سنسورها، بررسی بهروزرسانی دادهها در سرور.
از iOS 13 اپل UISceneDelegate را برای پشتیبانی از چندین پنجره در iPad معرفی کرد. در این حالت applicationDidBecomeActive با sceneDidBecomeActive برای هر صحنه جایگزین میشود. برنامههایی که فقط از یک صفحه پشتیبانی میکنند میتوانند همچنان از UIApplicationDelegate استفاده کنند. هر دو رویکرد در لحظهای که برنامه یا صحنه فعال میشود فراخوانی میشوند.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// برنامه فعال شد — وظایف را از سر میگیریم
func applicationDidBecomeActive(_ application: UIApplication) {
resumeAnimations()
restartTimers()
refreshDataIfNeeded()
startObservingSensors()
}
// برنامه فعالیت را از دست میدهد — متوقف میکنیم
func applicationWillResignActive(_ application: UIApplication) {
pauseAnimations()
stopTimers()
saveDraftData()
}
private func resumeAnimations() {
UIView.animate(withDuration: 0.3) {
// از سرگیری انیمیشنهای UI
}
}
private func refreshDataIfNeeded() {
let lastRefresh = UserDefaults.standard.object(forKey: "lastRefresh") as? Date ?? .distantPast
if Date().timeIntervalSince(lastRefresh) > 300 {
fetchDataFromServer()
}
}
}کد پردازش صحیح Active در UIKit را نشان میدهد. applicationDidBecomeActive انیمیشنها، تایمرها را از سر میگیرد و بررسی میکند که آیا بهروزرسانی داده نیاز است یا خیر. applicationWillResignActive هر چیزی که ممکن است منابع مصرف کند را متوقف میکند و پیشنویسها را ذخیره میکند. چنین جفت متدی تضمین میکند که برنامه به تغییر حالت به درستی واکنش نشان میدهد.
در SwiftUI AppDelegate وجود ندارد — مدیریت حالت از طریق Environment<ScenePhase> انجام میشود. مقدار .active زمانی تنظیم میشود که صحنه در پیشزمینه و تعاملی است. SwiftUI به طور خودکار انیمیشنها و بهروزرسانیها را هنگام بازگشت به Active راهاندازی مجدد میکند. توسعهدهنده فقط باید onChange را مشترک شود تا عوارض جانبی را اجرا کند.
import SwiftUI
@main
struct ActiveDemoApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { oldPhase, newPhase in
switch newPhase {
case .active:
print("صحنه فعال شد")
resumeWork()
case .inactive:
print("صحنه غیرفعال شد")
pauseWork()
case .background:
print("صحنه به پسزمینه رفت")
saveState()
@unknown default:
break
}
}
}
private func resumeWork() {
// از سرگیری درخواستهای شبکه، انیمیشنها
}
private func pauseWork() {
// توقف وظایف حساس به زمان
}
private func saveState() {
// ذخیره وضعیت برنامه
}
}در SwiftUI scenePhase تنها منبع حقیقت در مورد وضعیت برنامه است. onChange امکان اجرای اقدامات را در هر انتقال فراهم میکند. مهم است به خاطر داشته باشید که scenePhase فقط در iOS 14+ و SwiftUI Lifecycle در دسترس است. برای برنامههای UIKit با صفحات SwiftUI از رویکرد UIApplicationDelegate استفاده کنید.
Active از چندین مسیر قابل دستیابی است. اولین و واضح — راهاندازی سرد: کاربر روی آیکون کلیک میکند، برنامه از Not Running از طریق Inactive به Active میرود. دوم — بازگشت از پسزمینه: کاربر از طریق App Switcher به برنامه بازمیگردد، برنامه از Inactive عبور کرده و Active میشود. سوم — بازگشت از قطع موقت: کاربر تماس را پایان میدهد، Control Center را میبندد یا به اعلان پاسخ میدهد — برنامه از Inactive به Active بازمیگردد.
Not Running → Inactive → Active — راهاندازی سرد. Background → Inactive → Active — بازگشت از پسزمینه. Inactive → Active — بازگشت از قطع موقت. در هر مورد applicationDidBecomeActive فراخوانی میشود، اما زمینه ممکن است متفاوت باشد. در راهاندازی سرد قبل از Active، didFinishLaunchingWithOptions فراخوانی میشود، در بازگشت از پسزمینه — willEnterForeground. توسعهدهنده میتواند از این تفاوتها برای انتخاب استراتژی بازیابی حالت استفاده کند.
| سناریو | مسیر انتقال | کالبکهای iOS | کالبکهای Android |
|---|---|---|---|
| راهاندازی سرد | Not Running → Active | didFinishLaunching → didBecomeActive | onCreate → onStart → onResume |
| بازگشت از پسزمینه | Background → Active | willEnterForeground → didBecomeActive | onRestart → onStart → onResume |
| بازگشت از Suspended | Suspended → Active | willEnterForeground → didBecomeActive | onRestart → onStart → onResume |
| پس از قطع | Inactive → Active | didBecomeActive | onResume |
نکته مهم: هنگام بازگشت از Suspended، iOS didFinishLaunchingWithOptions را فراخوانی نمیکند، زیرا برنامه قبلاً در حافظه بارگذاری شده است. این بدان معناست که کد مقداردهی اولیه قرار داده شده در این متد دوباره اجرا نمیشود. توسعهدهندگان اغلب این را فراموش میکنند و منطق بحرانی را برای هر دو سناریو به applicationWillEnterForeground یا applicationDidBecomeActive منتقل میکنند.
در Android معادل Active حالت Activity پس از فراخوانی onResume() است. Activity زمانی فعال در نظر گرفته میشود که در پیشزمینه قرار داشته باشد و ورودی کاربر را دریافت کند. این حالت مربوط به بالای پشته Activity است. اگر Activity دیگری روی آن ظاهر شود (حتی به طور جزئی)، Activity فعلی به حالت onPause میرود — معادل Inactive در iOS.
تفاوت کلیدی Android — چندین Activity میتوانند همزمان در حالت multi-window (split screen, freeform) فعال باشند. در این حالت Activity که کاربر با آن تعامل دارد فعال در نظر گرفته میشود و Activity مجاور معلق (onPause) است. iOS از multi-window در iPhone پشتیبانی نمیکند، فقط در iPad از طریق UIScene.
class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// برنامه فعال شد — وظایف را از سر میگیریم
resumeCameraPreview()
startLocationUpdates()
activateSensors()
}
override fun onPause() {
super.onPause()
// برنامه فعالیت را از دست میدهد — منابع را آزاد میکنیم
releaseCamera()
stopLocationUpdates()
deactivateSensors()
}
private fun resumeCameraPreview() {
// راهاندازی پیشنمایش دوربین (نیاز به مجوز دارد)
cameraProvider?.unbindAll()
cameraProvider?.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalyzer
)
}
private fun startLocationUpdates() {
val locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, 5000
).build()
locationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
}کد پردازش Active در Android را از طریق onResume/onPause نشان میدهد. onResume کار با دوربین، مکانیابی و سنسورها را از سر میگیرد — منابعی که باید فقط زمانی فعال باشند که برنامه برای کاربر قابل مشاهده است. onPause این منابع را آزاد میکند تا باتری هدر نرود. CameraX lifecycle-aware API به طور خودکار پیشنمایش را در onPause متوقف میکند.
قانون اول — عملیات سنگین را در applicationDidBecomeActive یا onResume انجام ندهید. بارگذاری داده، تجزیه JSON، کار با پایگاه داده — همه اینها باید ناهمزمان باشند و رشته اصلی را مسدود نکنند. از GCD (DispatchQueue) در iOS و Coroutines در Kotlin برای وظایف پسزمینه استفاده کنید. رشته اصلی فقط باید UI را بهروزرسانی کند و عملیات ناهمزمان را راهاندازی کند.
قانون دوم — وضعیت را در هر بازگشت به Active همگامسازی کنید. کاربر ممکن است تنظیمات را در برنامه سیستم تغییر داده باشد، اعلان push دریافت کرده باشد یا دادهها را در برنامه دیگری بهروزرسانی کرده باشد. اعتبار کش را هنگام انتقال به Active بررسی کنید — ممکن است دادهها در طول غیبت کاربر قدیمی شده باشند.
قانون سوم — به Active به عنوان تنها حالت اعتماد نکنید. برنامه ممکن است Active را رد کرده و مستقیماً از Not Running به Background برود (اگر در پسزمینه راهاندازی شود). در iOS این اتفاق هنگام راهاندازی از طریق اعلان push با گزینه content-available رخ میدهد. در Android — هنگام راهاندازی از طریق BroadcastReceiver. همیشه وضعیت فعلی را قبل از اجرای عملیات UI بررسی کنید.
قانون چهارم — از Activity Result API در Android به جای onActivityResult استفاده کنید. این امکان پردازش نتیجه فراخوانی دوربین، گالری یا مجوزها را مستقیماً در حالت Active بدون از دست دادن داده در هنگام بازآفرینی Activity فراهم میکند. برای iOS از async/await با UIApplication.shared.open برای دیالوگهای سیستمی استفاده کنید.
import UIKit
final class ActiveStateManager {
static let shared = ActiveStateManager()
private var isActive = false
func setActive(_ active: Bool) {
isActive = active
if active {
NotificationCenter.default.post(name: .appDidBecomeActive, object: nil)
}
}
func performWhenActive(_ block: @escaping () -> Void) {
if isActive {
block()
} else {
// اجرا را تا بازگشت به Active به تأخیر بینداز
NotificationCenter.default.addObserver(
forName: .appDidBecomeActive,
object: nil,
queue: .main
) { _ in
block()
}
}
}
}
extension Notification.Name {
static let appDidBecomeActive = Notification.Name("appDidBecomeActive")
}کد مدیر وضعیت Active را نشان میدهد که به سایر مؤلفههای برنامه اجازه میدهد وضعیت فعال فعلی را بررسی کنند. performWhenActive یا بلوک را بلافاصله اجرا میکند (اگر برنامه فعال است) یا اجرا را تا بازگشت به Active به تعویق میاندازد. این برای خدماتی که باید پس از بازگشت کاربر به برنامه عملی را انجام دهند مفید است.
سوالات متداول
این متد هر بار که برنامه به حالت فعال میرود فراخوانی میشود: در اولین راهاندازی، هنگام بازگشت از پسزمینه، پس از بستن Control Center یا Notification Center، پس از پایان تماس. در یک جلسه عادی ممکن است ۵–۱۰ بار بسته به اقدامات کاربر فراخوانی شود. مقداردهی اولیه یکبار مصرف را در این متد قرار ندهید.
Visible — یک اصطلاح غیررسمی به این معنی است که برنامه روی صفحه قابل مشاهده است اما ممکن است رویدادها را دریافت نکند (مثلاً تا حدی توسط پنجره دیگری در iPad پوشانده شده باشد). Active — حالت رسمی که در آن برنامه هم قابل مشاهده و هم تعاملی است. در iPhone برنامه Visible همیشه Active است، در iPad وضعیت Visible + Inactive ممکن است.
willEnterForeground هنگام بازگشت از پسزمینه فراخوانی میشود، اما برنامه هنوز فعال نیست — در Inactive قرار دارد. didBecomeActive پس از اینکه برنامه کاملاً تعاملی شد فراخوانی میشود. اگر نیاز به انجام عملی قبل از دیدن رابط توسط کاربر دارید — از willEnterForeground استفاده کنید. اگر بعد از نمایش — didBecomeActive.
خیر. Active به این معنی است که برنامه در پیشزمینه قرار دارد و روی صفحه نمایش داده میشود. بدون UI قابل مشاهده برنامه میتواند در Background یا Suspended باشد. استثنا — iPad multi-window، جایی که یک پنجره میتواند فعال باشد و دیگری نه، اما هر دو قابل مشاهده هستند. VoiceOver و ضبط صدا این قانون را تغییر نمیدهند.
در شبیهساز iOS Cmd+Shift+H را برای رفتن به صفحه اصلی فشار دهید (برنامه به Background میرود)، سپس دوباره روی آیکون برنامه کلیک کنید. از Cmd+L برای قفل کردن صفحه (willResignActive) و باز کردن قفل (didBecomeActive) استفاده کنید. برای تست Inactive، Control Center (Cmd+Shift+; برای صفحهکلید macOS) یا Notification Center را فراخوانی کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید