Xin chào các bạn trên hành trình trở thành Lập trình viên Android chuyên nghiệp! Chào mừng trở lại với series “Android Developer Roadmap” của chúng ta.
Trong những bài viết trước, chúng ta đã cùng nhau khám phá những nền tảng cơ bản như thiết lập môi trường phát triển, cú pháp Kotlin cơ bản (hoặc lý do chọn Kotlin), lập trình hướng đối tượng, cấu trúc dữ liệu giải thuật, Gradle, tạo ứng dụng “Hello World”, và làm quen với Git và các nền tảng quản lý mã nguồn như GitHub, GitLab, Bitbucket. Hôm nay, chúng ta sẽ đi sâu vào một trong những khái niệm cốt lõi và quan trọng nhất mà mọi lập trình viên Android đều phải nắm vững: Vòng Đời (Lifecycle) của Activity.
Hiểu rõ vòng đời Activity không chỉ giúp bạn viết mã đúng đắn, tránh các lỗi thường gặp như rò rỉ bộ nhớ (memory leaks) hoặc ANRs (Application Not Responding), mà còn cho phép bạn tạo ra trải nghiệm người dùng mượt mà, phản hồi nhanh chóng và đáng tin cậy.
Mục lục
Activity Là Gì? Nhắc Lại Cơ Bản
Trước khi nói về vòng đời, chúng ta hãy nhắc lại một chút: Activity là một trong những thành phần cơ bản của ứng dụng Android. Nó thường đại diện cho một giao diện màn hình đơn lẻ mà người dùng có thể tương tác (ví dụ: màn hình đăng nhập, màn hình danh sách sản phẩm, màn hình cài đặt). Khi bạn tạo ứng dụng “Hello World” đầu tiên, bạn đã làm việc với một Activity rồi đấy!
Ứng dụng Android thường bao gồm nhiều Activity liên kết với nhau. Khi người dùng điều hướng qua lại giữa các màn hình, các Activity tương ứng sẽ được tạo, tạm dừng, tiếp tục hoặc hủy bỏ. Hệ điều hành Android quản lý chặt chẽ trạng thái và tài nguyên của từng Activity dựa trên các tương tác của người dùng và các sự kiện của hệ thống (như nhận cuộc gọi, xoay màn hình…). Đây chính là lúc Vòng Đời Activity phát huy vai trò của nó.
Tại Sao Phải Hiểu Vòng Đời Activity?
Hệ điều hành Android là một môi trường đa nhiệm, nơi mà nhiều ứng dụng và thành phần có thể chạy cùng lúc. Để đảm bảo hiệu suất, ổn định và tiết kiệm pin, hệ thống có thể tạm dừng hoặc thậm chí kết thúc (terminate) các tiến trình (process) chứa Activity không còn được người dùng nhìn thấy hoặc tương tác.
Việc hiểu vòng đời Activity giúp bạn:
- Quản lý Tài nguyên hiệu quả: Biết khi nào nên cấp phát (allocate) hoặc giải phóng (release) các tài nguyên đắt đỏ như kết nối mạng, cảm biến, hoặc tài nguyên đồ họa.
- Lưu và Khôi phục Trạng thái: Đảm bảo rằng dữ liệu và trạng thái UI của người dùng không bị mất khi hệ thống tạm dừng hoặc hủy bỏ Activity do thiếu bộ nhớ hoặc thay đổi cấu hình (như xoay màn hình).
- Xử lý các Tương tác của Hệ thống: Đáp ứng phù hợp khi ứng dụng bị ngắt ngang bởi cuộc gọi đến, chuyển sang ứng dụng khác, hoặc màn hình bị tắt.
- Tránh Lỗi và Tăng Tính Ổn định: Ngăn ngừa các vấn đề như rò rỉ bộ nhớ (khi tài nguyên không được giải phóng đúng cách) hoặc treo ứng dụng (ANR – khi các tác vụ tốn thời gian chặn luồng chính).
- Tạo Trải nghiệm Người dùng Mượt mà: Đảm bảo ứng dụng phản hồi nhanh chóng khi chuyển đổi giữa các trạng thái.
Về cơ bản, vòng đời Activity cung cấp cho bạn một tập hợp các phương thức (callback methods) mà bạn có thể ghi đè (override) để thực hiện các hành động cụ thể tại những thời điểm quan trọng trong cuộc đời của Activity đó.
Các Trạng Thái Chính và Phương Thức Callback
Một Activity có thể tồn tại ở nhiều trạng thái khác nhau trong vòng đời của nó. Khi trạng thái thay đổi, hệ thống sẽ gọi các phương thức callback tương ứng của Activity. Dưới đây là các trạng thái chính và các phương thức callback quan trọng nhất:
Trạng Thái và Phương Thức Callback
- Created (Đã tạo): Activity vừa được khởi tạo nhưng chưa hiển thị.
onCreate()
: Phương thức đầu tiên được gọi khi Activity được tạo. Đây là nơi bạn thực hiện hầu hết các thiết lập ban đầu:- Thiết lập giao diện người dùng bằng
setContentView()
. - Khởi tạo các View.
- Khởi tạo các biến cần thiết.
- Khôi phục trạng thái trước đó nếu có (từ Bundle được truyền vào).
Đây là điểm bắt đầu của vòng đời.
- Thiết lập giao diện người dùng bằng
- Started (Đã bắt đầu): Activity trở nên hiển thị với người dùng (mặc dù có thể chưa ở tiền cảnh và tương tác được).
onStart()
: Được gọi sauonCreate()
(hoặconRestart()
) khi Activity sắp hiển thị.- Bắt đầu các tác vụ cần chạy khi Activity hiển thị, như đăng ký các broadcast receiver lắng nghe thay đổi của hệ thống.
Từ trạng thái Started, Activity có thể chuyển sang Resumed hoặc Stopped.
- Resumed (Đã tiếp tục/Hoạt động): Activity đang ở tiền cảnh và người dùng có thể tương tác đầy đủ với nó. Đây là trạng thái mà Activity hoạt động tích cực nhất.
onResume()
: Được gọi sauonStart()
(hoặconPause()
khi Activity trở lại tiền cảnh).- Khởi động lại các tác vụ cần thiết khi Activity tương tác trực tiếp với người dùng, như bắt đầu animation, truy cập camera, hoặc cập nhật UI liên tục.
Activity sẽ ở trạng thái này cho đến khi có thứ gì đó lấy tiêu điểm (focus) của nó (ví dụ: có cuộc gọi đến, Activity khác bật lên trên nó, hoặc màn hình bị tắt).
- Paused (Đã tạm dừng): Activity mất tiêu điểm nhưng vẫn còn hiển thị (ví dụ: một cửa sổ dialog bật lên che một phần Activity, hoặc màn hình chia đôi). Activity này vẫn còn sống (vẫn còn trong bộ nhớ) nhưng không còn là Activity ở tiền cảnh được tương tác.
onPause()
: Được gọi khi hệ thống sắp tiếp tục một Activity khác và Activity hiện tại sắp bị chuyển sang trạng thái Paused.- Lưu trữ bất kỳ thông tin nào cần được duy trì liên tục (như chỉnh sửa của người dùng chưa được lưu vào bộ nhớ chính).
- Ngừng các tác vụ tiêu tốn tài nguyên không cần thiết khi Activity không ở tiền cảnh (ví dụ: dừng preview camera, dừng animation).
Lưu ý quan trọng: Phương thức này phải chạy nhanh, vì Activity tiếp theo sẽ không được tiếp tục cho đến khi
onPause()
của Activity hiện tại hoàn thành.
- Stopped (Đã dừng): Activity không còn hiển thị với người dùng. Điều này có thể xảy ra khi một Activity khác che phủ hoàn toàn nó, hoặc khi Activity được chuyển sang chế độ nền (background).
onStop()
: Được gọi khi Activity không còn hiển thị.- Giải phóng hầu hết các tài nguyên không cần thiết khi Activity bị ẩn (ví dụ: ngắt kết nối mạng, hủy đăng ký broadcast receiver).
Hệ thống có thể giải phóng bộ nhớ của Activity này nếu cần tài nguyên cho các tác vụ khác ưu tiên hơn. Từ trạng thái Stopped, Activity có thể chuyển sang Started (nếu quay lại hiển thị) hoặc Destroyed (nếu bị hệ thống kết thúc).
- Destroyed (Đã bị phá hủy): Activity đang bị hủy bỏ và sẽ bị xóa khỏi bộ nhớ. Điều này có thể xảy ra do Activity kết thúc (người dùng nhấn nút Back, hoặc gọi
finish()
), hoặc do hệ thống quyết định kết thúc tiến trình của Activity để giải phóng tài nguyên.onDestroy()
: Phương thức cuối cùng được gọi trước khi Activity bị hủy bỏ.- Giải phóng tất cả các tài nguyên còn lại (đóng database, hủy các thread đang chạy, đóng các kết nối…).
Luôn đảm bảo rằng tất cả tài nguyên đã được giải phóng để tránh rò rỉ bộ nhớ.
Phương Thức Khác:
onRestart()
: Được gọi khi Activity đã ở trạng thái Stopped và sắp bắt đầu lại. Phương thức này luôn theo sau bởionStart()
vàonResume()
. Ít khi cần ghi đè phương thức này trực tiếp.
Trực Quan Hóa Vòng Đời Activity
Việc hình dung các trạng thái và chuyển đổi giữa chúng là cách tốt nhất để hiểu vòng đời Activity.
Sơ đồ A: Vòng đời Activity đầy đủ từ khi được tạo đến khi bị hủy.
+------------------+
| Created |
+------------------+
| onCreate()
v
+------------------+
| Started |
+------------------+
| onStart()
v
+------------------+ <--- onPause() <---+
| Resumed | |
| (Running) | |
+------------------+ |
| onPause() |
v |
+------------------+ -- onRestart() -->+
| Paused |
+------------------+
| onStop()
v
+------------------+ -- onRestart() -->+
| Stopped |
+------------------+
| onDestroy()
v
+------------------+
| Destroyed |
+------------------+
(Sơ đồ này minh họa các chuyển đổi chính giữa các trạng thái.)
Sơ đồ B: Các kịch bản chuyển trạng thái phổ biến.
1. Khởi động ứng dụng lần đầu:
onCreate()
-> onStart()
-> onResume()
2. Người dùng nhấn nút Home (ứng dụng vào nền):
onPause()
-> onStop()
3. Người dùng quay lại ứng dụng từ nền:
onRestart()
-> onStart()
-> onResume()
4. Một Activity khác bật lên che phủ một phần (ví dụ: Dialog):
onPause()
(Activity dưới vẫn ở trạng thái Paused)
5. Đóng Activity hiện tại (nhấn nút Back hoặc gọi finish()):
onPause()
-> onStop()
-> onDestroy()
6. Thay đổi cấu hình (ví dụ: xoay màn hình):
onPause()
-> onStop()
-> onDestroy()
(Activity hiện tại bị hủy)
onCreate()
-> onStart()
-> onResume()
(Activity mới được tạo lại)
(Sơ đồ này cho thấy các chuỗi gọi callback trong các tình huống cụ thể.)
Xử Lý Thay Đổi Cấu Hình và Lưu/Khôi phục Trạng thái
Một trong những thách thức lớn nhất khi làm việc với vòng đời Activity là xử lý các thay đổi cấu hình (configuration changes). Thay đổi hướng màn hình (xoay), thay đổi ngôn ngữ, hoặc kết nối bàn phím cứng… đều là các thay đổi cấu hình. Theo mặc định, khi thay đổi cấu hình xảy ra, Android sẽ hủy bỏ và tạo lại Activity đó. Điều này có nghĩa là chuỗi các phương thức onPause()
-> onStop()
-> onDestroy()
sẽ được gọi trên instance cũ, và sau đó onCreate()
-> onStart()
-> onResume()
sẽ được gọi trên instance mới.
Nếu bạn không xử lý, mọi dữ liệu tạm thời hoặc trạng thái UI của người dùng sẽ bị mất.
Để giải quyết vấn đề này, Android cung cấp hai phương thức đặc biệt:
onSaveInstanceState(Bundle outState)
: Được gọi bởi hệ thống trước khi Activity có thể bị hệ thống kết thúc (ví dụ: do thay đổi cấu hình hoặc thiếu bộ nhớ).- Bạn ghi đè phương thức này để lưu trữ trạng thái động của Activity vào một đối tượng
Bundle
. Bundle này là một cặp key-value, nơi bạn có thể lưu trữ các kiểu dữ liệu nguyên thủy (primitive types) và một số đối tượng có thể tuần tự hóa (serializable) hoặc phân loại hóa (parcelable). - Phương thức này không được gọi khi người dùng tự thoát khỏi Activity (ví dụ: nhấn nút Back) vì trong trường hợp đó, trạng thái không cần được lưu lại.
- Bạn ghi đè phương thức này để lưu trữ trạng thái động của Activity vào một đối tượng
- Khôi phục trạng thái: Bạn có thể khôi phục trạng thái đã lưu từ Bundle trong
onCreate(Bundle savedInstanceState)
hoặconRestoreInstanceState(Bundle savedInstanceState)
.- Bundle được truyền vào
onCreate()
sẽ chứa trạng thái đã lưu nếu Activity được tạo lại sau khi bị hệ thống kết thúc. Nếu không, Bundle này sẽ lànull
. onRestoreInstanceState()
được gọi sauonStart()
(chỉ khi có trạng thái được lưu) và sau khionCreate()
đã khôi phục giao diện View. Phương thức này ít dùng hơn, thường thì việc khôi phục trạng thái được xử lý trongonCreate()
.
- Bundle được truyền vào
Ví dụ lưu/khôi phục trạng thái đơn giản:
override fun onSaveInstanceState(outState: Bundle) {
// Lưu một giá trị String vào Bundle với key "myEditableText"
val currentText = myEditText.text.toString()
outState.putString("myEditableText", currentText)
Log.d(TAG, "onSaveInstanceState: Saving '$currentText'")
super.onSaveInstanceState(outState) // Luôn gọi superclass
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate")
// Khôi phục trạng thái nếu savedInstanceState không null
if (savedInstanceState != null) {
val savedText = savedInstanceState.getString("myEditableText")
if (savedText != null) {
myEditText.setText(savedText)
Log.d(TAG, "onCreate: Restored '$savedText'")
}
}
// ... các khởi tạo khác ...
}
Đối với các trạng thái UI phức tạp hơn hoặc dữ liệu lớn, việc sử dụng onSaveInstanceState
có thể không phù hợp (Bundle có giới hạn kích thước và việc tuần tự hóa/phân loại hóa có thể tốn thời gian). Các kiến trúc hiện đại như ViewModel (sẽ tìm hiểu sau trong roadmap này) là giải pháp tốt hơn cho việc quản lý dữ liệu UI theo hướng lifecycle-aware (nhận biết vòng đời).
Thực Hành: Theo Dõi Vòng Đời Bằng Logcat
Cách tốt nhất để thực sự hiểu vòng đời là tự mình trải nghiệm nó. Hãy tạo một Activity đơn giản và thêm các câu lệnh log vào mỗi phương thức callback.
package com.yourdomain.mylifecycleapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
class MainActivity : AppCompatActivity() {
private val TAG = "LifecycleDemo" // Tag để lọc Logcat dễ hơn
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) // Giả sử bạn có layout activity_main.xml
Log.d(TAG, "============ onCreate() ============")
// Kiểm tra và khôi phục trạng thái ở đây
if (savedInstanceState != null) {
val savedValue = savedInstanceState.getString("my_test_state")
Log.d(TAG, "onCreate(): Restored state: $savedValue")
} else {
Log.d(TAG, "onCreate(): No saved state found.")
}
// Các thiết lập ban đầu khác...
}
override fun onStart() {
super.onStart()
Log.d(TAG, "============ onStart() ============")
}
override fun onResume() {
super.onResume()
Log.d(TAG, "============ onResume() ============")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "============ onPause() ============")
// Lưu trạng thái UI nhẹ ở đây nếu cần
}
override fun onStop() {
super.onStop()
Log.d(TAG, "============ onStop() ============")
// Giải phóng tài nguyên khi Activity không hiển thị
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "============ onDestroy() ============")
// Giải phóng tất cả tài nguyên còn lại
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, "============ onRestart() ============")
}
// Lưu trạng thái trước khi Activity có thể bị hủy bởi hệ thống
override fun onSaveInstanceState(outState: Bundle) {
Log.d(TAG, "============ onSaveInstanceState() ============")
// Ví dụ lưu một giá trị
outState.putString("my_test_state", "Hello from saved state!")
super.onSaveInstanceState(outState) // Luôn gọi superclass cuối cùng
}
// Có thể khôi phục trạng thái ở đây thay vì onCreate, ít dùng hơn
// override fun onRestoreInstanceState(savedInstanceState: Bundle) {
// super.onRestoreInstanceState(savedInstanceState)
// val restoredValue = savedInstanceState.getString("my_test_state")
// Log.d(TAG, "onRestoreInstanceState(): Restored state: $restoredValue")
// }
}
Sau khi thêm code này, chạy ứng dụng trên thiết bị ảo hoặc thật, mở cửa sổ Logcat trong Android Studio và lọc theo tag “LifecycleDemo”. Hãy thử các hành động sau và quan sát thứ tự các phương thức callback được gọi:
- Mở ứng dụng lần đầu.
- Nhấn nút Home (ứng dụng vào nền).
- Mở lại ứng dụng từ Recents screen.
- Mở một ứng dụng khác che phủ hoàn toàn ứng dụng của bạn.
- Quay lại ứng dụng của bạn.
- Xoay màn hình thiết bị.
- Nhấn nút Back để thoát Activity.
Việc quan sát trực tiếp sẽ giúp bạn hiểu rõ hơn về luồng đi của vòng đời trong các tình huống khác nhau.
Bảng Tổng Kết Các Phương Thức Vòng Đời Activity
Để dễ dàng tra cứu, đây là bảng tóm tắt các phương thức callback chính và mục đích sử dụng điển hình của chúng:
Phương thức Callback | Thời điểm gọi | Mục đích điển hình |
---|---|---|
onCreate() |
Khi Activity lần đầu được tạo. | Khởi tạo UI (setContentView ), thiết lập dữ liệu ban đầu, khôi phục trạng thái đã lưu (nếu có). |
onStart() |
Khi Activity trở nên hiển thị với người dùng. | Bắt đầu các hoạt động cần thiết khi Activity hiển thị (ví dụ: đăng ký BroadcastReceiver, khởi tạo các dịch vụ liên quan đến hiển thị). |
onResume() |
Khi Activity bắt đầu tương tác với người dùng, ở tiền cảnh. | Khởi động lại các hoạt động tạm dừng khi Activity bị tạm dừng (ví dụ: animation, truy cập camera, cập nhật UI liên tục, bắt đầu game loop). |
onPause() |
Khi Activity sắp mất tiêu điểm, nhưng vẫn còn hiển thị (có thể bị che một phần). | Lưu trạng thái UI nhẹ, tạm dừng các hoạt động tiêu tốn tài nguyên (ví dụ: dừng preview camera, dừng animation). **Phải hoàn thành nhanh chóng.** |
onStop() |
Khi Activity không còn hiển thị với người dùng. | Giải phóng tài nguyên không cần thiết khi Activity bị ẩn (ví dụ: ngắt kết nối mạng, hủy đăng ký broadcast receiver, dừng các dịch vụ không cần thiết ở chế độ nền). |
onDestroy() |
Khi Activity bị phá hủy hoàn toàn. | Giải phóng tất cả tài nguyên còn lại, dọn dẹp hoàn toàn (đóng database, hủy các thread, hủy bỏ các listener…). |
onRestart() |
Khi Activity đã dừng (Stopped) và sắp bắt đầu lại. | Được theo sau bởi onStart() . Ít dùng trực tiếp. |
onSaveInstanceState(Bundle) |
Trước khi Activity có thể bị hệ thống hủy để lưu trạng thái động. | Lưu trạng thái tạm thời (UI state) vào Bundle. |
onRestoreInstanceState(Bundle) |
Sau onStart() khi Activity được tạo lại với trạng thái đã lưu. |
Khôi phục trạng thái từ Bundle (thường thực hiện trong onCreate tiện hơn). |
Những Lưu Ý Quan Trọng và Lỗi Thường Gặp
* Không chặn luồng UI: Các phương thức vòng đời (đặc biệt là onCreate
, onResume
, onPause
) được gọi trên luồng chính (UI thread). Tránh thực hiện các tác vụ tốn thời gian như truy cập mạng, truy vấn database phức tạp, hoặc tính toán nặng trong các phương thức này để tránh ANRs.
* Giải phóng tài nguyên: Luôn giải phóng tài nguyên đã cấp phát. Cấp phát tài nguyên trong onCreate
/onStart
/onResume
thì nên giải phóng tương ứng trong onDestroy
/onStop
/onPause
. Ví dụ: đăng ký listener trong onStart
thì hủy đăng ký trong onStop
.
* onPause()
phải chạy nhanh: Như đã đề cập, Activity tiếp theo sẽ không được hiển thị cho đến khi onPause()
hoàn thành. Giữ cho nó nhẹ nhàng.
* Trạng thái của View: Hầu hết các View tiêu chuẩn (như EditText
, CheckBox
) tự động lưu và khôi phục trạng thái của chúng thông qua onSaveInstanceState
và onCreate
. Bạn chỉ cần xử lý trạng thái tùy chỉnh hoặc dữ liệu khác của Activity.
* Không dựa vào onDestroy()
để lưu dữ liệu quan trọng: onDestroy()
không phải lúc nào cũng được gọi (ví dụ: khi hệ thống kết thúc tiến trình ứng dụng ngay lập tức do thiếu bộ nhớ). Dữ liệu quan trọng nên được lưu trữ bền vững (database, Shared Preferences, file) vào những thời điểm thích hợp hơn, có thể là trong onPause()
hoặc khi dữ liệu được cập nhật.
Vòng Đời Activity Trong Bối Cảnh Android Developer Roadmap
Hiểu vòng đời Activity là viên gạch cực kỳ quan trọng trên con đường của bạn. Nó liên quan mật thiết đến cách bạn thiết kế ứng dụng, quản lý dữ liệu, xử lý các luồng (threading), và sử dụng các thành phần khác của Android như Services, Broadcast Receivers. Khi bạn học về các kiến trúc hiện đại hơn như MVVM, Clean Architecture, hoặc các thư viện Jetpack components như ViewModel, LiveData, Room, bạn sẽ thấy chúng được thiết kế để làm việc hòa hợp với vòng đời Activity, giúp bạn quản lý trạng thái và tài nguyên một cách hiệu quả và an toàn hơn.
Kiến thức về vòng đời cũng giúp bạn gỡ lỗi (debug) hiệu quả hơn khi ứng dụng gặp sự cố trong các kịch bản chuyển đổi trạng thái.
Kết Luận
Vòng đời Activity là một khái niệm nền tảng và không thể thiếu đối với bất kỳ lập trình viên Android nào. Mặc dù ban đầu có vẻ hơi phức tạp với nhiều trạng thái và phương thức callback, nhưng bằng cách thực hành, quan sát Logcat và hiểu rõ mục đích của từng phương thức, bạn sẽ dần làm chủ được nó.
Hãy dành thời gian thực hành với ví dụ logcat ở trên, thử nghiệm các kịch bản khác nhau trên thiết bị của bạn. Đừng ngại quay lại bài viết này hoặc tham khảo tài liệu chính thức của Android Developers khi cần.
Nắm vững vòng đời Activity sẽ mở ra cánh cửa để bạn xây dựng những ứng dụng Android mạnh mẽ, ổn định và mang lại trải nghiệm tốt cho người dùng. Đây là một bước tiến quan trọng trong lộ trình trở thành Lập trình viên Android của bạn.
Trong bài viết tiếp theo, chúng ta sẽ khám phá cách các Activity tương tác với nhau thông qua Intent và cách chuyển đổi giữa các màn hình trong ứng dụng Android. Hẹn gặp lại!