Warm Start — سناریوی راهاندازی برنامه Android است که در آن فرآیند برنامه قبلاً در حافظه وجود دارد (مثلاً پس از کوچکسازی)، اما Activity توسط سیستم برای صرفهجویی در منابع از بین رفته است. Application.onCreate قبلاً اجرا شده، کلاسها بارگذاری شدهاند، اما UI از نو ساخته میشود. طبق Google, 2024، Warm Start بین ۲۰۰ تا ۸۰۰ میلیثانیه طول میکشد و حدود ۴۰٪ از تمام راهاندازیها در دستگاههای با ۴ گیگابایت RAM را تشکیل میدهد.
نکات اصلی
Warm Start (راهاندازی گرم) — حالتی بین Cold Start و Hot Start است: فرآیند برنامه در حافظه وجود دارد (گاهی در کش پسزمینه Linux)، اما Activity فعال نیست و از نو ساخته خواهد شد. سیستم Android در صورت کمبود RAM ممکن است Activity را از پشته تخلیه کند و فرآیند را زنده نگه دارد. وقتی کاربر به برنامه برمیگردد، Warm Start آغاز میشود: نمونه جدیدی از Activity ایجاد میشود، متدهای چرخه حیات onCreate → onStart → onResume اجرا میشوند، اما Application.onCreate و بارگذاری کلاسها رد میشوند.
سیستم Android بر اساس اولویت فرآیند (importance rank) درباره تخلیه Activity تصمیم میگیرد. Activity در پسزمینه (سطح PROCESS_STATE_IMPORTANT_FOREGROUND یا PROCESS_STATE_TOP_SLEEPING) ممکن است ۵–۳۰ دقیقه پس از کوچکسازی برنامه از بین برود، بسته به RAM موجود. در دستگاههای با ۳ گیگابایت RAM، Activity ممکن است پس از ۱۰ دقیقه تخلیه شود، در دستگاههای با ۸ گیگابایت — پس از چند ساعت. مهم: در Warm Start onSaveInstanceState قبل از تخلیه Activity فراخوانی میشود و توسعهدهنده میتواند وضعیت UI را ذخیره کند.
کاربر تفاوتی بین Warm و Cold Start نمیبیند — او فقط روی آیکون برنامه کلیک میکند و منتظر میماند. اما در Warm Start ممکن است صفحه سفید (blank window) ظاهر شود اگر برنامه تم مخصوص پنجره شروع را تنظیم نکرده باشد. Google توصیه میکند یک تم سفارشی در مانیفست (Theme.AppCompat.Light یا Theme.Material3.DayNight) برای Activity شروع تنظیم کنید تا از سوسو زدن صفحه سفید/سیاه در Warm Start جلوگیری شود. در Android 12+، SplashScreen API نیز این اثر را پنهان میکند.
درک تفاوت بین سه نوع راهاندازی برای انتخاب استراتژی صحیح پروفایلگیری و بهینهسازی ضروری است. هر نوع مدت زمان، گلوگاهها و ابزارهای اندازهگیری خاص خود را دارد.
| معیار | Cold Start | Warm Start | Hot Start |
|---|---|---|---|
| فرآیند | از نو ایجاد میشود | در حافظه وجود دارد | در حافظه وجود دارد |
| Application.onCreate | اجرا میشود | اجرا نمیشود | اجرا نمیشود |
| Activity | از صفر ساخته میشود | از صفر ساخته میشود | از پشته بازیابی میشود |
| زمان | ۱–۵ ثانیه | ۲۰۰–۸۰۰ میلیثانیه | < ۲۰۰ میلیثانیه |
| onCreate Activity | کامل | کامل (با restore) | رد میشود |
در عمل، Warm Start بین ۳۰٪ تا ۶۰٪ از تمام راهاندازیهای برنامه را تشکیل میدهد، بسته به عادات کاربر و میزان RAM دستگاه. کاربرانی که برنامههای زیادی را باز نگه میدارند (multitasker)، بیشتر با Warm Start مواجه میشوند. برای شبکههای اجتماعی و پیامرسانها، Warm Start رایجترین سناریو است زیرا برنامه همیشه در پسزمینه است. برای برنامههای بانکی، برعکس، Cold Start غالب است (پاکسازی اجباری فرآیند به دلایل امنیتی).
Warm Start از سه مرحله تشکیل شده است که هر کدام قابل اندازهگیری و بهینهسازی هستند. برخلاف Cold Start، مرحله fork و بارگذاری کلاسها وجود ندارد، اما مرحله بازیابی وضعیت (restore) وجود دارد که میتواند پرهزینه باشد.
سیستم بررسی میکند که آیا برنامه تمی برای پنجره شروع دارد یا خیر. اگر تم تنظیم نشده باشد، صفحه سفید (یا سیاه، بسته به سیستم) نمایش داده میشود. اگر تم تنظیم شده باشد، background از تم نمایش داده میشود. این مرحله ۱۰–۳۰ میلیثانیه طول میکشد، اما اگر تم با UI واقعی برنامه مطابقت نداشته باشد، از نظر بصری قابل احساس است. از Theme.Material3.DayNight با windowBackground سفارشی استفاده کنید که رنگ آن با پسزمینه صفحه اول مطابقت دارد — این اثر بارگذاری فوری را ایجاد میکند.
سیستم onCreate را با ارسال Bundle savedInstanceState که در onSaveInstanceState قبل از تخلیه Activity ذخیره شده بود، فراخوانی میکند. اگر برنامه وضعیت را به درستی ذخیره کرده باشد (متن فیلدها، موقعیت اسکرول، دادههای ViewModel)، بازیابی سریع انجام میشود. اگر نه — Activity از یک صفحه خالی شروع میشود و کاربر لودر را میبیند تا دادهها بارگذاری شوند. نکته کلیدی: اشیاء ViewModel از Warm Start فقط در صورتی زنده میمانند که فرآیند از بین نرفته باشد — در Warm Start ViewModel در حافظه حفظ میشود.
پس از onCreate، onStart → onResume اجرا میشود و سیستم اولین رندر را فراخوانی میکند. TTFD (Time To First Draw) برای Warm Start باید کمتر از ۳۰۰ میلیثانیه در دستگاه متوسط باشد. اگر صفحه اول شامل RecyclerView پیچیده با Viewهای سنگین یا بارگذاری تصاویر از شبکه باشد، TTFD ممکن است از آستانه فراتر رود. از Placeholder و Shimmer برای بارگذاری روان محتوا پس از اولین فریم استفاده کنید.
اندازهگیری Warm Start پیچیدهتر از Cold Start است زیرا باید وضعیت «فرآیند زنده است، Activity از بین رفته» را شبیهسازی کنید. دستور استاندارد ADB با پرچم -S مناسب نیست — فرآیند را میکشد. برای Warm Start از روشهای دیگر استفاده کنید.
ابتدا برنامه را از طریق adb shell monkey یا کلیک روی آیکون اجرا کنید، سپس آن را کوچک کنید (adb shell input keyevent 3 keyevent HOME). ۵–۱۰ ثانیه صبر کنید تا سیستم بتواند Activity را تخلیه کند و adb shell am start -W (بدون -S) را اجرا کنید. دستور زمان راهاندازی را برمیگرداند که کوتاهتر از Cold Start خواهد بود. برای تکرارپذیری از اسکریپت استفاده کنید: اجرا → صبر → home → صبر → اجرا.
# شبیهسازی Warm Start از طریق ADB
$ adb shell am start -W \
com.example.app/.MainActivity
# خروجی (Warm Start):
# ThisTime: 412 ms
# TotalTime: 412 ms
# WaitTime: 423 ms
کتابخانه androidx.benchmark.macro از اندازهگیری Warm Start پشتیبانی میکند. برای این کار در تست startupMode = StartupMode.WARM را تنظیم کنید — کتابخانه برنامه را اجرا میکند، آن را کوچک میکند، صبر میکند (تأخیر قابل تنظیم) و سپس راهاندازی مجدد را اندازهگیری میکند. Macrobenchmark ۱۰–۲۰ بار اجرا میکند و صدکها را محاسبه میکند. در CI/CD میتوان آستانه تنظیم کرد: اگر P50 Warm Start از ۶۰۰ میلیثانیه بیشتر شود — تست رد میشود. این امکان ردیابی رگرسیونها در هر commit را فراهم میکند.
Firebase به طور خودکار بین Cold و Warm Start بر اساس زمان از آخرین بسته شدن برنامه تفاوت قائل میشود. اگر برنامه در ۳۰ دقیقه گذشته باز شده باشد، Firebase راهاندازی را به عنوان Warm طبقهبندی میکند. در کنسول Firebase نمودارهای جداگانهای برای هر نوع راهاندازی مشاهده خواهید کرد که امکان ارزیابی اثربخشی بهینهسازیها را فراهم میکند. به عنوان مثال، پس از پیادهسازی ذخیرهسازی وضعیت در ViewModel میتوان کاهش Warm Start را تا ۳۰٪ مشاهده کرد.
بهینهسازی Warm Start بر دو جهت متمرکز است: تسریع Activity.onCreate و بازیابی صحیح وضعیت. از آنجایی که Application.onCreate و بارگذاری کلاسها قبلاً انجام شده است، گلوگاه اصلی کد UI صفحه اول است.
اگر وضعیت ذخیره شده (savedInstanceState) حاوی دادههایی باشد که نیاز به دیسریالسازی دارند (Bitmap، String، JSON)، این کار را در رشته پسزمینه انجام دهید. به جای خواندن مستقیم از Bundle در onCreate، یک coroutine راهاندازی کنید و صفحه shimmer را نمایش دهید. در عمل، دیسریالسازی Bundle در دستگاه متوسط ۲۰–۱۰۰ میلیثانیه طول میکشد — به ظاهر کم، اما برای Warm Start این ۱۰–۵۰٪ از کل زمان است. از Saved State Module کتابخانه Jetpack استفاده کنید که به طور خودکار وضعیت ViewModel را در Bundle یا پایگاه داده ذخیره و بازیابی میکند.
گسترش طرح XML (layout inflation) — یکی از گرانترین مراحل Warm Start است. اگر صفحه اول از CoordinatorLayout پیچیده با AppBar، CollapsingToolbar، NestedScrollView به همراه سه RecyclerView استفاده کند، زمان inflation میتواند به ۳۰۰ میلیثانیه برسد. راهحلها: از ConstraintLayout برای سلسلهمراتب مسطح استفاده کنید، ViewStub را برای بخشهای نامرئی در شروع (bottom sheet، dialog) به کار ببرید، گسترش ناهمگام برای فرگمنتهای سنگین را از طریق AsyncLayoutInflater فعال کنید. در Jetpack Compose نیازی به inflation نیست، اما کامپایل درخت Compose در Warm Start ممکن است زمان مشابهی بگیرد.
در Warm Start، دادههایی که برنامه در نشست قبلی بارگذاری کرده بود ممکن است در کش وجود داشته باشند: پایگاه داده Room، SharedPreferences، کش in-memory در ViewModel. اگر صفحه اول شما لیستی از سرور نمایش میدهد، کش را در شروع بررسی کنید و دادهها را در پسزمینه بهروز کنید. از استراتژی cache-then-network استفاده کنید: ابتدا دادههای کش شده را نمایش دهید (فوری)، سپس از سرور بهروز کنید (ناهمگام). این کار زمان درک شده Warm Start را به ۱۰۰–۲۰۰ میلیثانیه کاهش میدهد.
// ViewModel با کشسازی برای Warm Start
class FeedViewModel : ViewModel() {
private val cache = MutableStateFlow<List<Item>>(emptyList())
init {
// ابتدا کش، سپس شبکه
viewModelScope.launch {
cache.emit(db.getItems()) // Warm Start: دادهها قبلاً در پایگاه داده
cache.emit(api.fetchItems()) // بهروزرسانی در پسزمینه
}
}
}
ذخیرهسازی صحیح وضعیت — عامل کلیدی است که Warm Start خوب را از بد متمایز میکند. کاربر انتظار دارد به برنامه برگردد و همان چیزی را ببیند که ترک کرده — شامل موقعیت اسکرول، متن در فیلدها، تبهای انتخاب شده.
سیستم onSaveInstanceState را هنگام تخلیه Activity فراخوانی میکند، اما قبل از اینکه فرآیند کشته شود. در Bundle فقط دادههای ساده (String، Int، Parcelable، Serializable) ذخیره میشوند. برای دادههای پیچیده از SavedStateHandle در ViewModel استفاده کنید — به طور خودکار فیلدها را در Warm Start ذخیره و بازیابی میکند. برخلاف onSaveInstanceState، SavedStateHandle حتی اگر فرآیند از Warm Start زنده مانده باشد کار میکند (ViewModel از بین نمیرود). مثال: برای متن در EditText از SavedStateHandle.getLiveData("text") استفاده کنید — متن به طور خودکار ذخیره و بازیابی میشود.
اگر در Warm Start فرآیند از بین نرفته باشد، ViewModel در حافظه باقی میماند و onCleared فراخوانی نمیشود. این بدان معناست که تمام دادههای بارگذاری شده در نشست قبلی فوری در دسترس هستند. اما اگر فرآیند از بین رفته باشد (دستگاه در deep sleep بیش از ۳۰ دقیقه)، ViewModel از بین میرود و با SavedStateHandle از نو ساخته میشود. برای کار صحیح ViewModel در Warm Start از SavedStateHandle با فیلدهایی که باید در هر سناریویی بازیابی شوند استفاده کنید. تفاوت: ViewModel با @HiltViewModel به طور خودکار از SavedStateHandle پشتیبانی میکند.
| مکانیسم | فرآیند زنده | فرآیند کشته شده |
|---|---|---|
| ViewModel | دادهها در حافظه | از بین رفته، از نو ساخته میشود |
| SavedStateHandle | دادهها در حافظه | از Bundle بازیابی میشود |
| onSaveInstanceState | هنگام تخلیه Activity فراخوانی میشود | فراخوانی نمیشود |
| Room DB | کش در دسترس است | کش در دسترس است (دیسک) |
یکی از رایجترین مشکلات Warm Start — از دست دادن موقعیت اسکرول است. کاربر فید را تا المان ۵۰ام اسکرول کرده، برنامه را کوچک کرده، برگشته — و ابتدای لیست را میبیند. راهحل: layoutManager.onSaveInstanceState را ذخیره کنید (موقعیت و offset اولین عنصر قابل مشاهده را ذخیره میکند) و آن را در onRestoreInstanceState بازیابی کنید. همچنین میتوان آخرین موقعیت قابل مشاهده را در SharedPreferences با کلید تاریخ/زمان ذخیره کرد تا در Warm Start سریعاً موقعیت بازیابی شود.
// ذخیره موقعیت اسکرول RecyclerView
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(
"rv_state", binding.recyclerView
.layoutManager?.onSaveInstanceState()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.getParcelable<Parcelable>("rv_state")
?.let { binding.recyclerView.layoutManager?.onRestoreInstanceState(it) }
}
دو مثال عملی از بهینهسازی Warm Start: استفاده از SavedStateHandle در ViewModel و بازیابی ناهمگام دادههای پیچیده پس از راهاندازی.
SavedStateHandle به طور خودکار فیلدها را در Bundle ذخیره کرده و در Warm Start بازیابی میکند. فیلد پروفایل کاربر (String, JSON) بدون درخواستهای اضافی به سرور بازیابی میشود. اگر فرآیند از بین رفته باشد، SavedStateHandle آخرین وضعیت ذخیره شده را از Bundle بارگذاری میکند.
class ProfileViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val profile: StateFlow<Profile?>
get() = savedStateHandle
.getStateFlow("profile", null)
fun loadProfile(id: String) {
viewModelScope.launch {
savedStateHandle["profile"] =
api.getProfile(id)
}
}
}
// Warm Start: profile نال نیست، UI بدون لودر
// پس از بارگذاری: profile در SavedStateHandle بهروز میشود
اگر صفحه اول شامل یک طرح پیچیده (نقشه، گرادیان، چند لیست) است، از AsyncLayoutInflater برای گسترش عناصر سنگین در پسزمینه استفاده کنید. در حین گسترش طرح، یک placeholder با اثر shimmer نمایش دهید. این به ویژه برای Warm Start مهم است، جایی که هر میلیثانیه ارزش دارد. AsyncLayoutInflater در رشته پسزمینه کار میکند و View آماده را در callback به رشته اصلی تحویل میدهد.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// طرح placeholder برای رندر فوری
setContentView(R.layout.placeholder_shimmer)
// بارگذاری ناهمگام طرح سنگین
AsyncLayoutInflater(this).inflate(
R.layout.activity_main_complex,
findViewById(R.id.container)
) { view, resId, parent ->
parent?.removeAllViews()
parent?.addView(view)
}
}
}
سوالات متداول
بله، اگر در لحظه Warm Start سیستم تصمیم به کشتن فرآیند برنامه بگیرد (مثلاً برای آزاد کردن حافظه برای برنامه دیگر)، راهاندازی به Cold Start از صفر تبدیل میشود. این در دستگاههای با ۲–۳ گیگابایت RAM هنگام کار همزمان چند برنامه رخ میدهد. در عمل، Warm Start فقط در ۱۰–۲۰ دقیقه پس از کوچکسازی در دستگاههای میانرده تضمین میشود.
بله، اگر فرآیند از بین نرفته باشد، ViewModel در حافظه باقی میماند و onCleared فراخوانی نمیشود. این مزیت کلیدی Warm Start است: تمام دادههای بارگذاری شده، درخواستهای شبکه، کش در ViewModel — فوری در دسترس هستند. اگر فرآیند از بین رفته باشد، ViewModel از طریق ViewModelProvider.Factory یا @HiltViewModel از نو ساخته میشود و SavedStateHandle فیلدهای ذخیره شده را بازیابی میکند.
از نظر تئوری، Warm Start همیشه سریعتر از Cold Start است، اما در عمل سناریوهایی وجود دارد که تفاوت حداقل است: اگر Application.onCreate سبک (۵۰ میلیثانیه) و Activity.onCreate سنگین (۸۰۰ میلیثانیه) باشد، Warm Start (۸۰۰ میلیثانیه) تقریباً برابر Cold Start (۸۵۰ میلیثانیه) است. در این حالت باید نه Application، بلکه Activity.onCreate را بهینه کرد — این همان گلوگاه Warm Start میشود.
SplashScreen API در Android 12+ یک اسپلش سیستم (آیکون روی پسزمینه رنگی) را بلافاصله در شروع نمایش میدهد — هم برای Cold و هم برای Warm Start. برای Warm Start، اسپلش فقط ۱۰۰–۳۰۰ میلیثانیه نمایش داده میشود و سپس UI برنامه جایگزین آن میشود. SplashScreen خود راهاندازی را تسریع نمیکند، اما زمان ایجاد Activity را پنهان کرده و ادراک را بهبود میبخشد.
بله، زیرا Warm Start ۲–۳ برابر بیشتر از Cold Start رخ میدهد. اگر Cold Start ۱.۲ ثانیه و Warm Start ۶۰۰ میلیثانیه طول بکشد، ۴۰٪ از راهاندازیها (Warm) هنوز ۰.۶ ثانیه طول میکشد که قابل احساس است. بهینهسازی Warm Start تا ۲۰۰–۳۰۰ میلیثانیه به کاربر احساس بازگشت فوری میدهد. در دستگاههای با ۶+ گیگابایت RAM، Warm Start میتواند تا ۸۰٪ از تمام راهاندازیها را تشکیل دهد و بهینهسازی آن اولویت مییابد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید