Lớp học lập trình này thuộc khoá học Kiến thức cơ bản về Kotlin cho Android. Bạn sẽ nhận được nhiều giá trị nhất qua khoá học này nếu thực hiện các lớp học lập trình theo trình tự. Tất cả lớp học lập trình của khoá học đều được liệt kê trên trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.
Giới thiệu
Trong lớp học lập trình gần đây nhất, bạn đã tìm hiểu về vòng đời Activity và Fragment, đồng thời khám phá các phương thức được gọi khi trạng thái vòng đời thay đổi trong các hoạt động và mảnh. Trong lớp học lập trình này, bạn sẽ tìm hiểu chi tiết hơn về vòng đời hoạt động. Bạn cũng tìm hiểu về thư viện vòng đời của Android Jetpack. Thư viện này có thể giúp bạn quản lý các sự kiện trong vòng đời bằng mã được sắp xếp hợp lý và dễ bảo trì hơn.
Kiến thức bạn cần có
- Hoạt động là gì và cách tạo hoạt động trong ứng dụng.
- Thông tin cơ bản trong vòng đời của
ActivityvàFragment, cũng như các lệnh gọi lại được gọi khi một hoạt động di chuyển giữa các trạng thái. - Cách ghi đè các phương thức gọi lại trong vòng đời
onCreate()vàonStop()để thực hiện các thao tác tại những thời điểm khác nhau trong vòng đời hoạt động hoặc vòng đời mảnh.
Kiến thức bạn sẽ học được
- Cách thiết lập, bắt đầu và dừng các phần của ứng dụng trong phương thức gọi lại vòng đời.
- Cách sử dụng thư viện vòng đời Android để tạo một đối tượng theo dõi vòng đời và giúp quản lý vòng đời hoạt động và mảnh dễ dàng hơn.
- Ảnh hưởng của việc tắt quy trình Android đến dữ liệu trong ứng dụng của bạn, cũng như cách tự động lưu và khôi phục dữ liệu đó khi Android đóng ứng dụng của bạn.
- Cách xoay thiết bị và các thay đổi khác về cấu hình tạo ra những thay đổi đối với trạng thái vòng đời và ảnh hưởng đến trạng thái của ứng dụng.
Bạn sẽ thực hiện
- Sửa đổi ứng dụng DessertClicker để thêm một hàm hẹn giờ, đồng thời bắt đầu và dừng hàm hẹn giờ đó tại nhiều thời điểm trong vòng đời hoạt động.
- Sửa đổi ứng dụng để sử dụng thư viện vòng đời Android và chuyển đổi lớp
DessertTimerthành một đối tượng theo dõi vòng đời. - Thiết lập và sử dụng Cầu gỡ lỗi Android (
adb) để mô phỏng quá trình tắt ứng dụng và các phương thức gọi lại trong vòng đời xảy ra sau đó. - Triển khai phương thức
onSaveInstanceState()để giữ lại dữ liệu ứng dụng có thể bị mất nếu ứng dụng bị đóng bất ngờ. Thêm mã để khôi phục dữ liệu đó khi ứng dụng khởi động lại.
Trong lớp học lập trình này, bạn sẽ mở rộng ứng dụng DessertClicker từ lớp học lập trình trước. Bạn thêm một bộ hẹn giờ chạy nền, sau đó chuyển đổi ứng dụng để sử dụng thư viện vòng đời Android.

