Lớp học lập trình này nằm trong khóa học về Khái niệm cơ bản về Android Kotlin. Bạn sẽ nhận được nhiều giá trị nhất từ khóa học này nếu bạn làm việc qua các lớp học lập trình theo trình tự. Tất cả các lớp học lập trình trong khóa học đều có trên trang đích của các lớp học lập trình cơ bản về Android Kotlin.
Giới thiệu
Trong lớp học lập trình vừa qua, bạn đã tìm hiểu về vòng đời của Activity
và Fragment
, đồng thời đã tìm hiểu 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. Các 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ý hơn và dễ bảo trì hơn.
Những điều bạn nên biết
- Hoạt động là gì và cách tạo hoạt động trong ứng dụng.
- Thông tin cơ bản về vòng đời
Activity
vàFragment
cũng như các lệnh gọi lại được gọi khi một hoạt động chuyển đổi giữa các trạng thái. - Cách ghi đè 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 của hoạt động hoặc 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 lệnh gọi lại vòng đời.
- Cách sử dụng thư viện vòng đời Android để tạo đối tượng tiếp nhận dữ liệu về vòng đời, đồng thời giúp quản lý vòng đời của hoạt động và mảnh dễ dàng hơn.
- Quá trình tắt Android sẽ ảnh hưởng như thế nào đến dữ liệu trong ứng dụng của bạn và 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 cấu hình khác tạo ra các 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 để bao gồm hàm bộ tính giờ, đồng thời bắt đầu và dừng bộ tính giờ đó vào 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
DessertTimer
thành trình quan sát 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 của bạn và những lệnh 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 mở rộng ứng dụng DessertClicker trong lớp học lập trình trước đó. Bạn thêm bộ tính giờ trong 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 đây, bạn đã tìm hiểu cách quan sát vòng đời của hoạt động và mảnh bằng cách ghi đè các lệnh gọi lại khác nhau trong vòng đời và ghi nhật ký khi hệ thống gọi các lệnh gọi lại đó. Trong tác vụ này, bạn sẽ khám phá một ví dụ phức tạp hơn về việc quản lý tác vụ vòng đời trong ứng dụng DessertClicker. Bạn sử dụng đồng hồ hẹn giờ để in câu lệnh nhật ký mỗi giây, trong đó có số giây đã chạy báo cáo này.
Bước 1: Thiết lập DessertTimer
- Mở ứng dụng DessertClicker trong lớp học lập trình cuối cùng. (Bạn có thể tải DessertClickerLogs tại đây nếu không có ứng dụng.)
- Trong chế độ xem Dự án, hãy mở rộng java > com.example.android.{/7}clicker và mở
DessertTimer.kt
. Xin lưu ý rằng ngay bây giờ, tất cả mã đều được nhận xét để mã không chạy trong ứng dụng này. - Chọn tất cả mã trong cửa sổ trình chỉnh sửa. Chọn 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 bỏ nhận xét tất cả mã trong tệp. (Android Studio có thể hiển thị các lỗi tham chiếu chưa được giải quyết cho đến khi bạn tạo lại ứng dụng.) - Xin lưu ý rằng lớp
DessertTimer
cóstartTimer()
vàstopTimer()
. Lớp này bắt đầu và dừng đồng hồ hẹn giờ. KhistartTimer()
đang chạy, bộ tính giờ sẽ in một thông điệp nhật ký mỗi giây, với tổng số giây là thời gian đã chạy. Đổi lại, 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 dưới biếndessertsSold
, hãy thêm một biến cho bộ tính giờ:
private lateinit var dessertTimer : DessertTimer;
- Di chuyển xuống
onCreate()
và tạo đối tượngDessertTimer
mới, ngay sau lệnh gọi tớisetOnClickListener()
:
dessertTimer = DessertTimer()
Bây giờ, bạn đã có một đối tượng đồng hồ hẹn giờ tráng miệng, hãy cân nhắc nơi bạn nên bắt đầu và dừng đồng hồ hẹn giờ để chỉ chạy đồng hồ khi hoạt động xuất hiện trên màn hình. Bạn xem xét một vài lựa chọn trong 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ị. Các lệnh gọi lại này có vẻ phù hợp với thời điểm bắt đầu và dừng đồng hồ hẹn giờ.
- Trong lớp
MainActivity
, hãy bắt đầu hẹn giờ trong lệnh gọi lạionStart()
:
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}
- Dừng đồng hồ hẹn giờ trong
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
. Bộ lọc này sẽ lọc theo cả lớpMainActivity
vàDessertTimer
. Xin lưu ý rằng sau khi ứng dụng khởi động, đồng hồ 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à nhận thấy rằng đồng hồ hẹn giờ dừng lại. Đồng hồ hẹn giờ dừng vì cả hoạt động và đồng hồ hẹn giờ mà chế độ điều khiển đó đã bị hủy bỏ.
- Sử dụng màn hình gần đây để quay lại ứng dụng. Thông báo trong Logcat rằng bộ tính giờ khởi động lại từ 0.
- Nhấp vào nút Chia sẻ. Thông báo trong Logcat rằng bộ hẹn giờ vẫn đang chạy.
- Nhấp vào nút Home (Trang chủ). Thông báo trong Logcat rằng bộ hẹn giờ ngừng chạy.
- Sử dụng màn hình gần đây để quay lại ứng dụng. Thông báo trong Logcat rằng bộ tính giờ khởi động lại từ nơi đã dừng lại.
- Trong
MainActivity
, trong phương thứconStop()
, hãy nhận xét cuộc gọi đếnstopTimer()
. Việc nhận xétstopTimer()
sẽ cho biết trường hợp bạn bắt đầu một hoạt động trongonStart()
, nhưng quên dừng lại lần nữa trongonStop()
. - Biên dịch và chạy ứng dụng và nhấp vào nút Màn hình chính sau khi bộ tính giờ bắt đầu. Mặc dù ứng dụng chạy ở chế độ nền, bộ hẹn giờ đang chạy và liên tục sử dụng các tài nguyên của hệ thống. Việc hẹn giờ tiếp tục chạy là một sự cố rò rỉ bộ nhớ đối với ứng dụng của bạn và có thể không phải là hành vi bạn muốn.
Hình thức chung là khi bạn thiết lập hoặc bắt đầu một nội dung nào đó trong lệnh gọi lại, bạn sẽ dừng hoặc xóa nội dung đó trong lệnh gọi lại tương ứng. Bằng cách này, bạn có thể tránh chạy bất cứ thứ gì khi không cần thiết nữa.
- Hủy nhận xét dòng trong
onStop()
nơi bạn dừng đồng hồ hẹn giờ. - Cắt và dán cuộc gọi
startTimer()
từonStart()
sangonCreate()
. Thay đổi này minh họa trường hợp bạn vừa khởi tạo và vừa bắt đầu một tài nguyên trongonCreate()
, thay vì sử dụngonCreate()
để khởi tạo vàonStart()
để bắt đầu. - Biên dịch và chạy ứng dụng. Xin lưu ý rằng đồng hồ hẹn giờ bắt đầu chạy, như bạn mong đợi.
- Nhấp vào Trang chủ để dừng ứng dụng. Đồng hồ hẹn giờ sẽ ngừng chạy, như bạn mong đợi.
- Hãy dùng màn hình gần đây để quay lại ứng dụng. Xin lưu ý rằng bộ tính giờ không bắt đầu lại trong trường hợp này vì
onCreate()
chỉ được gọi khi ứng dụng khởi động. Ứng dụng sẽ không gọi khi ứng dụng quay về 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 trong vòng đời, cũng phải chia nhỏ tài nguyên đó.
- Thiết lập và chia nhỏ theo các phương pháp tương ứng.
- Nếu bạn thiết lập điều gì đó trong
onStart()
, hãy dừng hoặc chia nhỏ lại lần nữa trongonStop()
.
Trong ứng dụng DessertClicker, bạn có thể thấy rằng nếu đã bắt đầu hẹn giờ trong onStart()
, thì bạn cần dừng bộ tính giờ trong onStop()
. Chỉ có một đồng hồ hẹn giờ, vì vậy, việc dừng đồng hồ hẹn giờ không khó nhớ.
Trong một ứng dụng Android phức tạp hơn, bạn có thể thiết lập nhiều tùy chọn trong onStart()
hoặc onCreate()
, sau đó chia nhỏ tất cả trong onStop()
hoặc onDestroy()
. Ví dụ: bạn có thể đang sử dụng tính năng hiệu ứng động, nhạc, cảm biến hoặc bộ tính giờ để thiết lập và chia nhỏ đồng thời, bắt đầu và dừng. Nếu bạn quên một lỗi, sẽ dẫn đến lỗi và đau đầu.
Thư viện vòng đời, thuộc Android Jetpack, giúp đơn giản hóa việc này. Thư viện đặc biệt hữu ích trong trường hợp bạn phải theo dõi nhiều phần chuyển động, một số phần trong số đó ở các trạng thái vòng đời khác nhau. Thư viện xoay vòng cách thức hoạt động của vòng đời: Thông thường, hoạt động hoặc mảnh cho một thành phần (chẳng hạn như DessertTimer
) phải làm gì khi 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, thành phần này sẽ tự theo dõi các thay đổi về vòng đời, sau đó làm những gì 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 đó "own") một vòng đời.
Activity
vàFragment
là các chủ sở hữu vòng đời. Chủ sở hữu vòng đời triển khai giao diệnLifecycleOwner
. - Lớp
Lifecycle
, lưu giữ trạng thái thực tế của chủ sở hữu vòng đời và kích hoạt các sự kiện khi thay đổi vòng đời xảy ra. - Quan sát vòng đời, quan sát trạng thái vòng đời và thực hiện các tác vụ khi vòng đời thay đổi. Trình quan sát vòng đời triển khai giao diện
LifecycleObserver
.
Trong nhiệm vụ này, bạn chuyển đổi ứng dụng DessertClicker để sử dụng thư viện vòng đời trên Android và tìm hiểu cách thư viện giúp xử lý hoạt động trên Android và vòng đời của mảnh dễ dàng hơn.
Bước 1: Biế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 quan sát vòng đời. Chế độ quan sát cho phép các lớp (chẳng hạn như DessertTimer
) biết về hoạt động hoặc vòng đời của mảnh, đồng thời bắt đầu và dừng lại để phản ứng với các thay đổi đối với trạng thái vòng đời đó. Với một đối tượng tiếp nhận dữ liệu về vòng đời, bạn có thể xóa trách nhiệm bắt đầu 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 học
DesertTimer.kt
. - Thay đổi chữ ký lớp của lớp
DessertTimer
để trông như sau:
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {
Định nghĩa lớp mới này thực hiện hai việc:
- Hàm dựng sẽ nhận một đối tượng
Lifecycle
, đây là vòng đời mà bộ tính giờ đang quan sát. - Định nghĩa lớp sẽ triển khai giao diện
LifecycleObserver
.
- Bên dưới biến
runnable
, hãy thêm một khốiinit
và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 đã chuyển từ chủ sở hữu (hoạt động) đến lớp này (đối tượng tiếp nhận dữ liệu).
init {
lifecycle.addObserver(this)
}
- Chú thích
startTimer()
bằng@OnLifecycleEvent annotation
và sử dụng sự kiện trong 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ó 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()
, bằng cách 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à chủ sở hữu vòng đời thông qua tính kế thừa, vì lớp cao cấp FragmentActivity
sẽ triển khai LifecycleOwner
. Do đó, bạn không cần phải làm gì để nhận biết hoạt động của mình trong vòng đời. Tất cả những gì bạn cần làm là chuyển đối tượng vòng đời của hoạt động vào hàm dựng DessertTimer
.
- Mở
MainActivity
. Trong phương thứconCreate()
, hãy sửa đổi quá trình khởi tạoDessertTimer
để bao gồ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.
- Xóa cuộc gọi tới
startTimer()
ởonCreate()
và cuộc gọi đếnstopTimer()
ởonStop()
. Bạn không cần phải thông báo choDessertTimer
biết việc cần làm trong hoạt động nữa, vìDessertTimer
hiện đang quan sát chính vòng đời và tự động đượ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 đều là nhật ký tin nhắn. - Biên dịch và chạy ứng dụng và mở Logcat. Xin lưu ý rằng đồng hồ hẹn giờ đã bắt đầu chạy, như mong đợi.
- Nhấp vào nút màn hình chính để đặt ứng dụng ở chế độ nền. Bạn có thể thấy rằng đồng hồ hẹn giờ đã ngừng chạy như mong đợi.
Điều gì sẽ xảy ra với ứng dụng của bạn và dữ liệu của ứng dụng nếu Android tắt ứng dụng đó khi ứng dụng đang chạy trong nền? Trường hợp phức tạp này rất quan trọng.
Khi ứng dụng của bạn chạy trong nền, ứng dụng không bị hủy bỏ, chỉ dừng lại và chờ người dùng quay lại. Nhưng một trong những mối quan ngại chính của hệ điều hành Android là giữ cho hoạt động ở nền trước hoạt động trơn tru. Ví dụ: nếu người dùng của bạn đang sử dụng ứng dụng GPS để giúp họ bắt xe buýt, điều quan trọng là phải kết xuất ứng dụng GPS đó nhanh chóng và tiếp tục hiển thị chỉ đường. Việc giữ lại ứng dụng DessertClicker ít quan trọng hơn mà người dùng có thể chưa xem xét trong vài ngày, chạy ở chế độ nề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í sẽ tắt toàn bộ quá trình ứng dụng, bao gồm mọi hoạt động liên quan đến ứng dụng. Android thực hiện loại tắt này khi hệ thống bị căng thẳng và có nguy cơ bị trễ một cách trực quan, vì vậy, sẽ không có lệnh gọi lại hoặc mã nào khá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 ứng dụng mà 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 mô phỏng một quy trình tắt Android và kiểm tra đ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 một quy 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 trình mô phỏng và thiết bị đính kèm vào máy tính của mình. Ở bước này, bạn dùng adb
để đóng quy trình của ứng dụng và xem điều gì sẽ 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ài lần vào bánh cupcake.
- Nhấn nút Màn hình chính để đặt ứng dụng của bạn ở chế độ nền. Ứng dụng của bạn hiện đã bị dừng và ứng dụng sẽ bị đóng nếu Android cần tài nguyên mà ứng dụng đang dùng.
- Trong Android Studio, hãy nhấp vào thẻ Terminal (Thiết bị đầu cuối) để mở dòng lệnh của dòng lệnh.
- Nhập
adb
rồi nhấn Quay lại.
Nếu bạn thấy nhiều kết quả bắt đầu bằngAndroid Debug Bridge version X.XX.X
và kết thúc bằngtags to be used by logcat (see logcat —h
elp), thì mọi thứ đều ổn. Nếu thấyadb: command not found
, hãy đảm bảo bạn có lệnhadb
trong đường dẫn thực thi. Để biết hướng dẫn, hãy xem "Thêm adb vào đường dẫn thực thi của bạn" 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 vào Quay lại:
adb shell am kill com.example.android.dessertclicker
Lệnh này yêu cầu trình mô phỏng hoặc bất kỳ thiết bị đã kết nối nào dừng quá trình này bằng tên gói dessertclicker
, nhưng chỉ khi ứng dụng chạy ở chế độ nền. Vì ứng dụng của bạn đang chạy trong nền nên không có gì hiển thị trên thiết bị hoặc màn hình trình mô phỏng để cho biết rằng quá trình của bạn đã dừng lại. Trong Android Studio, nhấp vào thẻ Chạy để xem thông báo có nội dung “Bạn đã chấm dứt ứng dụng”. Nhấp vào thẻ Logcat để xem lệnh gọi lại onDestroy()
không bao giờ chạy – hoạt động của bạn chỉ kết thúc.
- Sử dụng màn hình gần đây để quay lại ứng dụng. Ứng dụng của bạn xuất hiện trong thời gian gần đây cho dù ứng dụng đã được đưa vào nền hay đã bị dừ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ẽ bắt đầu lại. Hoạt động này sẽ trải qua toàn bộ các lệnh gọi lại trong vòng đời khởi động, bao gồm cả
onCreate()
. - Xin lưu ý rằng khi ứng dụng khởi động lại, ứng dụng sẽ đặt lại "score" (cả số lượng món tráng miệng đã bán và tổng số tiền) thành giá trị mặc định (0). Nếu Android tắt ứng dụng của bạn, tại sao lại không lưu trạng thái của bạn?
Khi hệ điều hành khởi động lại 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 sẽ lấy trạng thái của một số chế độ xem của bạn và lưu chế độ đó trong 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 Chỉnh sửa văn bản (miễn là chúng có mã được đặt trong bố cục) và ngăn xếp lùi hoạt động của bạn.
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 tùy chỉnh nhưrevenue
trong ứ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 đố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 sử dụng để lưu mọi dữ liệu mà bạn có thể cần nếu Hệ điều hành Android hủy bỏ ứng dụng của bạn. Trong sơ đồ gọi lại trong vòng đời, onSaveInstanceState()
được gọi sau khi hoạt động đã 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). Giờ đây, hệ thống sẽ lưu dữ liệu này vì nếu chờ cho đến khi tắt ứng dụng, thì hệ điều hành có thể đang bị áp lực tài nguyên. Việc lưu dữ liệu mỗi lần đảm bảo rằng dữ liệu cập nhật trong gói có sẵn để khôi phục, 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. Xin lưu ý rằng lệnh gọi lại
onSaveInstanceState()
xảy ra ngay 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ể.
- Di chuyển xuống
onSaveInstanceState()
và nhận thấy thông sốoutState
thuộc loạiBundle
.
Gói là một tập hợp các cặp khóa-giá trị, trong đó các khóa luôn là chuỗi. Bạn có thể đặt các giá trị nguyên thủy, chẳng hạn như giá trịint
vàboolean
, vào gói.
Vì hệ thống sẽ giữ gói này trong RAM nên phương pháp hay nhất là giữ cho dữ liệu trong gói nhỏ gọn. 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ữ dưới 100 nghìn người dùng, nếu không bạn có thể gặp rủi ro khi gặp 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 này với số lượng món tráng miệng đã bán và trạng thái của đồng hồ 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 bắt đầu lại do quá trình tắt, gói mà bạn đã lưu sẽ được chuyển vào onCreate()
. Nếu hoạt động của bạn bắt đầu lại từ đầu, thì gói này trong onCreate()
sẽ là null
. Vì vậy, nếu gói ứng dụng không phải là null
, thì bạn sẽ biết rằng bạn đang tạo
- Thêm mã này vào
onCreate()
, sau 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 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 đồng hồ 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 Màn hình chính để đặt ứng dụng ở 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
- Sử 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 trả lại doanh thu chính xác và các món tráng miệng đã bán giá trị của gói. Tuy nhiên, cũng xin lưu ý rằng món tráng miệng đó đã quay trở lại bánh cupcake. 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()
. Xin lưu ý rằng phương pháp này xác định hình ảnh món tráng miệng nào sẽ hiển thị trong hoạt động dựa trên số món tráng miệng hiện tại đã 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 và đặt ứng dụng trong nền. Dùng
adb
để tắt quy trình. Sử dụng màn hình gần đây để quay lại ứng dụng. Xin lưu ý rằng cả hai giá trị của món tráng miệng đã nêu, tổng doanh thu và hình ả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 của hoạt động và mảnh rất quan trọng: đó là cách các thay đổi về cấu hình ảnh hưởng đến vòng đời của các hoạt động và mảnh của bạn.
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ó thể cần thay đổi để phù hợp với các hướng văn bản khác nhau. 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 kích thước hiển thị hoặc bố cục 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 vòng 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 phím
Control
và các phím mũi tên (Command
và 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
.
Xin 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 lệnh 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 nhận xét 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 nhỏ vài lần và xoay thiết bị hoặc trình mô phỏng. Lần này, khi thiết bị xoay vòng và hoạt động bị tắt rồi tạo lại, hoạt động sẽ bắt đầu bằng các giá trị mặc định.
Khi thay đổi cấu hình xảy ra, Android sử dụng cùng một gói trạng thái phiên bản 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. Giống như khi tắt quy trình, hãy sử dụngonSaveInstanceState()
để đưa dữ liệu của ứng dụng vào gói. Sau đó, khôi phục dữ liệu trongonCreate()
để tránh mất dữ liệu trạng thái hoạt động nếu xoay thiết bị. - Trong
MainActivity
, hãy hủy nhận xét phương thứconSaveInstanceState()
, chạy ứng dụng, nhấp vào bánh cupcake và xoay ứng dụng hoặc thiết bị. Lưu ý lần này dữ liệu món tráng miệng được giữ lại trong chế độ xoay vòng 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 lệnh gọi lại trong vòng đời, hãy dừng hoặc xóa lệnh đó trong lệnh gọi lại tương ứng. Bằng cách dừng mọi thứ, bạn đảm bảo rằng nó không tiếp tục chạy khi không cần thiết nữa. Ví dụ: nếu bạn thiết lập đồng hồ hẹn giờ trong
onStart()
, bạn cần tạm dừng hoặc dừng đồng hồ hẹn giờ trongonStop()
. - Chỉ sử dụng
onCreate()
để khởi chạy các phần của ứng dụng sẽ chạy một lần khi ứng dụng khởi động lần đầu. DùngonStart()
để khởi động 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 về nền trước.
Thư viện vòng đời
- Dùng thư viện vòng đời của 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 cần nhận biết vòng đời.
- Chủ sở hữu vòng đời là các thành phần có vòng đời (và do đó "own"), vòng đời, bao gồm
Activity
vàFragment
. Chủ 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. Trình quan sát vòng đời triển khai giao diện
LifecycleObserver
. - Đối tượng
Lifecycle
chứa các trạng thái vòng đời thực 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
LifecycleObserver
trong các lớp cần nhận biết vòng đời. - Khởi tạo lớp quan sát vòng đời với đối tượng vòng đời từ hoạt động hoặc mảnh.
- Trong lớp quan sát vòng đời, hãy chú thích các phương thức nhận biết vòng đời với sự thay đổi trạng thái vòng đời mà họ quan tâm.
Ví dụ: chú thích@OnLifecycleEvent(Lifecycle.Event.ON_START)
cho biết rằng phương thức này đang xem sự kiện trong vòng đờionStart
.
Quy trình tắt và lưu trạng thái hoạt động
- Android điều chỉnh các ứng dụng chạy trong nền để ứng dụng này 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 lượng xử lý mà các ứng dụng trong nền có thể thực hiện và đôi khi, thậm chí còn tắt toàn bộ quy trình ứng dụng của bạn.
- Người dùng không thể biết liệu hệ thống đã tắt ứng dụng ở chế độ nền hay chưa. Ứng dụng vẫn xuất hiện trong màn hình gần đây và sẽ khởi động lại ở cùng 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 trình mô phỏng và thiết bị đính kèm vào máy tính của mình. Bạn có thể dùngadb
để mô phỏng một quy trình tắt trong ứng dụng của mình. - Khi Android tắt quá 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 vừa dừng lại.
Giữ lại hoạt động và trạng thái của mảnh
- Khi ứng dụng của bạn chạy ở 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 là một bản sao của
Bundle
, tập hợp các khóa 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ốsavedInstanceState
chứa gói này. - Nếu biến
savedInstanceState
chứanull
, thì hoạt động đã bắt đầu mà không có gói trạng thái và không có dữ liệu trạng thái để truy xuất. - Để truy xuất dữ liệu từ gói bằng khoá, sử dụng các phương thức
Bundle
bắ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ị thay đổi hoàn toàn để hệ thống có thể giải quyết thay đổi một cách dễ dàng nhất là tắt và thiết lập 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 xảy ra thay đổi cấu hình, 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, lưu trạng thái của ứng dụng vào gói trong
onSaveInstanceState()
.
Khóa học từ 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òng đời của hoạt động
- Điều khiển vòng đời bằng các yếu tố nhận biết vòng đời
LifecycleOwner
Lifecycle
LifecycleObserver
onSaveInstanceState()
- Xử lý thay đổi về cấu hình
- Lưu trạng thái giao diện người dùng
Các tài liệu khác:
- Timber (GitHub)
Phần này liệt kê các bài tập về nhà có thể được giao cho học viên đang làm việc qua lớp học lập trình này trong khóa học do người hướng dẫn tổ chức. Người hướng dẫn có thể làm những việc sau:
- Giao bài tập về nhà nếu được yêu cầu.
- Trao đổi với học viên 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 những đề xuất này ít hay nhiều tùy ý. Do đó, họ có thể thoải mái giao 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ự mình làm việc qua lớp học lập trình này, hãy thoải mái sử dụng các bài tập về nhà này để kiểm tra kiến thức của bạn.
Thay đổi ứng dụng
Mở ứng dụng DiceScroller từ Bài học 1. (Bạn có thể tải ứng dụng xuống tại đây nếu chưa có.) Biên dịch và chạy ứng dụng và 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 những câu hỏi này
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 thực tế.
- 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 thực tế.
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?
Lifecycle
LifecycleOwner
Lifecycle.Event
LifecycleObserver
Câu hỏi 4
Trong những trường hợp nào, phương thức onCreate()
trong hoạt động của bạn nhận được một Bundle
có chứa dữ liệu (nghĩa là Bundle
không phải là null
)? Có thể áp dụng nhiều câu trả lời.
- Hoạt động sẽ khởi động lại sau khi xoay thiết bị.
- 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 khóa học này, hãy xem trang đích của các lớp học lập trình cơ bản về Android Kotlin.