Not Running — حالت اولیه چرخه حیات یک برنامه موبایل است که در آن برنامه هنوز راهاندازی نشده یا کار خود را به پایان رسانده است. بیاموزید که چگونه سیستم iOS و Android این حالت را مدیریت میکنند، چه رویدادهایی منجر به انتقال از Not Running میشوند و چگونه راهاندازی و پایان برنامه را در Swift و Kotlin به درستی مدیریت کنید.
نکات اصلی
Not Running — حالت پایه چرخه حیات یک برنامه موبایل است که در آن برنامه در حافظه RAM دستگاه بارگذاری نشده و منابع سیستم را مصرف نمیکند. در iOS و Android این حالت به معنای عدم کامل وجود فرآیندها و رشتههای مرتبط با برنامه است. کاربر آیکون برنامه را روی دسکتاپ میبیند، اما خود برنامه فعال نیست و در لیست اخیرها قرار ندارد.
وقتی کاربر روی آیکون ضربه میزند، سیستم یک فرآیند جدید ایجاد میکند، کد قابل اجرا را در حافظه بارگذاری میکند و تمام ساختارهای داده لازم را مقداردهی میکند. این فرآیند راهاندازی سرد (cold start) نامیده میشود و از نظر زمان بارگذاری پرمصرفترین است.
سیستم میتواند برنامه را از هر حالت دیگری به Not Running منتقل کند. اگر برنامه در پسزمینه (Background) یا معلق (Suspended) باشد، سیستم عامل حق دارد در کمبود حافظه RAM برای وظایف اولویتدارتر آن را تخلیه کند — مثلاً برای یک برنامه فعال در پیشزمینه.
توسعهدهنده باید در نظر داشته باشد که برنامه میتواند در هر لحظه توسط سیستم پایان یابد، زمانی که در پسزمینه است. این بدان معناست که تمام دادههای ذخیرهنشده ممکن است از دست بروند. بنابراین ذخیره وضعیت در فروشگاههای کلید-مقدار (UserDefaults, SharedPreferences) یا در پایگاه داده محلی در انتقالهای Active به Background بسیار حیاتی است.
iOS از اولویتها بر اساس وضعیت فعلی برنامه استفاده میکند: Active بالاترین اولویت را دارد، سپس Inactive، Background، Suspended و در نهایت Not Running — حداقل اولویت. Android از سلسلهمراتب فرآیند مشابهی استفاده میکند: فرآیند Foreground اولویت OOM_ADJ = 0، فرآیند Visible = 100، فرآیند Service = 200، فرآیند Background = 300، فرآیند Empty = 400. هرچه مقدار بالاتر باشد، احتمال پایان یافتن فرآیند در کمبود حافظه بیشتر است.
| پلتفرم | وضعیت | اولویت تخلیه | توضیحات |
|---|---|---|---|
| iOS | Not Running | بالاترین | برنامه بارگذاری نشده — منبع سیستم مصرف نمیشود |
| iOS | Suspended | بالا | برنامه در حافظه است اما کد اجرا نمیشود — هدف اول برای تخلیه |
| iOS | Background | متوسط | برنامه وظیفه پسزمینه را انجام میدهد — پس از زمانبندی تخلیه میشود |
| iOS | Active | پایین | برنامه فعال — فقط در کمبود بحرانی حافظه تخلیه میشود |
| Android | Empty Process | بالاترین | فرآیند بدون مؤلفه فعال — اول حذف میشود |
| Android | Background Process | بالا | فرآیند پسزمینه بدون Activity قابل مشاهده |
| Android | Foreground Service | پایین | سرویس با اعلان — به ندرت پایان مییابد |
| Android | Foreground Process | حداقل | Activity فعال — آخر از همه پایان مییابد |
راهاندازی سرد (cold start) زمانی رخ میدهد که برنامه از Not Running مستقیماً به Active منتقل میشود. سیستم یک فرآیند جدید ایجاد میکند، کلاسها را بارگذاری میکند، فیلدهای ایستا را مقداردهی میکند، رشته اصلی را ایجاد میکند و چارچوب UI را راهاندازی میکند. در iOS این به معنای فراخوانی application(_:didFinishLaunchingWithOptions:)، در Android به معنای فراخوانی Application.onCreate() و Activity.onCreate() است. زمان راهاندازی سرد بسته به پیچیدگی برنامه میتواند از 200 میلیثانیه تا چند ثانیه باشد.
راهاندازی گرم (warm start یا hot start) — برنامه در حالت Suspended بوده و بدون بارگذاری مجدد کامل به کار بازمیگردد. سیستم آخرین پشته UI را از حافظه بازیابی میکند و کاربر کار را از همان مکان ادامه میدهد. راهاندازی گرم به طور قابل توجهی سریعتر از سرد است، زیرا بیشتر کدها قبلاً در حافظه بارگذاری شدهاند. در iOS راهاندازی گرم application(_:didFinishLaunchingWithOptions:) را فراخوانی نمیکند، فقط applicationWillEnterForeground و applicationDidBecomeActive را فراخوانی میکند.
تفاوت بین راهاندازی سرد و گرم برای تجربه کاربری حیاتی است. در راهاندازی سرد، توسعهدهنده باید اطمینان حاصل کند که راهاندازی تا حد امکان سریع انجام میشود — مقداردهی تنبل ماژولها، بارگذاری تأخیری منابع سنگین، به حداقل رساندن کار در رشته اصلی در شروع. Google توصیه میکند راهاندازی سرد بیشتر از 500 میلیثانیه نباشد، Apple — بیشتر از 400 میلیثانیه برای iOS.
// اندازهگیری زمان راهاندازی سرد در Android
class App : Application() {
private var startTime: Long = 0L
override fun onCreate() {
super.onCreate()
startTime = System.currentTimeMillis()
}
fun getStartupTime(): Long {
return System.currentTimeMillis() - startTime
}
}
// راهاندازی Activity با مقداردهی تنبل
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by lazy {
ViewModelProvider(this).get(MainViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// فقط حداقل لازم برای اولین فریم
setupNavigation()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
// مقداردهی سنگین پس از رندر
initializeHeavyModules()
}
}در مثال اندازهگیری زمان راهاندازی سرد در Android نشان داده شده است. Application.onCreate() در انتقال از Not Running به Active فراخوانی میشود. برچسب زمانی در شروع فرآیند ثبت میشود. Activity از مقداردهی تنبل از طریق lazy-delegate استفاده میکند تا اولین فریم را مسدود نکند. onPostCreate مکان بهینه برای مقداردهی ماژولهای سنگین است، زیرا UI قبلاً رندر شده است.
در iOS Not Running از طریق delegate UIApplicationDelegate مدیریت میشود. روشهای کلیدی: application(_:didFinishLaunchingWithOptions:) پس از راهاندازی سرد فراخوانی میشود، applicationWillTerminate(_:) قبل از پایان برنامه توسط کاربر فراخوانی میشود. با این حال سیستم میتواند برنامه را بدون فراخوانی applicationWillTerminate پایان دهد — مثلاً در پایان اضطراری یا تخلیه حافظه. iOS تضمین نمیکند که این روش فراخوانی شود، بنابراین دادهها باید در applicationDidEnterBackground ذخیره شوند.
کاربر میتواند دستی برنامه را با swipe در App Switcher پایان دهد. سیستم میتواند برنامه را از حافظه در پسزمینه تخلیه کند. برنامه میتواند به طور اضطراری (crash) پایان یابد. در همه موارد، تمام اشیاء ایجاد شده در طول راهاندازی نابود میشوند. وضعیتی که ذخیره نشده است، به طور جبرانناپذیری از دست میرود. در iOS 13+ برای ذخیره وضعیت توصیه میشود از NSUserActivity یا مکانیزم state restoration از طریق UIApplication.stateRestorationIdentifier استفاده کنید.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
// راهاندازی سرد: برنامه از Not Running منتقل شد
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// مقداردهی حداقل مجموعه سرویسها
setupAnalytics()
configureAppearance()
return true
}
// برنامه کار را پایان میدهد — فقط بستن دستی
func applicationWillTerminate(
_ application: UIApplication
) {
saveCriticalData()
}
// ذخیره دادهها قبل از رفتن به پسزمینه
func applicationDidEnterBackground(
_ application: UIApplication
) {
saveApplicationState()
}
private func saveCriticalData() {
UserDefaults.standard.synchronize()
}
private func saveApplicationState() {
let state = ["lastScreen": "main", "timestamp": Date()]
try? NSKeyedArchiver.archivedData(
withRootObject: state,
requiringSecureCoding: true
)
}
}کد مدیریت صحیح Not Running در iOS را نشان میدهد. applicationWillTerminate فقط در پایان دستی توسط کاربر فراخوانی میشود. ذخیره دادههای حیاتی در applicationDidEnterBackground تکرار میشود، زیرا این روش تضمین شده قبل از رفتن به پسزمینه فراخوانی میشود. State restoration اجازه میدهد پشته UI برای بازیابی بعدی در راهاندازی سرد ذخیره شود.
در Android Not Running به این معنی است که فرآیند برنامه وجود ندارد. سیستم Linux که Android بر پایه آن است، فرآیندها را از طریق مکانیزم Zygote مدیریت میکند. هنگام راهاندازی برنامه، Zygote یک فرآیند جدید fork میکند، Dalvik/ART را بارگذاری میکند و Application.onCreate() را فراخوانی میکند. در Android معادل مستقیمی برای applicationWillTerminate وجود ندارد — سیستم میتواند در هر لحظه بدون هشدار فرآیند را پایان دهد.
وقتی Activity برای اولین بار فراخوانی میشود، سیستم فرآیند، Application و Activity را از طریق زنجیره onCreate → onStart → onResume ایجاد میکند. اگر کاربر دکمه Back را فشار دهد، Activity نابود میشود (onDestroy) و فرآیند ممکن است توسط سیستم پایان یابد. تفاوت کلیدی با iOS: در Android فرآیند میتواند حتی بدون Activity های فعال ادامه وجود داشته باشد — مثلاً اگر Foreground Service در حال اجرا باشد یا BroadcastReceiver فعالی وجود داشته باشد.
// مدیریت Not Running از طریق SavedStateHandle در ViewModel
class MainViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
private const val KEY_LAST_SCREEN = "last_screen"
private const val KEY_USER_DATA = "user_data"
}
fun saveCurrentState(screen: String, data: String) {
savedStateHandle[KEY_LAST_SCREEN] = screen
savedStateHandle[KEY_USER_DATA] = data
}
fun restoreState(): AppState? {
val screen = savedStateHandle.get<String>(KEY_LAST_SCREEN)
val data = savedStateHandle.get<String>(KEY_USER_DATA)
return if (screen != null && data != null) {
AppState(screen, data)
} else null
}
}
// Application — اولین فراخوانی پس از Not Running
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
initCrashReporter()
initDependencyInjection()
}
}SavedStateHandle — مؤلفه Android Architecture Components که به طور خودکار وضعیت را در انتقال به Not Running ذخیره میکند و آن را در راهاندازی سرد بازیابی میکند. ViewModel ایجاد شده از طریق ViewModelProvider از چرخش صفحه و نابودی Activity جان سالم به در میبرد. در پایان فرآیند، دادههای SavedStateHandle به Bundle سریالیزه شده و در saved instance state ذخیره میشوند.
Not Running به چند دلیل رخ میدهد. کاربر دستی برنامه را میبندد. سیستم برنامه را در کمبود حافظه تخلیه میکند. برنامه با خطا به طور اضطراری پایان مییابد. در Android سیستم میتواند فرآیند را در بهروزرسانی انبوه برنامهها یا راهاندازی مجدد دستگاه پایان دهد. iOS میتواند برنامه را در زمانبندی وظیفه پسزمینه (معمولاً 30 ثانیه) پایان دهد.
| دلیل | iOS | Android | امکان جلوگیری |
|---|---|---|---|
| بستن دستی توسط کاربر | Swipe در App Switcher | Swipe از Recents | خیر — اقدام کاربر |
| کمبود حافظه | فعال شدن memory warning | onTrimMemory / LMK | تا حدی — بهینهسازی حافظه |
| Crash برنامه | NSException / سیگنال | UncaughtException / ANR | بله — مدیریت خطا و crash-reporting |
| زمانبندی وظیفه پسزمینه | 30 ثانیه برای Background task | 10 دقیقه برای JobScheduler | بله — برنامهریزی صحیح وظایف |
| راهاندازی مجدد سیستمعامل | فراخوانی applicationWillTerminate | Broadcast ACTION_SHUTDOWN | خیر — رویداد سیستمی |
| بهروزرسانی برنامه | رخ نمیدهد (iOS Sandbox) | فرآیند در بهروزرسانی APK پایان مییابد | خیر — بهروزرسانی سیستمی |
برای iOS از لاگ کنسول در applicationWillTerminate و applicationDidFinishLaunching استفاده کنید. در هر راهاندازی یک پرچم به UserDefaults اضافه کنید — اگر در شروع بعدی پرچم وجود نداشت، برنامه به درستی پایان نیافته است. در Android از ActivityManager.isBackgroundRestricted() استفاده کنید تا بررسی کنید آیا برنامه میتواند وظایف پسزمینه را اجرا کند. همچنین onTrimMemory(TRIM_MEMORY_COMPLETE) را ردیابی کنید — این سیگنالی است که فرآیند پایان خواهد یافت.
قانون اول — هرگز فرض نکنید که applicationWillTerminate یا onDestroy فراخوانی خواهند شد. دادههای حیاتی را در هر انتقال از Active به Background ذخیره کنید. از فروشگاههای کلید-مقدار برای تنظیمات ساده و SQLite/Room برای دادههای ساختاریافته استفاده کنید.
قانون دوم — زمان راهاندازی سرد را اندازهگیری و بهینهسازی کنید. مقداردهی تنبل، به حداقل رساندن کار در رشته اصلی، بارگذاری پیشفرض منابع، استفاده از SplashScreen API — همه اینها درک زمان راهاندازی را بهبود میبخشد. Google توصیه میکند راهاندازی سرد کمتر از 200 میلیثانیه برای UX عالی.
قانون سوم — State Restoration را پیادهسازی کنید. در iOS از UIApplication.stateRestorationIdentifier و NSUserActivity استفاده کنید. در Android از SavedStateHandle در ViewModel به همراه onSaveInstanceState استفاده کنید. این به کاربر اجازه میدهد پس از راهاندازی مجدد برنامه از همان مکان به کار ادامه دهد.
قانون چهارم — launchOptions و Intent را که برنامه با آنها پس از Not Running راهاندازی شده است، مدیریت کنید. Deep linkها، اعلانهای push، پیوندهای جهانی — همه آنها از طریق پارامترهای راهاندازی منتقل میشوند. توسعهدهنده باید این دادهها را به درستی استخراج کرده و کاربر را به صفحه مربوطه هدایت کند.
// مدیریت deep link پس از راهاندازی سرد
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// بررسی رسیدن اعلان
if let notification = launchOptions?[.remoteNotification] as? [String: Any] {
handleNotification(notification)
}
// بررسی deep link
if let url = launchOptions?[.url] as? URL {
handleDeepLink(url)
}
return true
}
private func handleDeepLink(_ url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let screenId = components.queryItems?.first(where: { $0.name == "screen" })?.value
else { return }
openScreen(screenId)
}کد مدیریت پارامترهای راهاندازی در راهاندازی سرد iOS را نشان میدهد. launchOptions حاوی دادههایی است که سیستم برنامه را با آنها راهاندازی کرده است. اعلانها، deep linkها و پیوندهای جهانی از طریق این دیکشنری منتقل میشوند. توسعهدهنده باید تمام سناریوهای ممکن راهاندازی را به درستی مدیریت کند تا تجربه کاربری بینقصی ارائه دهد.
سوالات متداول
دادههایی که در حافظه دائمی ذخیره شدهاند (UserDefaults, Core Data, SharedPreferences, Room) حفظ میشوند. دادههای موجود در RAM — متغیرها، کش، وضعیت ViewModel بدون SavedStateHandle — به طور جبرانناپذیری از دست میروند. بنابراین ذخیره وضعیت برنامه در هر انتقال به پسزمینه بسیار حیاتی است.
در راهاندازی سرد application(_:didFinishLaunchingWithOptions:) فراخوانی میشود. در راهاندازی گرم (بازگشت از Suspended) این روش فراخوانی نمیشود — فقط applicationWillEnterForeground و applicationDidBecomeActive فعال میشوند. اگر نیاز به انجام عملی فقط در راهاندازی سرد دارید، یک پرچم در didFinishLaunchingWithOptions تنظیم کنید.
بله. Foreground Service با اعلان دائمی از پایان فرآیند توسط سیستم جلوگیری میکند، حتی اگر همه Activity ها نابود شده باشند. Background Service (startService بدون foreground) ممکن است در هر زمان توسط سیستم متوقف شود. Service در حال اجرا به این معنی است که فرآیند وجود دارد و این دیگر Not Running نیست.
در شبیهساز iOS برنامه را از طریق App Switcher پایان دهید (Cmd+Shift+H دو بار، swipe به بالا). در شبیهساز Android از adb shell am force-stop com.example.app یا دکمه Stop در Logcat استفاده کنید. سپس برنامه را دوباره راهاندازی کنید — این یک راهاندازی سرد تمیز از Not Running خواهد بود.
Kill-switch — دستور سرور برای پایان اضطراری برنامه. در برنامههای بانکی و شرکتی برای مسدود کردن دسترسی از راه دور استفاده میشود. اگر برنامه دستور kill را دریافت کرده باشد، در راهاندازی سرد بعدی UI را مسدود کرده و درخواست مجوز مجدد میکند. در iOS kill-switch از طریق remote notifications با پرچم مسدودسازی پیادهسازی میشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.