Trong lớp học lập trình trước, bạn đã tìm hiểu cách theo dõi vòng đời hoạt động và vòng đời mảnh bằng cách ghi đè nhiều lệnh gọi lại trong vòng đời và ghi nhật ký khi hệ thống gọi các lệnh gọi lại đó. Trong nhiệm vụ này, bạn sẽ khám phá một ví dụ phức tạp hơn về cách quản lý các tác vụ trong vòng đời trong ứng dụng DessertClicker. Bạn sẽ sử dụng một bộ hẹn giờ in câu lệnh nhật ký mỗi giây, với số lượng giây mà bộ hẹn giờ đã chạy.
Bước 1: Thiết lập DessertTimer
- Mở ứng dụng DessertClicker của lớp học lập trình trước. (Bạn có thể tải DessertClickerLogs xuống tại đây nếu chưa có ứng dụng này.)
- Trong chế độ xem Project (Dự án), hãy mở rộng java > com.example.android.dessertclicker rồi mở
DessertTimer.kt. Lưu ý rằng hiện tại tất cả mã đều được đánh dấu là nhận xét, nên mã này không chạy như một phần của ứng dụng. - Chọn tất cả mã trong cửa sổ trình chỉnh sửa. Chọn Code > Comment with Line Comment (Mã > Nhận xét bằng nhận xét dòng) hoặc nhấn
Control+/(Command+/trên máy Mac). Lệnh này sẽ bỏ chú thích tất cả mã trong tệp. (Android Studio có thể hiện lỗi tham chiếu chưa được giải quyết cho đến khi bạn tạo lại ứng dụng.) - Lưu ý rằng lớp
DessertTimerbao gồmstartTimer()vàstopTimer(), dùng để bắt đầu và dừng bộ hẹn giờ. KhistartTimer()đang chạy, bộ tính giờ sẽ in một thông báo nhật ký mỗi giây, với tổng số giây mà thời gian đã chạy. Đến lượt, phương thứcstopTimer()sẽ dừng đồng hồ hẹn giờ và các câu lệnh nhật ký.
- Mở
MainActivity.kt. Ở đầu lớp, ngay bên dưới biếndessertsSold, hãy thêm một biến cho bộ hẹn giờ:
private lateinit var dessertTimer : DessertTimer;- Di chuyển xuống
onCreate()rồi tạo một đối tượngDessertTimermới, ngay sau lệnh gọi đếnsetOnClickListener():
dessertTimer = DessertTimer()
Giờ đây, bạn đã có một đối tượng bộ tính giờ món tráng miệng. Hãy cân nhắc xem bạn nên bắt đầu và dừng bộ tính giờ ở đâu để bộ tính giờ chỉ chạy khi hoạt động đang diễn ra trên màn hình. Bạn sẽ xem xét một vài lựa chọn ở các bước tiếp theo.
Bước 2: Bắt đầu và dừng đồng hồ hẹn giờ
Phương thức onStart() được gọi ngay trước khi hoạt động hiển thị. Phương thức onStop() được gọi sau khi hoạt động ngừng hiển thị. Những lệnh gọi lại này có vẻ là lựa chọn phù hợp để xác định thời điểm bắt đầu và dừng đồng hồ hẹn giờ.
- Trong lớp
MainActivity, hãy bắt đầu bộ hẹn giờ trong lệnh gọi lạionStart():
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}- Dừng hẹn giờ sau
onStop():
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}- Biên dịch và chạy ứng dụng. Trong Android Studio, hãy nhấp vào ngăn Logcat. Trong hộp tìm kiếm Logcat, hãy nhập
dessertclicker. Thao tác này sẽ lọc theo cả lớpMainActivityvàDessertTimer. Lưu ý rằng sau khi ứng dụng bắt đầu, bộ hẹn giờ cũng sẽ bắt đầu chạy ngay lập tức.
- Nhấp vào nút Quay lại và lưu ý rằng bộ hẹn giờ sẽ dừng lại. Bộ hẹn giờ dừng vì cả hoạt động và bộ hẹn giờ mà hoạt động đó kiểm soát đều đã bị huỷ.
- Dùng màn hình gần đây để quay lại ứng dụng. Lưu ý trong Logcat rằng bộ hẹn giờ khởi động lại từ 0.
- Nhấp vào nút Chia sẻ. Bạn có thể nhận thấy trong Logcat rằng bộ hẹn giờ vẫn đang chạy.

