Warm Start là một kịch bản khởi động ứng dụng Android trong đó tiến trình ứng dụng đã tồn tại trong bộ nhớ (ví dụ: sau khi thu nhỏ), nhưng Activity đã bị hệ thống hủy để tiết kiệm tài nguyên. Application.onCreate đã được thực thi, các lớp đã được tải, nhưng UI được tạo lại. Theo Google, 2024, Warm Start mất 200–800 ms và chiếm khoảng 40% tổng số lần khởi động trên các thiết bị có 4 GB RAM.
Những điểm chính
Warm Start là trạng thái giữa Cold Start và Hot Start: tiến trình ứng dụng tồn tại trong bộ nhớ (đôi khi trong bộ đệm nền Linux), nhưng Activity không hoạt động và sẽ được tạo lại. Khi Android hết RAM, nó có thể loại bỏ Activity khỏi ngăn xếp, để tiến trình sống. Khi người dùng quay lại ứng dụng, Warm Start xảy ra: một phiên bản Activity mới được tạo, các phương thức vòng đời onCreate → onStart → onResume được thực thi, nhưng Application.onCreate và tải lớp được bỏ qua.
Android quyết định loại bỏ Activity dựa trên mức độ ưu tiên của tiến trình (thứ tự quan trọng). Activity ở nền (cấp PROCESS_STATE_IMPORTANT_FOREGROUND hoặc PROCESS_STATE_TOP_SLEEPING) có thể bị hủy 5–30 phút sau khi thu nhỏ ứng dụng, tùy thuộc vào RAM khả dụng. Trên thiết bị có 3 GB RAM, Activity có thể bị loại bỏ trong vòng 10 phút; trên thiết bị có 8 GB RAM, sau vài giờ. Quan trọng: trong Warm Start, onSaveInstanceState được gọi trước khi Activity bị hủy và nhà phát triển có thể lưu trạng thái UI.
Người dùng không thấy sự khác biệt giữa Warm và Cold Start — họ chỉ chạm vào biểu tượng ứng dụng và chờ. Tuy nhiên, trong Warm Start, màn hình trắng có thể xuất hiện nếu ứng dụng chưa đặt chủ đề cửa sổ khởi động tùy chỉnh. Google khuyên bạn nên đặt chủ đề tùy chỉnh trong tệp kê khai (Theme.AppCompat.Light hoặc Theme.Material3.DayNight) cho Activity khởi động để tránh nhấp nháy màn hình trắng/đen trong Warm Start. Trên Android 12+, API SplashScreen cũng ẩn hiệu ứng này.
Hiểu sự khác biệt giữa ba loại khởi động là cần thiết để chọn chiến lược phân tích và tối ưu hóa phù hợp. Mỗi loại có thời lượng, nút thắt cổ chai và công cụ đo lường riêng.
| Tiêu chí | Cold Start | Warm Start | Hot Start |
|---|---|---|---|
| Tiến trình | Được tạo mới | Tồn tại trong bộ nhớ | Tồn tại trong bộ nhớ |
| Application.onCreate | Được thực thi | Không được thực thi | Không được thực thi |
| Activity | Được tạo từ đầu | Được tạo từ đầu | Được khôi phục từ ngăn xếp |
| Thời gian | 1–5 giây | 200–800 ms | < 200 ms |
| Activity.onCreate | Đầy đủ | Đầy đủ (có khôi phục) | Bỏ qua |
Trong thực tế, Warm Start chiếm 30% đến 60% tổng số lần khởi động ứng dụng, tùy thuộc vào thói quen người dùng và RAM thiết bị. Người dùng giữ nhiều ứng dụng mở (đa nhiệm) gặp Warm Start thường xuyên hơn. Đối với mạng xã hội và tin nhắn, Warm Start là kịch bản phổ biến nhất vì ứng dụng luôn ở nền. Đối với ứng dụng ngân hàng, ngược lại, Cold Start chiếm ưu thế (dọn dẹp tiến trình bắt buộc vì lý do bảo mật).
Warm Start bao gồm ba giai đoạn, mỗi giai đoạn có thể được đo lường và tối ưu hóa. Không giống Cold Start, không có giai đoạn fork hoặc tải lớp, nhưng có giai đoạn khôi phục trạng thái có thể tốn kém.
Hệ thống kiểm tra xem ứng dụng có chủ đề cho cửa sổ khởi động không. Nếu chủ đề không được đặt, màn hình trắng (hoặc đen, tùy hệ thống) sẽ hiển thị. Nếu chủ đề được đặt, nền của chủ đề sẽ hiển thị. Giai đoạn này mất 10–30 ms, nhưng có thể nhận thấy bằng mắt nếu chủ đề không khớp với UI thực tế của ứng dụng. Sử dụng Theme.Material3.DayNight với windowBackground tùy chỉnh có màu khớp với nền màn hình đầu tiên — điều này tạo hiệu ứng tải tức thì.
Hệ thống gọi onCreate truyền Bundle savedInstanceState đã được lưu trong onSaveInstanceState trước khi Activity bị hủy. Nếu ứng dụng đã lưu trạng thái đúng cách (văn bản trường, vị trí cuộn, dữ liệu ViewModel), việc khôi phục diễn ra nhanh chóng. Nếu không, Activity bắt đầu từ đầu và người dùng thấy trình tải trong khi dữ liệu được tải. Điểm chính: các đối tượng ViewModel sống sót qua Warm Start chỉ khi tiến trình không bị hủy — trong Warm Start, ViewModel ở lại trong bộ nhớ.
Sau onCreate, onStart → onResume được thực thi và hệ thống kích hoạt bản vẽ đầu tiên. TTFD (Time To First Draw) cho Warm Start phải dưới 300 ms trên thiết bị tầm trung. Nếu màn hình đầu tiên chứa RecyclerView phức tạp với Views nặng hoặc tải hình ảnh từ mạng, TTFD có thể vượt quá ngưỡng. Sử dụng Placeholder và Shimmer để tải nội dung mượt mà sau khung hình đầu tiên.
Đo Warm Start phức tạp hơn Cold Start vì bạn cần mô phỏng trạng thái tiến trình còn sống nhưng Activity bị hủy. Lệnh ADB tiêu chuẩn với cờ -S không hoạt động — nó giết tiến trình. Sử dụng các cách tiếp cận khác cho Warm Start.
Đầu tiên, khởi chạy ứng dụng qua adb shell monkey hoặc chạm vào biểu tượng, sau đó thu nhỏ nó (adb shell input keyevent 3 keyevent HOME). Đợi 5–10 giây để hệ thống loại bỏ Activity, sau đó chạy adb shell am start -W (không có -S). Lệnh sẽ trả về thời gian khởi động ngắn hơn Cold Start. Để tái tạo, hãy sử dụng tập lệnh: khởi chạy → chờ → home → chờ → khởi chạy.
# Mô phỏng Warm Start qua ADB
$ adb shell am start -W \
com.example.app/.MainActivity
# Đầu ra (Warm Start):
# ThisTime: 412 ms
# TotalTime: 412 ms
# WaitTime: 423 ms
Thư viện androidx.benchmark.macro hỗ trợ đo Warm Start. Trong bài kiểm tra, đặt startupMode = StartupMode.WARM — thư viện sẽ khởi chạy ứng dụng, thu nhỏ nó, chờ (độ trễ có thể cấu hình), sau đó đo lần khởi chạy lại. Macrobenchmark thực hiện 10–20 lần lặp và tính phần trăm. Trong CI/CD, bạn có thể đặt ngưỡng: nếu P50 Warm Start vượt quá 600 ms, bài kiểm tra thất bại. Điều này cho phép theo dõi hồi quy với mỗi commit.
Firebase tự động phân biệt Cold và Warm Start dựa trên thời gian kể từ lần đóng ứng dụng cuối cùng. Nếu ứng dụng được mở trong vòng 30 phút qua, Firebase phân loại lần khởi động là Warm. Trong bảng điều khiển Firebase, bạn sẽ thấy các biểu đồ riêng cho từng loại khởi động, cho phép bạn đánh giá hiệu quả tối ưu hóa. Ví dụ: sau khi triển khai bảo toàn trạng thái trong ViewModel, bạn có thể thấy giảm 30% thời gian Warm Start.
Tối ưu hóa Warm Start tập trung vào hai lĩnh vực: tăng tốc Activity.onCreate và khôi phục trạng thái đúng cách. Vì Application.onCreate và tải lớp đã hoàn thành, nút thắt cổ chai chính là mã UI của màn hình đầu tiên.
Nếu trạng thái đã lưu (savedInstanceState) chứa dữ liệu cần giải tuần tự hóa (Bitmap, String, JSON), hãy thực hiện việc này trên luồng nền. Thay vì đọc trực tiếp từ Bundle trong onCreate, hãy khởi chạy một coroutine và hiển thị màn hình shimmer. Trong thực tế, giải tuần tự hóa Bundle trên thiết bị tầm trung mất 20–100 ms — có vẻ nhỏ, nhưng đối với Warm Start, đây là 10–50% tổng thời gian. Sử dụng Saved State Module của Jetpack, tự động lưu và khôi phục trạng thái ViewModel trong Bundle hoặc cơ sở dữ liệu.
Mở rộng bố cục XML là một trong những giai đoạn tốn kém nhất của Warm Start. Nếu màn hình đầu tiên sử dụng CoordinatorLayout phức tạp với AppBar, CollapsingToolbar, NestedScrollView cộng với ba RecyclerView, thời gian mở rộng có thể đạt 300 ms. Giải pháp: sử dụng ConstraintLayout cho hệ thống phân cấp phẳng, áp dụng ViewStub cho các phần không nhìn thấy khi khởi động (bottom sheet, dialog), bật mở rộng không đồng bộ cho các fragment nặng qua AsyncLayoutInflater. Trong Jetpack Compose, không cần mở rộng, nhưng biên dịch cây Compose trong Warm Start có thể mất thời gian tương tự.
Trong Warm Start, dữ liệu ứng dụng đã tải trong phiên trước có thể đã có trong bộ đệm: cơ sở dữ liệu Room, SharedPreferences, bộ đệm trong bộ nhớ trong ViewModel. Nếu màn hình đầu tiên của bạn hiển thị danh sách từ máy chủ, hãy kiểm tra bộ đệm khi khởi động và cập nhật dữ liệu trong nền. Sử dụng chiến lược cache-then-network: đầu tiên hiển thị dữ liệu đã lưu trong bộ đệm (tức thì), sau đó cập nhật từ máy chủ (không đồng bộ). Điều này giảm thời gian Warm Start cảm nhận xuống còn 100–200 ms.
// ViewModel với lưu đệm cho Warm Start
class FeedViewModel : ViewModel() {
private val cache = MutableStateFlow<List<Item>>(emptyList())
init {
// Lưu đệm trước, sau đó mạng
viewModelScope.launch {
cache.emit(db.getItems()) // Warm Start: dữ liệu đã có trong DB
cache.emit(api.fetchItems()) // Cập nhật nền
}
}
}
Bảo toàn trạng thái đúng cách là yếu tố chính phân biệt Warm Start tốt với xấu. Người dùng mong đợi quay lại ứng dụng và thấy chính xác những gì họ đã để lại — bao gồm vị trí cuộn, văn bản trong trường và các tab đã chọn.
Hệ thống gọi onSaveInstanceState khi Activity đang bị hủy, nhưng TRƯỚC KHI tiến trình có thể bị giết. Chỉ các loại dữ liệu đơn giản (String, Int, Parcelable, Serializable) được lưu trong Bundle. Đối với dữ liệu phức tạp, hãy sử dụng SavedStateHandle trong ViewModel — nó tự động lưu và khôi phục các trường trong Warm Start. Không giống onSaveInstanceState, SavedStateHandle hoạt động ngay cả khi tiến trình sống sót qua Warm Start (ViewModel không bị hủy). Ví dụ: cho văn bản trong EditText, sử dụng SavedStateHandle.getLiveData(“text”) — văn bản sẽ được tự động lưu và khôi phục.
Nếu tiến trình không bị giết trong Warm Start, ViewModel ở lại trong bộ nhớ và onCleared không được gọi. Điều này có nghĩa là tất cả dữ liệu đã tải trong phiên trước có sẵn ngay lập tức. Tuy nhiên, nếu tiến trình bị giết (thiết bị ở chế độ ngủ sâu hơn 30 phút), ViewModel bị hủy và được tạo lại với SavedStateHandle. Để hành vi ViewModel chính xác trong Warm Start, hãy sử dụng SavedStateHandle với các trường cần khôi phục trong mọi kịch bản. Khác biệt: ViewModel với @HiltViewModel hỗ trợ SavedStateHandle tự động.
| Cơ chế | Tiến trình sống | Tiến trình bị giết |
|---|---|---|
| ViewModel | Dữ liệu trong bộ nhớ | Bị hủy, tạo lại |
| SavedStateHandle | Dữ liệu trong bộ nhớ | Khôi phục từ Bundle |
| onSaveInstanceState | Được gọi khi loại bỏ Activity | Không được gọi |
| Room DB | Bộ đệm có sẵn | Bộ đệm có sẵn (đĩa) |
Một trong những vấn đề phổ biến nhất của Warm Start — mất vị trí cuộn. Người dùng cuộn đến mục thứ 50, thu nhỏ ứng dụng, quay lại — và thấy đầu danh sách. Giải pháp: lưu layoutManager.onSaveInstanceState (lưu vị trí và độ lệch của mục hiển thị đầu tiên) và khôi phục nó trong onRestoreInstanceState. Bạn cũng có thể lưu vị trí hiển thị cuối cùng trong SharedPreferences với khóa ngày/giờ để khôi phục nhanh vị trí trong Warm Start.
// Lưu vị trí cuộn 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) }
}
Hai ví dụ thực tế về tối ưu hóa Warm Start: sử dụng SavedStateHandle trong ViewModel và khôi phục không đồng bộ dữ liệu phức tạp sau khi khởi động.
SavedStateHandle tự động lưu các trường vào Bundle và khôi phục chúng trong Warm Start. Trường hồ sơ người dùng (String, JSON) sẽ được khôi phục mà không cần yêu cầu máy chủ không cần thiết. Nếu tiến trình bị giết, SavedStateHandle tải trạng thái đã lưu cuối cùng từ 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 không null, UI không có trình tải
// Sau khi tải: profile cập nhật trong SavedStateHandle
Nếu màn hình đầu tiên chứa bố cục phức tạp (bản đồ, gradient, nhiều danh sách), hãy sử dụng AsyncLayoutInflater để mở rộng các phần tử nặng trong nền. Trong khi bố cục đang được mở rộng, hãy hiển thị phần giữ chỗ với hiệu ứng shimmer. Điều này đặc biệt quan trọng cho Warm Start, nơi mỗi mili giây đều có giá trị. AsyncLayoutInflater chạy trên luồng nền và chuyển View đã sẵn sàng đến callback trên luồng chính.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Bố cục Placeholder để hiển thị tức thì
setContentView(R.layout.placeholder_shimmer)
// Tải không đồng bộ bố cục nặng
AsyncLayoutInflater(this).inflate(
R.layout.activity_main_complex,
findViewById(R.id.container)
) { view, resId, parent ->
parent?.removeAllViews()
parent?.addView(view)
}
}
}
Câu hỏi thường gặp
Có, nếu tại thời điểm Warm Start hệ thống quyết định giết tiến trình ứng dụng (ví dụ: để giải phóng bộ nhớ cho ứng dụng khác), lần khởi động trở thành Cold Start từ đầu. Điều này xảy ra trên thiết bị có 2–3 GB RAM khi nhiều ứng dụng chạy đồng thời. Trên thực tế, Warm Start chỉ được đảm bảo trong 10–20 phút sau khi thu nhỏ trên thiết bị tầm trung.
Có, nếu tiến trình không bị giết, ViewModel ở lại trong bộ nhớ và onCleared không được gọi. Đây là lợi thế chính của Warm Start: tất cả dữ liệu được tải qua yêu cầu mạng, bộ đệm trong ViewModel — tất cả đều có sẵn ngay lập tức. Nếu tiến trình bị giết, ViewModel được tạo lại qua ViewModelProvider.Factory hoặc @HiltViewModel, và SavedStateHandle khôi phục các trường đã lưu.
Về mặt lý thuyết, Warm Start luôn nhanh hơn Cold Start, nhưng trong thực tế có những kịch bản mà sự khác biệt là tối thiểu: nếu Application.onCreate nhẹ (50 ms) và Activity.onCreate nặng (800 ms), thì Warm Start (800 ms) gần bằng Cold Start (850 ms). Trong trường hợp này, bạn nên tối ưu không phải Application, mà là Activity.onCreate — nó trở thành nút thắt cổ chai cho Warm Start.
API SplashScreen trên Android 12+ hiển thị màn hình chào hệ thống (biểu tượng trên nền màu) ngay lập tức khi khởi động — cho cả Cold và Warm Start. Đối với Warm Start, màn hình chào chỉ hiển thị 100–300 ms, sau đó được thay thế bằng UI của ứng dụng. SplashScreen không tăng tốc bản thân việc khởi động, nhưng che giấu thời gian tạo Activity, cải thiện nhận thức.
Có, vì Warm Start xảy ra nhiều gấp 2–3 lần Cold Start. Nếu Cold Start mất 1.2 giây và Warm Start mất 600 ms, thì 40% số lần khởi động (Warm) vẫn mất 0.6 giây, điều này có thể nhận thấy. Tối ưu Warm Start xuống 200–300 ms mang lại cho người dùng cảm giác quay lại tức thì. Trên thiết bị có 6+ GB RAM, Warm Start có thể chiếm tới 80% tổng số lần khởi động, khiến việc tối ưu hóa nó trở thành ưu tiên.
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