- Nhấp vào nút Trang chủ. Lưu ý trong Logcat rằng bộ hẹn giờ ngừng chạy.
- Dùng màn hình gần đây để quay lại ứng dụng. Lưu ý trong Logcat rằng bộ hẹn giờ sẽ khởi động lại từ điểm dừng trước đó.
- Trong
MainActivity, trong phương thứconStop(), hãy nhận xét về lệnh gọi đếnstopTimer(). Việc nhận xétstopTimer()minh hoạ trường hợp bạn bắt đầu một thao tác trongonStart()nhưng quên dừng lại trongonStop(). - Biên dịch và chạy ứng dụng, rồi nhấp vào nút Home sau khi bộ hẹn giờ bắt đầu. Mặc dù ứng dụng ở chế độ nền, nhưng đồng hồ hẹn giờ vẫn chạy và liên tục sử dụng tài nguyên hệ thống. Việc để bộ hẹn giờ tiếp tục chạy là một rò rỉ bộ nhớ cho ứng dụng của bạn và có thể không phải là hành vi bạn muốn.
Mẫu chung là khi bạn thiết lập hoặc bắt đầu một việc gì đó trong một lệnh gọi lại, bạn sẽ dừng hoặc xoá việc đó trong lệnh gọi lại tương ứng. Bằng cách này, bạn sẽ tránh được tình trạng có ứng dụng hoặc dịch vụ đang chạy khi không còn cần thiết.
- Bỏ chú thích dòng trong
onStop()nơi bạn dừng đồng hồ hẹn giờ. - Cắt và dán lệnh gọi
startTimer()từonStart()sangonCreate(). Thay đổi này minh hoạ trường hợp bạn vừa khởi chạy vừa bắt đầu một tài nguyên trongonCreate(), thay vì dùngonCreate()để khởi chạy vàonStart()để bắt đầu tài nguyên đó. - Biên dịch và chạy ứng dụng. Lưu ý rằng bộ tính giờ bắt đầu chạy như bạn mong đợi.
- Nhấp vào Home để dừng ứng dụng. Bộ hẹn giờ sẽ dừng chạy như bạn mong đợi.
- Dùng màn hình gần đây để quay lại ứng dụng. Lưu ý rằng bộ hẹn giờ không khởi động lại trong trường hợp này, vì
onCreate()chỉ được gọi khi ứng dụng khởi động, chứ không được gọi khi ứng dụng quay lại nền trước.
Những điểm chính cần nhớ:
- Khi bạn thiết lập một tài nguyên trong một lệnh gọi lại vòng đời, hãy huỷ tài nguyên đó.
- Thiết lập và huỷ trong các phương thức tương ứng.
- Nếu bạn thiết lập một thứ gì đó trong
onStart(), hãy dừng hoặc tháo dỡ lại trongonStop().
Trong ứng dụng DessertClicker, bạn có thể dễ dàng nhận thấy rằng nếu đã bắt đầu hẹn giờ trong onStart(), thì bạn cần dừng hẹn giờ trong onStop(). Chỉ có một đồng hồ hẹn giờ, nên bạn sẽ không khó nhớ cách dừng đồng hồ.
Trong một ứng dụng Android phức tạp hơn, bạn có thể thiết lập nhiều thứ trong onStart() hoặc onCreate(), sau đó xoá tất cả trong onStop() hoặc onDestroy(). Ví dụ: bạn có thể có các hình động, nhạc, cảm biến hoặc bộ hẹn giờ mà bạn cần thiết lập và tháo dỡ, cũng như khởi động và dừng. Nếu bạn quên một trong hai, điều đó sẽ dẫn đến lỗi và gây khó chịu.
Thư viện vòng đời (thuộc Android Jetpack) giúp đơn giản hoá nhiệm vụ này. Thư viện này đặc biệt hữu ích trong trường hợp bạn phải theo dõi nhiều thành phần chuyển động, một số thành phần ở các trạng thái vòng đời khác nhau. Thư viện này đảo ngược cách hoạt động của vòng đời: Thông thường, hoạt động hoặc mảnh sẽ cho một thành phần (chẳng hạn như DessertTimer) biết phải làm gì khi một lệnh gọi lại vòng đời xảy ra. Nhưng khi bạn sử dụng thư viện vòng đời, chính thành phần đó sẽ theo dõi các thay đổi trong vòng đời, sau đó thực hiện những việc cần thiết khi các thay đổi đó xảy ra.
Thư viện vòng đời có 3 phần chính:
- Chủ sở hữu vòng đời là những thành phần có (và do đó "sở hữu") một vòng đời.
ActivityvàFragmentlà chủ sở hữu vòng đời. Các đối tượng sở hữu vòng đời triển khai giao diệnLifecycleOwner. - Lớp
Lifecycle, chứa trạng thái thực tế của một chủ sở hữu vòng đời và kích hoạt các sự kiện khi có thay đổi về vòng đời. - Trình quan sát vòng đời, theo dõi trạng thái vòng đời và thực hiện các tác vụ khi vòng đời thay đổi. Các đối tượng tiếp nhận dữ liệu vòng đời triển khai giao diện
LifecycleObserver.
Trong nhiệm vụ này, bạn sẽ chuyển đổi ứng dụng DessertClicker để sử dụng thư viện vòng đời Android và tìm hiểu cách thư viện này giúp bạn dễ dàng quản lý vòng đời hoạt động và mảnh của Android hơn.
Bước 1: Chuyển DessertTimer thành LifecycleObserver
Một phần quan trọng của thư viện vòng đời là khái niệm về hoạt động quan sát vòng đời. Hoạt động quan sát cho phép các lớp (chẳng hạn như DessertTimer) biết về vòng đời hoạt động hoặc vòng đời mảnh, đồng thời tự khởi động và dừng để phản hồi những thay đổi đối với các trạng thái vòng đời đó. Với trình quan sát vòng đời, bạn có thể loại bỏ trách nhiệm khởi động và dừng các đối tượng khỏi các phương thức hoạt động và mảnh.
- Mở lớp
DesertTimer.kt. - Thay đổi chữ ký lớp của lớp
DessertTimerđể có dạng như sau:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {Định nghĩa lớp mới này thực hiện 2 việc:
- Hàm dựng lấy một đối tượng
Lifecycle, là vòng đời mà bộ hẹn giờ đang quan sát. - Định nghĩa lớp triển khai giao diện
LifecycleObserver.
- Bên dưới biến
runnable, hãy thêm một khốiinitvào định nghĩa lớp. Trong khốiinit, hãy dùng phương thứcaddObserver()để kết nối đối tượng vòng đời được truyền vào từ đối tượng sở hữu (hoạt động) với lớp này (trình quan sát).
init {
lifecycle.addObserver(this)
}- Chú thích
startTimer()bằng@OnLifecycleEvent annotationvà sử dụng sự kiện vòng đờiON_START. Tất cả các sự kiện trong vòng đời mà trình quan sát vòng đời của bạn có thể quan sát đều nằm trong lớpLifecycle.Event.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {- Làm tương tự với
stopTimer(), sử dụng sự kiệnON_STOP:
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()Bước 2: Sửa đổi MainActivity
Lớp MainActivity của bạn đã là một đối tượng sở hữu vòng đời thông qua hoạt động kế thừa, vì siêu lớp FragmentActivity triển khai LifecycleOwner. Do đó, bạn không cần làm gì để hoạt động của mình nhận biết được vòng đời. Bạn chỉ cần truyền đối tượng vòng đời của hoạt động vào hàm khởi tạo DessertTimer.
- Mở
MainActivity. Trong phương thứconCreate(), hãy sửa đổi quá trình khởi chạyDessertTimerđể thêmthis.lifecycle:
dessertTimer = DessertTimer(this.lifecycle)Thuộc tính lifecycle của hoạt động chứa đối tượng Lifecycle mà hoạt động này sở hữu.
- Xoá lệnh gọi đến
startTimer()trongonCreate()và lệnh gọi đếnstopTimer()trongonStop(). Bạn không cần choDessertTimerbiết phải làm gì trong hoạt động nữa, vìDessertTimerhiện đang theo dõi chính vòng đời và tự động nhận được thông báo khi trạng thái vòng đời thay đổi. Giờ đây, tất cả những gì bạn làm trong các lệnh gọi lại này là ghi lại một thông báo. - Biên dịch và chạy ứng dụng, sau đó mở Logcat. Lưu ý rằng đồng hồ hẹn giờ đã bắt đầu chạy như dự kiến.

- Nhấp vào nút trang chủ để đưa ứng dụng vào chế độ nền. Lưu ý rằng đồng hồ hẹn giờ đã dừng chạy như dự kiến.
Điều gì sẽ xảy ra với ứng dụng và dữ liệu của ứng dụng nếu Android tắt ứng dụng đó khi ứng dụng đang ở chế độ nền? Bạn cần hiểu rõ trường hợp đặc biệt này.
Khi ứng dụng của bạn chuyển sang chế độ nền, ứng dụng đó sẽ không bị huỷ mà chỉ dừng lại và chờ người dùng quay lại. Tuy nhiên, một trong những mối lo ngại chính của hệ điều hành Android là duy trì hoạt động trơn tru của hoạt động ở nền trước. Ví dụ: nếu người dùng đang sử dụng một ứng dụng GPS để giúp họ bắt xe buýt, thì bạn cần hiển thị ứng dụng GPS đó một cách nhanh chóng và tiếp tục chỉ đường. Việc giữ cho ứng dụng DessertClicker (mà người dùng có thể không xem trong vài ngày) chạy trơn tru ở chế độ nền là điều ít quan trọng hơn.
Android điều chỉnh các ứng dụng ở chế độ nền để ứng dụng trên nền trước có thể chạy mà không gặp vấn đề gì. Ví dụ: Android giới hạn mức xử lý mà các ứng dụng chạy ở chế độ nền có thể làm.
Đôi khi, Android thậm chí còn tắt toàn bộ quy trình ứng dụng, bao gồm cả mọi hoạt động liên quan đến ứng dụng. Android thực hiện dạng tắt này khi hệ thống gặp áp lực và có nguy cơ bị trễ hình ảnh, do đó, không có lệnh gọi lại hoặc mã bổ sung nào được chạy vào thời điểm này. Quy trình của ứng dụng chỉ cần lặng lẽ tắt khi ở trong chế độ nền. Nhưng với người dùng, có vẻ như ứng dụng vẫn chưa được đóng. Khi người dùng quay lại một ứng dụng đã bị hệ điều hành Android tắt, Android sẽ khởi động lại ứng dụng đó.
Trong nhiệm vụ này, bạn sẽ mô phỏng quá trình tắt quy trình Android và kiểm tra xem điều gì sẽ xảy ra với ứng dụng của bạn khi ứng dụng khởi động lại.
Bước 1: Sử dụng adb để mô phỏng quá trình tắt
Cầu gỡ lỗi Android (adb) là một công cụ dòng lệnh cho phép bạn gửi hướng dẫn đến các trình mô phỏng và thiết bị được kết nối với máy tính. Trong bước này, bạn sẽ dùng adb để đóng quy trình của ứng dụng và xem điều gì xảy ra khi Android tắt ứng dụng.
- Biên dịch và chạy ứng dụng của bạn. Nhấp vào bánh nướng một vài lần.
- Nhấn nút Màn hình chính để chuyển ứng dụng sang chế độ nền. Ứng dụng của bạn hiện đã dừng và có thể bị đóng nếu Android cần các tài nguyên mà ứng dụng đang sử dụng.
- Trong Android Studio, hãy nhấp vào thẻ Terminal để mở cửa sổ dòng lệnh.

- Nhập
adbrồi nhấn phím Return.
Nếu bạn thấy nhiều đầu ra bắt đầu bằngAndroid Debug Bridge version X.XX.Xvà kết thúc bằngtags to be used by logcat (see logcat —help), thì mọi thứ đều ổn. Nếu bạn thấyadb: command not found, hãy đảm bảo rằng lệnhadbcó trong đường dẫn thực thi của bạn. Để biết hướng dẫn, hãy xem phần "Thêm adb vào đường dẫn thực thi" trong chương Tiện ích. - Sao chép và dán nhận xét này vào dòng lệnh rồi nhấn phím Return:
adb shell am kill com.example.android.dessertclickerLệnh này yêu cầu mọi thiết bị hoặc trình mô phỏng được kết nối dừng quy trình có tên gói dessertclicker, nhưng chỉ khi ứng dụng ở chế độ nền. Vì ứng dụng của bạn ở chế độ nền, nên không có nội dung nào xuất hiện trên màn hình thiết bị hoặc trình mô phỏng để cho biết rằng quy trình của bạn đã dừng. Trong Android Studio, hãy nhấp vào thẻ Run (Chạy) để xem thông báo "Application terminated" (Ứng dụng đã bị chấm dứt). Nhấp vào thẻ Logcat để xem phương thức gọi lại onDestroy() chưa bao giờ chạy – hoạt động của bạn chỉ đơn giản là kết thúc.
- Dùng màn hình gần đây để quay lại ứng dụng. Ứng dụng của bạn sẽ xuất hiện trong danh sách gần đây cho dù ứng dụng đã được chuyển sang chế độ nền hay đã ngừng hoàn toàn. Khi bạn dùng màn hình gần đây để quay lại ứng dụng, hoạt động sẽ khởi động lại. Hoạt động này trải qua toàn bộ tập hợp các lệnh gọi lại khởi động vòng đời, bao gồm cả
onCreate(). - Lưu ý rằng khi khởi động lại, ứng dụng sẽ đặt lại "điểm số" (cả số lượng món tráng miệng đã bán và tổng số tiền) về các giá trị mặc định (0). Nếu Android tắt ứng dụng của bạn, tại sao hệ thống không lưu trạng thái của ứng dụng?
Khi hệ điều hành khởi động lại ứng dụng cho bạn, Android sẽ cố gắng hết sức để đặt lại ứng dụng về trạng thái trước đó. Android lấy trạng thái của một số khung hiển thị và lưu trạng thái đó vào một gói bất cứ khi nào bạn rời khỏi hoạt động. Một số ví dụ về dữ liệu được lưu tự động là văn bản trong EditText (miễn là chúng có một mã nhận dạng được đặt trong bố cục) và ngăn xếp quay lại của hoạt động.
Tuy nhiên, đôi khi hệ điều hành Android không biết về tất cả dữ liệu của bạn. Ví dụ: nếu bạn có một biến tuỳ chỉnh nhưrevenuetrong ứng dụng DessertClicker, thì hệ điều hành Android không biết về dữ liệu này hoặc tầm quan trọng của dữ liệu này đối với hoạt động của bạn. Bạn cần tự thêm dữ liệu này vào gói.
Bước 2: Sử dụng onSaveInstanceState() để lưu dữ liệu gói
Phương thức onSaveInstanceState() là lệnh gọi lại mà bạn dùng để lưu mọi dữ liệu cần thiết nếu hệ điều hành Android huỷ ứng dụng của bạn. Trong sơ đồ lệnh gọi lại trong vòng đời, onSaveInstanceState() được gọi sau khi hoạt động bị dừng. Phương thức này được gọi mỗi khi ứng dụng chuyển sang chạy ở chế độ nền.

Coi cuộc gọi onSaveInstanceState() là biện pháp an toàn, nhờ đó, bạn có thể lưu một lượng nhỏ thông tin vào gói khi hoạt động thoát khỏi nền trước (foreground). Bây giờ, hệ thống lưu dữ liệu này vì nếu bạn đợi cho đến khi ứng dụng ngừng hoạt động, thì hệ điều hành có thể phải chịu áp lực về tài nguyên. Việc lưu dữ liệu mỗi lần đảm bảo rằng có thể khôi phục dữ liệu mới trong gói nếu cần.
- Trong
MainActivity, hãy ghi đè lệnh gọi lạionSaveInstanceState()và thêm câu lệnh nhật kýTimber.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}- Biên dịch và chạy ứng dụng rồi nhấp vào nút Home (Trang chủ) để đưa ứng dụng đó chạy trong chế độ nền. Lưu ý rằng lệnh gọi lại
onSaveInstanceState()chỉ xảy ra sauonPause()vàonStop():
- Ở đầu tệp, ngay trước định nghĩa của lớp, thêm các hằng số sau:
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"Bạn sẽ sử dụng các khoá này để lưu và truy xuất dữ liệu từ gói trạng thái thực thể.
- Cuộn xuống
onSaveInstanceState()và để ý tham sốoutStatethuộc loạiBundle.
Gói là một tập hợp các cặp khoá-giá trị, trong đó khoá luôn là chuỗi. Bạn có thể đặt các giá trị nguyên thuỷ, chẳng hạn như giá trịintvàboolean, vào gói này.
Vì hệ thống duy trì gói này trong RAM, do vậy tốt nhất nên duy trì dung lượng của dữ liệu trong gói ở mức thấp. Kích thước của gói này cũng bị hạn chế, cho dù kích thước thay đổi tuỳ theo loại thiết bị. Thông thường, bạn nên lưu trữ ít hơn 100.000, nếu không, ứng dụng của bạn có thể gặp sự cố với lỗiTransactionTooLargeException. - Trong
onSaveInstanceState(), hãy đặt giá trịrevenue(một số nguyên) vào gói bằng phương thứcputInt():
outState.putInt(KEY_REVENUE, revenue)Phương thức putInt() (và các phương thức tương tự từ lớp Bundle như putFloat() và putString() sẽ nhận hai đối số: một chuỗi cho khoá (hằng số KEY_REVENUE) và giá trị thực tế để lưu lại.
- Lặp lại quy trình tương tự với số lượng món tráng miệng đã bán và trạng thái của bộ hẹn giờ:
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)Bước 3: Sử dụng onCreate() để khôi phục dữ liệu gói
- Cuộn lên đến
onCreate()và kiểm tra chữ ký phương thức:
override fun onCreate(savedInstanceState: Bundle) {Lưu ý rằng onCreate() nhận được Bundle mỗi khi được gọi. Khi hoạt động của bạn được khởi động lại do tắt quá trình, gói bạn đã lưu được chuyển vào onCreate(). Nếu hoạt động của bạn bắt đầu được làm mới, thì gói này trong onCreate() sẽ là null. Do vậy, nếu gói không phải là null, thì bạn biết bạn đang "tạo lại" hoạt động từ một điểm đã biết trước đó.
- Thêm mã này vào
onCreate(), sau khi thiết lậpDessertTimer:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}Quá trình kiểm tra null sẽ xác định xem có dữ liệu trong gói hay không hoặc liệu gói có phải là null hay không, từ đó cho biết ứng dụng được bắt đầu làm mới hay đã được tạo lại sau khi tắt. Kiểm thử này là một mô hình phổ biến để khôi phục dữ liệu từ gói.
Lưu ý rằng khoá bạn đã sử dụng tại đây (KEY_REVENUE) cũng là khoá bạn đã sử dụng cho putInt(). Để bảo đảm lần nào bạn cũng sử dụng cùng một khoá, phương pháp hay nhất là xác định các khoá đó là hằng số. Bạn sử dụng getInt() để lấy dữ liệu ra khỏi gói, giống như việc bạn dùng putInt() để đặt dữ liệu vào gói. Phương thức getInt() nhận hai đối số:
- Một chuỗi đóng vai trò là khoá, ví dụ:
"key_revenue"cho giá trị doanh thu. - Một giá trị mặc định trong trường hợp không có giá trị nào cho khoá đó trong gói.
Tiếp đó, số nguyên bạn nhận được từ gói sẽ được gán cho biến revenue và giao diện người dùng sẽ sử dụng giá trị đó.
- Thêm các phương thức
getInt()để khôi phục số lượng món tráng miệng đã bán và giá trị của bộ hẹn giờ:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}- Biên dịch và chạy ứng dụng. Nhấn vào bánh nướng ít nhất năm lần cho đến khi chuyển sang bánh donut. Nhấp vào Home (Trang chủ) để đưa ứng dụng vào chế độ nền.
- Trong thẻ Terminal (Thiết bị đầu cuối) của Android Studio, hãy chạy
adbđể tắt quy trình của ứng dụng.
adb shell am kill com.example.android.dessertclicker- Dùng màn hình gần đây để quay lại ứng dụng. Lưu ý rằng lần này ứng dụng sẽ quay lại với doanh thu và số món tráng miệng được bán chính xác từ gói. Tuy nhiên cũng lưu ý rằng món tráng miệng đã hiển thị trở lại thành bánh nướng. Chỉ còn một việc cần phải làm để bảo đảm ứng dụng quay lại sau một lần tắt chính xác như khi bạn rời khỏi ứng dụng.
- Trong
MainActivity, kiểm tra phương thứcshowCurrentDessert(). Lưu ý rằng phương thức này xác định ảnh món tráng miệng nào sẽ hiển thị trong hoạt động dựa trên số lượng món tráng miệng hiện được bán và danh sách món tráng miệng trong biếnallDesserts.
for (dessert in allDesserts) {
if (dessertsSold >= dessert.startProductionAmount) {
newDessert = dessert
}
else break
}Phương thức này dựa vào số lượng món tráng miệng được bán để chọn hình ảnh phù hợp. Do đó, bạn không cần phải làm gì để lưu trữ tham chiếu đến hình ảnh trong gói trong onSaveInstanceState(). Trong gói đó, bạn đã lưu trữ số lượng món tráng miệng bán được.
- Trong
onCreate(), trong khối khôi phục trạng thái từ gói, hãy gọishowCurrentDessert():
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
showCurrentDessert()
}- Biên dịch và chạy ứng dụng, rồi đưa ứng dụng đó vào chế độ nền. Sử dụng
adbđể tắt quy trình. Dùng màn hình gần đây để quay lại ứng dụng. Lưu ý rằng cả giá trị của món tráng miệng được bán, tổng doanh thu và ảnh món tráng miệng đều được khôi phục chính xác.
Có một trường hợp đặc biệt cuối cùng trong việc quản lý vòng đời hoạt động và vòng đời mảnh mà bạn cần phải nắm vững, đó là tác động của các thay đổi trong cấu hình đối với vòng đời của hoạt động và mảnh.
Thay đổi cấu hình xảy ra khi trạng thái của thiết bị hoàn toàn thay đổi đến mức cách dễ nhất để hệ thống giải quyết thay đổi là hoàn toàn tắt đi và xây dựng lại hoạt động. Ví dụ: nếu người dùng thay đổi ngôn ngữ của thiết bị, thì toàn bộ bố cục cần phải thay đổi cho phù hợp với các hướng văn bản. Nếu người dùng cắm thiết bị vào đế sạc hoặc thêm bàn phím thực, thì bố cục ứng dụng có thể cần tận dụng bố cục hoặc kích thước hiển thị khác. Còn nếu hướng thiết bị thay đổi—nếu thiết bị được xoay từ dọc sang ngang hoặc quay lại theo hướng khác—bố cục có thể cần đổi để phù hợp với hướng mới.
Bước 1: Khám phá tính năng xoay thiết bị và các lệnh gọi lại trong vòng đời
- Biên dịch và chạy ứng dụng của bạn, sau đó mở Logcat.
- Xoay thiết bị hoặc trình mô phỏng sang chế độ ngang. Bạn có thể xoay trình mô phỏng sang trái hoặc sang phải bằng các nút xoay hoặc bằng
Controlvà các phím mũi tên (Commandvà các phím mũi tên trên máy Mac).
- Kiểm tra kết quả đầu ra trong Logcat. Lọc kết quả đầu ra trên
MainActivity.
Lưu ý rằng khi thiết bị hoặc trình mô phỏng xoay màn hình, hệ thống sẽ gọi tất cả các phương thức gọi lại trong vòng đời để tắt hoạt động. Sau đó, khi hoạt động được tạo lại, hệ thống sẽ gọi tất cả các phương thức gọi lại trong vòng đời để khởi động hoạt động. - Trong
MainActivity, hãy thêm chú thích cho toàn bộ phương thứconSaveInstanceState(). - Biên dịch và chạy lại ứng dụng. Nhấp vào bánh nướng một vài lần rồi xoay thiết bị hoặc trình mô phỏng. Lần này, khi thiết bị xoay và hoạt động bị tắt rồi tạo lại, hoạt động sẽ khởi động với các giá trị mặc định.
Khi có thay đổi về cấu hình, Android sẽ dùng cùng một gói trạng thái của thực thể mà bạn đã tìm hiểu trong tác vụ trước để lưu và khôi phục trạng thái của ứng dụng. Tương tự như khi tắt quy trình, hãy dùngonSaveInstanceState()để đưa dữ liệu của ứng dụng vào gói. Sau đó, hãy khôi phục dữ liệu trongonCreate()để tránh mất dữ liệu trạng thái hoạt động nếu thiết bị bị xoay. - Trong
MainActivity, hãy xoá dấu nhận xét phương thứconSaveInstanceState(), chạy ứng dụng, nhấp vào bánh cupcake rồi xoay ứng dụng hoặc thiết bị. Lần này, hãy lưu ý rằng dữ liệu món tráng miệng được giữ lại trong quá trình xoay hoạt động.
Dự án Android Studio: DessertClickerFinal
Mẹo về vòng đời
- Nếu bạn thiết lập hoặc bắt đầu một thao tác trong một lệnh gọi lại trong vòng đời, hãy dừng hoặc xoá thao tác đó trong lệnh gọi lại tương ứng. Bằng cách dừng đối tượng, bạn đảm bảo rằng đối tượng đó không tiếp tục chạy khi không còn cần thiết nữa. Ví dụ: nếu thiết lập một bộ hẹn giờ trong
onStart(), bạn cần tạm dừng hoặc dừng bộ hẹn giờ đó trongonStop(). - Chỉ sử dụng
onCreate()để khởi chạy những phần của ứng dụng chạy một lần, khi ứng dụng khởi động lần đầu. Sử dụngonStart()để bắt đầu các phần của ứng dụng chạy cả khi ứng dụng khởi động và mỗi khi ứng dụng quay lại nền trước.
Thư viện vòng đời
- Sử dụng thư viện vòng đời Android để chuyển quyền kiểm soát vòng đời từ hoạt động hoặc mảnh sang thành phần thực tế cần nhận biết được vòng đời.
- Chủ sở hữu vòng đời là những thành phần có (và do đó "sở hữu") vòng đời, bao gồm cả
ActivityvàFragment. Các đối tượng sở hữu vòng đời triển khai giao diệnLifecycleOwner. - Đối tượng tiếp nhận dữ liệu vòng đời chú ý đến trạng thái vòng đời hiện tại và thực hiện các tác vụ khi vòng đời thay đổi. Các đối tượng tiếp nhận dữ liệu vòng đời triển khai giao diện
LifecycleObserver. - Các đối tượng
Lifecyclechứa các trạng thái vòng đời thực tế và kích hoạt các sự kiện khi vòng đời thay đổi.
Cách tạo một lớp nhận biết vòng đời:
- Triển khai giao diện
LifecycleObservertrong các lớp cần nhận biết vòng đời. - Khởi chạy một lớp đối tượng tiếp nhận dữ liệu vòng đời bằng đối tượng vòng đời từ hoạt động hoặc mảnh.
- Trong lớp đối tượng tiếp nhận dữ liệu vòng đời, hãy chú thích các phương thức nhận biết vòng đời bằng thay đổi trạng thái vòng đời mà chúng quan tâm.
Ví dụ: chú thích@OnLifecycleEvent(Lifecycle.Event.ON_START)cho biết phương thức đang theo dõi sự kiện vòng đờionStart.
Tắt quy trình và lưu trạng thái hoạt động
- Android điều chỉnh các ứng dụng chạy ở chế độ nền để ứng dụng trên nền trước có thể chạy mà không gặp vấn đề gì. Quy định này bao gồm việc giới hạn mức xử lý mà các ứng dụng ở chế độ nền có thể làm, và đôi khi thậm chí tắt toàn bộ quy trình của ứng dụng.
- Người dùng không thể biết liệu hệ thống có tắt một ứng dụng ở chế độ nền hay không. Ứng dụng vẫn xuất hiện trên màn hình gần đây và sẽ khởi động lại ở trạng thái mà người dùng đã rời khỏi.
- Cầu gỡ lỗi Android (
adb) là một công cụ dòng lệnh cho phép bạn gửi hướng dẫn đến các trình mô phỏng và thiết bị được kết nối với máy tính. Bạn có thể dùngadbđể mô phỏng quá trình tắt trong ứng dụng. - Khi Android tắt quy trình ứng dụng của bạn, phương thức vòng đời
onDestroy()sẽ không được gọi. Ứng dụng chỉ dừng lại.
Bảo tồn trạng thái hoạt động và trạng thái mảnh
- Khi ứng dụng của bạn chuyển sang chế độ nền, ngay sau khi
onStop()được gọi, dữ liệu ứng dụng sẽ được lưu vào một gói. Một vài dữ liệu ứng dụng, chẳng hạn như nội dung củaEditText, sẽ tự động được lưu cho bạn. - Gói này là một thực thể của
Bundle, một tập hợp các khoá và giá trị. Các khoá luôn là chuỗi. - Sử dụng lệnh gọi lại
onSaveInstanceState()để lưu dữ liệu khác vào gói mà bạn muốn giữ lại, ngay cả khi ứng dụng tự động tắt. Để đưa dữ liệu vào gói, sử dụng phương thức gói bắt đầu bằngput, chẳng hạn nhưputInt(). - Bạn có thể lấy lại dữ liệu từ gói trong phương thức
onRestoreInstanceState()hoặc phổ biến hơn là trongonCreate(). Phương thứconCreate()có tham sốsavedInstanceStatechứa gói này. - Nếu biến
savedInstanceStatechứanull, hoạt động được bắt đầu mà không cần gói trạng thái và không có dữ liệu trạng thái nào để truy xuất. - Để truy xuất dữ liệu từ gói bằng khoá, sử dụng các phương thức
Bundlebắt đầu bằngget, chẳng hạn nhưgetInt().
Thay đổi cấu hình
- Thay đổi cấu hình xảy ra khi trạng thái của thiết bị hoàn toàn thay đổi đến mức cách dễ nhất để hệ thống giải quyết thay đổi là tắt và tạo lại hoạt động.
- Ví dụ phổ biến nhất về việc thay đổi cấu hình là khi người dùng xoay thiết bị từ chế độ dọc sang chế độ ngang, hoặc từ chế độ ngang sang chế độ dọc. Việc thay đổi cấu hình cũng có thể xảy ra khi thay đổi ngôn ngữ của thiết bị hoặc thiết bị được kết nối với bàn phím phần cứng.
- Khi cấu hình thay đổi, Android sẽ gọi tất cả các lệnh gọi lại để tắt (shutdown callback) của vòng đời hoạt động. Sau đó, Android khởi động lại hoạt động từ đầu, chạy tất cả các lệnh gọi lại khởi động vòng đời.
- Khi Android tắt ứng dụng do thay đổi cấu hình, ứng dụng sẽ khởi động lại hoạt động bằng gói trạng thái có trên
onCreate(). - Cũng giống như trong chế độ tắt quy trình, hãy lưu trạng thái của ứng dụng vào gói trong
onSaveInstanceState().
Khoá học của Udacity:
Tài liệu dành cho nhà phát triển Android:
- Hoạt động (hướng dẫn về API)
Activity(Tài liệu tham khảo API)- Tìm hiểu về Vòng đời hoạt động
- Điều khiển vòng đời bằng các thành phần nhận biết vòng đời
LifecycleOwnerLifecycleLifecycleObserveronSaveInstanceState()- Xử lý thay đổi về cấu hình
- Lưu trạng thái giao diện người dùng
Khác:
- Timber (GitHub)
Phần này liệt kê các bài tập về nhà cho học viên của lớp học lập trình này trong phạm vi khoá học có người hướng dẫn. Người hướng dẫn phải thực hiện các việc sau đây:
- Giao bài tập về nhà nếu cần.
- Trao đổi với học viên về cách nộp bài tập về nhà.
- Chấm điểm bài tập về nhà.
Người hướng dẫn có thể sử dụng các đề xuất này ít hoặc nhiều tuỳ ý và nên giao cho học viên bất kỳ bài tập về nhà nào khác mà họ cảm thấy phù hợp.
Nếu bạn đang tự học các lớp học lập trình, hãy sử dụng những bài tập về nhà này để kiểm tra kiến thức của mình.
Thay đổi ứng dụng
Mở ứng dụng DiceRoller trong Bài 1. (Bạn có thể tải ứng dụng này xuống tại đây nếu chưa cài đặt.) Biên dịch và chạy ứng dụng, đồng thời lưu ý rằng nếu bạn xoay thiết bị, giá trị hiện tại của xúc xắc sẽ bị mất. Triển khai onSaveInstanceState() để giữ lại giá trị đó trong gói và khôi phục giá trị đó trong onCreate().
Trả lời các câu hỏi sau
Câu hỏi 1
Ứng dụng của bạn chứa một hoạt động mô phỏng thực tế yêu cầu sử dụng phép tính phức tạp để hiển thị. Sau đó, người dùng sẽ nhận được một cuộc gọi điện thoại. Câu nào sau đây là đúng?
- Trong cuộc gọi điện thoại, bạn nên tiếp tục tính toán vị trí của các vật thể trong hoạt động mô phỏng vật lý.
- Trong cuộc gọi điện thoại, bạn nên ngừng tính toán vị trí của các vật thể trong hoạt động mô phỏng vật lý.
Câu hỏi 2
Bạn nên ghi đè phương thức vòng đời nào để tạm dừng hoạt động mô phỏng khi ứng dụng không xuất hiện trên màn hình?
onDestroy()onStop()onPause()onSaveInstanceState()
Câu hỏi 3
Để lớp này nhận biết được vòng đời thông qua thư viện vòng đời của Android, lớp nên triển khai giao diện nào?
LifecycleLifecycleOwnerLifecycle.EventLifecycleObserver
Câu hỏi 4
Phương thức onCreate() trong hoạt động của bạn nhận được Bundle có dữ liệu trong các trường hợp nào (tức là Bundle không phải null)? Có thể có nhiều câu trả lời đúng.
- Hoạt động sẽ khởi động lại sau khi thiết bị được xoay.
- Hoạt động này sẽ bắt đầu từ đầu.
- Hoạt động sẽ tiếp tục sau khi người dùng trở lại từ nền.
- Khởi động lại thiết bị.
Bắt đầu bài học tiếp theo:
Để biết đường liên kết đến các lớp học lập trình khác trong khoá học này, hãy xem trang đích của lớp học lập trình Kiến thức cơ bản về cách tạo ứng dụng Android bằng Kotlin.