Các giai đoạn trong Vòng đời hoạt động

1. Trước khi bắt đầu

Trong lớp học lập trình này, bạn sẽ tìm hiểu về một phần cơ bản của Android: vòng đời hoạt động.

Trong vòng đời của mình, một hoạt động sẽ chuyển qua và đôi khi quay lại nhiều trạng thái khác nhau. Quá trình chuyển đổi trạng thái này được gọi là vòng đời hoạt động.

Trên Android, một hoạt động là điểm truy cập để tương tác với người dùng.

Trước đây, mỗi hoạt động sẽ hiện một màn hình trong ứng dụng. Nhờ vào những phương pháp hay nhất hiện tại, một hoạt động có thể hiện nhiều màn hình bằng cách hoán đổi các màn hình với nhau khi cần.

Vòng đời hoạt động kéo dài từ khi tạo hoạt động cho đến khi hoạt động bị huỷ, khi hệ thống thu hồi tài nguyên của hoạt động đó. Khi người dùng di chuyển vào và ra khỏi một hoạt động, mỗi hoạt động sẽ chuyển đổi qua nhiều trạng thái trong vòng đời hoạt động.

Là nhà phát triển Android, bạn cần hiểu rõ vòng đời hoạt động. Nếu hoạt động không phản hồi đúng cách với các thay đổi về trạng thái của vòng đời, ứng dụng có thể tạo ra các lỗi lạ, hành vi khó hiểu đối với người dùng hoặc sử dụng quá nhiều tài nguyên hệ thống của Android. Hiểu được vòng đời của Android và phản hồi đúng cách với các thay đổi về trạng thái của vòng đời là một phần quan trọng trong quá trình phát triển Android.

Điều kiện tiên quyết

  • Kiến thức về khái niệm của một hoạt động và cách tạo hoạt động trong ứng dụng
  • Kiến thức về những gì phương thức onCreate() của một hoạt động thực hiện và loại thao tác được thực hiện trong phương thức đó

Kiến thức bạn sẽ học được

  • Cách in thông tin ghi nhật ký vào Logcat.
  • Thông tin cơ bản trong vòng đời của Activity và các lệnh gọi lại được gọi khi hoạt động di chuyển giữa các trạng thái
  • Cách ghi đè phương thức gọi lại trong vòng đời để thực hiện thao tác tại nhiều thời điểm trong vòng đời hoạt động.

Sản phẩm bạn sẽ tạo ra

  • Sửa đổi một ứng dụng khởi đầu có tên là Dessert Clicker để thêm thông tin ghi nhật ký hiện trong Logcat.
  • Ghi đè phương thức gọi lại trong vòng đời và ghi lại các thay đổi vào trạng thái hoạt động.
  • Chạy ứng dụng và để ý thông tin ghi nhật ký xuất hiện khi hoạt động bắt đầu, dừng và tiếp tục.
  • Triển khai rememberSaveable để giữ lại dữ liệu ứng dụng có thể bị mất nếu cấu hình thiết bị thay đổi.

2. Tổng quan về ứng dụng

Ở lớp học lập trình này, bạn sẽ làm việc với một ứng dụng khởi đầu có tên là Dessert Clicker. Trong ứng dụng Dessert Clicker, mỗi lần người dùng nhấn vào một món tráng miệng trên màn hình, ứng dụng sẽ "mua" món tráng miệng đó cho người dùng. Ứng dụng sẽ cập nhật các giá trị trong bố cục về:

  • Số lượng món tráng miệng "được mua"
  • Tổng doanh thu từ các món tráng miệng "đã mua"

245d0bdfc09f4d54.png

Ứng dụng này chứa một số lỗi liên quan đến vòng đời của Android. Ví dụ: trong một số trường hợp, ứng dụng đặt lại các giá trị của món tráng miệng về 0. Khi nắm rõ vòng đời của ứng dụng Android, bạn sẽ hiểu được nguyên nhân của những vấn đề này và cách khắc phục.

Tải mã khởi động xuống

Trong Android Studio, hãy mở thư mục basic-android-kotlin-compose-training-dessert-clicker.

3. Khám phá các phương thức của vòng đời và thêm tính năng ghi nhật ký cơ bản

Mọi hoạt động đều có vòng đời hoạt động. Thuật ngữ này ám chỉ vòng đời của thực vật và động vật, như vòng đời của bươm bướm — các trạng thái cho thấy sự phát triển của bươm bướm từ khi còn là trứng đến khi thành sâu bướm, rồi thành nhộng và bướm cho đến khi chết.

Vòng đời của bươm bướm — sự phát triển từ trứng thành sâu bướm, rồi thành nhộng và bướm cho đến khi chết.

Tương tự như vậy, vòng đời hoạt động bao gồm các trạng thái mà một hoạt động có thể trải qua, từ lúc được khởi tạo lần đầu cho đến khi bị huỷ bỏ. Tại thời điểm đó, hệ điều hành (OS) sẽ lấy lại bộ nhớ của hoạt động đó. Thông thường, điểm truy cập của chương trình là phương thức main(). Tuy nhiên, hoạt động trên Android bắt đầu bằng phương thức onCreate(). Phương thức này tương đương với giai đoạn từ khi còn là trứng trong ví dụ trên. Bạn đã sử dụng các hoạt động nhiều lần trong suốt khoá học này nên có thể bạn sẽ nhận ra phương thức onCreate(). Khi người dùng khởi động ứng dụng, di chuyển giữa các hoạt động, di chuyển trong và ngoài ứng dụng, thì trạng thái của hoạt động sẽ thay đổi.

Sơ đồ dưới đây cho thấy tất cả các trạng thái của vòng đời hoạt động. Đúng như tên gọi cho thấy, tên của các trạng thái này cho biết trạng thái của hoạt động. Lưu ý: khác với vòng đời của bươm bướm, hoạt động có thể chuyển đổi qua lại giữa các trạng thái trong suốt vòng đời thay vì chỉ di chuyển theo một hướng.

ca808edb1c95f07a.png

Thường thì bạn muốn thay đổi một số hành vi hoặc chạy một mã cụ thể khi trạng thái của vòng đời hoạt động thay đổi. Do đó, chính lớp Activity và bất kỳ lớp con nào của Activity, chẳng hạn như ComponentActivity, sẽ triển khai một tập hợp các phương thức gọi lại trong vòng đời. Android gọi đến những lệnh gọi lại này khi hoạt động chuyển từ trạng thái này sang trạng thái khác và bạn có thể ghi đè các phương thức đó vào hoạt động của riêng mình để thực hiện các nhiệm vụ ứng với những thay đổi đó về trạng thái vòng đời. Sơ đồ dưới đây cho thấy các trạng thái vòng đời kèm theo lệnh gọi lại có thể ghi đè hiện có.

Lược đồ Vòng đời hoạt động

Điều quan trọng là phải biết khi nào Android gọi các lệnh gọi lại có thể ghi đè này và cần làm gì trong mỗi phương thức gọi lại, nhưng cả hai sơ đồ này đều phức tạp và có thể gây nhầm lẫn. Trong lớp học lập trình này, thay vì chỉ đọc ý nghĩa của từng trạng thái và lệnh gọi lại, bạn sẽ thực hiện một số thao tác để tìm hiểu và xây dựng kiến thức về vòng đời hoạt động của Android.

Bước 1: Kiểm tra phương thức onCreate() và thêm tính năng ghi nhật ký

Để tìm hiểu những gì đang xảy ra với vòng đời Android, việc nắm được thời điểm các phương thức vòng đời được gọi sẽ rất hữu ích. Thông tin này giúp bạn xác định nơi diễn ra sự cố trong ứng dụng Dessert Clicker.

Cách đơn giản để xác định thông tin này là sử dụng chức năng ghi nhật ký của Android. Tính năng ghi nhật ký cho phép bạn ghi thông báo ngắn vào bảng điều khiển trong khi ứng dụng chạy, đồng thời sử dụng bảng điều khiển đó để xem thời điểm các lệnh gọi lại được kích hoạt.

  1. Chạy ứng dụng Dessert Clicker và nhấn vài lần vào ảnh món tráng miệng. Lưu ý giá trị của Món tráng miệng đã bán và tổng số tiền thay đổi.
  2. Mở MainActivity.kt và kiểm tra phương thức onCreate() của hoạt động này:
override fun onCreate(savedInstanceState: Bundle?) {
    // ...
}

Trong sơ đồ vòng đời hoạt động, có thể bạn sẽ nhận ra phương thức onCreate() do đã từng sử dụng lệnh gọi lại này. Đây là phương thức mà mọi hoạt động đều phải triển khai. Phương thức onCreate() là nơi bạn nên thực hiện mọi thao tác khởi động một lần cho hoạt động của mình. Ví dụ: trong onCreate(), bạn gọi setContent(), trong đó chỉ định bố cục giao diện người dùng của hoạt động.

Phương thức vòng đời onCreate

Phương thức vòng đời onCreate() được gọi một lần, ngay sau khi khởi động hoạt động (khi hệ điều hành tạo đối tượng Activity mới trong bộ nhớ). Sau khi onCreate() thực thi, hoạt động sẽ được coi là đã được tạo.

  1. Thêm hằng số sau vào cấp cao nhất của MainActivity.kt, phía trên phần khai báo lớp class MainActivity.

Có một thông lệ tốt là khai báo hằng số TAG trong tệp vì giá trị của hằng số đó sẽ không thay đổi.

Để đánh dấu hằng số này là hằng số thời gian biên dịch, hãy sử dụng const khi khai báo biến. Hằng số thời gian biên dịch là một giá trị được xác định trong quá trình biên dịch.

private const val TAG = "MainActivity"
  1. Trong phương thức onCreate(), ngay sau lệnh gọi đến super.onCreate(), hãy thêm dòng sau:
Log.d(TAG, "onCreate Called")
  1. Nhập lớp Log nếu cần (nhấn Alt+Enter hoặc Option+Enter trên máy Mac và chọn Import (Nhập)). Nếu bạn đã bật tính năng nhập tự động, thì quá trình này sẽ diễn ra một cách tự động.
import android.util.Log

Lớp Log ghi thông báo vào Logcat. Logcat là bảng điều khiển dành cho thông báo nhật ký. Các thông báo của Android về ứng dụng của bạn sẽ xuất hiện tại đây, bao gồm cả các thông báo bạn công khai gửi tới nhật ký bằng phương thức Log.d() hoặc các phương thức lớp Log khác.

Có ba khía cạnh quan trọng của hướng dẫn Log:

  • Mức độ ưu tiên của thông báo nhật ký chính là mức độ quan trọng của thông báo. Trong trường hợp này, Log.v() sẽ ghi lại các thông báo chi tiết. Phương thức Log.d() sẽ viết một thông báo gỡ lỗi. Các phương thức khác trong lớp Log bao gồm Log.i() để thông báo thông tin, Log.w() để cảnh báo và Log.e() để thông báo lỗi.
  • Nhật ký tag (tham số đầu tiên), trong trường hợp này là "MainActivity". Thẻ là một chuỗi cho phép bạn dễ dàng tìm thấy các thông báo nhật ký trong Logcat. Thẻ thường mang tên của lớp.
  • Thông điệp nhật ký thực tế có tên msg (tham số thứ hai) là một chuỗi ngắn, trong trường hợp này là "onCreate Called".

a4ff4aa74384ff6.png

  1. Biên dịch và chạy ứng dụng Dessert Clicker. Không có khác biệt nào về hành vi trong ứng dụng khi bạn nhấn vào món tráng miệng. Trong Android Studio, ở cuối màn hình, hãy nhấp vào thẻ Logcat.

ed03d4bb1f020995.png

  1. Trong cửa sổ Logcat, hãy nhập tag:MainActivity vào trường tìm kiếm.

961ea44c4b9ee3c.png

Logcat chứa nhiều thông báo, hầu hết đều không hữu ích với bạn. Bạn có thể lọc các mục logcat (entries Logcat) theo nhiều cách, nhưng tìm kiếm là cách dễ nhất. Do đã sử dụng MainActivity làm thẻ nhật ký trong mã nguồn nên bạn có thể dùng thẻ đó để lọc nhật ký. Thông điệp nhật ký bao gồm cả ngày và giờ, thẻ nhật ký, tên gói (com.example.dessertclicker) và thông báo thực tế. Vì thông báo này xuất hiện trong nhật ký nên bạn biết rằng onCreate() đã được thực thi.

Bước 2: Triển khai phương thức onStart()

Phương thức vòng đời onStart() được gọi ngay sau onCreate(). Sau khi onStart() chạy, hoạt động của bạn sẽ hiển thị trên màn hình. Không giống như onCreate() chỉ được gọi một lần để khởi tạo hoạt động của bạn, hệ thống có thể gọi onStart() nhiều lần trong vòng đời của hoạt động.

a357d2291de472d9.png

Lưu ý là onStart() được ghép nối với phương thức vòng đời onStop() tương ứng. Nếu người dùng khởi động ứng dụng của bạn rồi quay lại màn hình chính của thiết bị, thì hoạt động sẽ dừng lại và không còn xuất hiện trên màn hình.

  1. Trong Android Studio, với MainActivity.kt mở và con trỏ bên trong lớp MainActivity, hãy chọn Code (Mã)> Override Methods...(Phương thức ghi đè) hoặc nhấn vào Control+O. Một hộp thoại sẽ xuất hiện với một danh sách dài tất cả phương thức bạn có thể ghi đè trong lớp này.

11ff93bee1c3940f.png

  1. Bắt đầu nhập onStart để tìm phương thức chính xác. Để cuộn đến mục trùng khớp tiếp theo, sử dụng mũi tên xuống. Chọn onStart() trong danh sách và nhấp vào OK để chèn mã ghi đè nguyên mẫu. Mã sẽ có dạng như trong ví dụ sau:
override fun onStart() {
    super.onStart()
}
  1. Bên trong phương thức onStart(), hãy thêm một thông báo nhật ký:
override fun onStart() {
    super.onStart()
    Log.d(TAG, "onStart Called")
}
  1. Biên dịch và chạy ứng dụng Dessert Clicker, sau đó mở ngăn Logcat.
  2. Nhập tag:MainActivity vào trường tìm kiếm để lọc nhật ký. Hãy lưu ý rằng cả hai phương thức onCreate()onStart() đều lần lượt được gọi và hoạt động sẽ hiện trên màn hình.
  3. Nhấn vào nút Home (Màn hình chính) trên thiết bị rồi dùng màn hình Recents (Gần đây) để quay lại hoạt động. Xin lưu ý rằng hoạt động này sẽ tiếp tục từ điểm dừng trước đó, với cùng các giá trị và onStart() được ghi lại lần thứ hai vào Logcat. Ngoài ra, cũng xin lưu ý là phương thức onCreate() sẽ không được gọi lại.
2024-02-20 10:30:00.231  5684-5684  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:30:00.278  5684-5684  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:30:39.020  5684-5684  MainActivity            com.example.dessertclicker           D  onStart Called

Bước 3: Thêm câu lệnh nhật ký khác

Tại bước này, bạn triển khai tính năng ghi nhật ký cho tất cả phương thức vòng đời khác.

  1. Ghi đè phần còn lại của các phương thức vòng đời trong MainActivity và thêm câu lệnh nhật ký cho từng phương thức, như trong mã sau:
override fun onResume() {
    super.onResume()
    Log.d(TAG, "onResume Called")
}

override fun onRestart() {
    super.onRestart()
    Log.d(TAG, "onRestart Called")
}

override fun onPause() {
    super.onPause()
    Log.d(TAG, "onPause Called")
}

override fun onStop() {
    super.onStop()
    Log.d(TAG, "onStop Called")
}

override fun onDestroy() {
    super.onDestroy()
    Log.d(TAG, "onDestroy Called")
}
  1. Biên dịch và chạy lại Dessert Clicker rồi kiểm tra Logcat.

Hãy lưu ý rằng lần này, ngoài onCreate()onStart(), còn có thông điệp nhật ký cho phương thức gọi lại trong vòng đời onResume().

2024-02-20 10:33:36.033  5789-5789  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:33:36.073  5789-5789  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:33:36.075  5789-5789  MainActivity            com.example.dessertclicker           D  onResume Called

Khi một hoạt động bắt đầu từ đầu, bạn sẽ thấy cả ba lệnh gọi lại trong vòng đời này đều được gọi theo thứ tự:

  • onCreate() khi hệ thống tạo ứng dụng.
  • onStart() làm cho ứng dụng hiện trên màn hình nhưng người dùng chưa thể tương tác.
  • onResume() đưa ứng dụng lên nền trước và người dùng hiện có thể tương tác với ứng dụng.

Tuy có tên như vậy nhưng phương thức onResume() vẫn được gọi khi khởi động, ngay cả khi không có gì để tiếp tục.

Lược đồ Vòng đời hoạt động

4. Khám phá các trường hợp sử dụng vòng đời

Sau khi thiết lập ứng dụng Dessert Clicker để ghi nhật ký, bạn hiện đã sẵn sàng dùng ứng dụng và khám phá cách kích hoạt phương thức gọi lại trong vòng đời.

Trường hợp sử dụng 1: Mở và đóng hoạt động

Bạn bắt đầu với trường hợp sử dụng cơ bản nhất, đó là khởi động ứng dụng lần đầu tiên rồi đóng ứng dụng.

  1. Biên dịch và chạy ứng dụng Dessert Clicker nếu bạn vẫn chưa chạy ứng dụng này. Như đã thấy, phương thức gọi lại onCreate(), onStart()onResume() được gọi khi hoạt động khởi động lần đầu tiên.
2024-02-20 10:33:36.033  5789-5789  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:33:36.073  5789-5789  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:33:36.075  5789-5789  MainActivity            com.example.dessertclicker           D  onResume Called
  1. Nhấn vào bánh nướng một vài lần.
  2. Nhấn vào nút Back (Quay lại) trên thiết bị.

Bạn có thể nhận thấy trong Logcat rằng onPause()onStop() được gọi theo thứ tự đó.

2024-02-20 10:34:41.974  5789-5789  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:34:42.411  5789-5789  MainActivity            com.example.dessertclicker           D  onStop Called

Trong trường hợp này, việc sử dụng nút Back (Quay lại) sẽ khiến hoạt động (và ứng dụng) bị xoá khỏi màn hình và được chuyển về phía sau ngăn xếp hoạt động.

Hệ điều hành Android có thể đóng hoạt động nếu mã gọi phương thức finish() của hoạt động theo cách thủ công, hoặc nếu người dùng buộc thoát khỏi ứng dụng. Ví dụ: người dùng có thể buộc thoát khỏi ứng dụng hoặc đóng ứng dụng trong màn hình Recents (Gần đây). Hệ điều hành cũng có thể tự tắt hoạt động nếu ứng dụng không xuất hiện trên màn hình sau một lúc lâu. Android làm vậy để duy trì thời lượng pin và thu hồi tài nguyên mà ứng dụng đang dùng để các ứng dụng khác có thể sử dụng các tài nguyên đó. Đây chỉ là một vài ví dụ về lý do hệ thống Android huỷ bỏ hoạt động của bạn. Có những trường hợp khác khi hệ thống Android huỷ bỏ hoạt động mà không đưa ra cảnh báo.

Trường hợp sử dụng 2: Rời khỏi và quay lại hoạt động

Bây giờ, khi đã khởi động và đóng ứng dụng, bạn đã thấy hầu hết các trạng thái của vòng đời khi hoạt động được tạo lần đầu tiên. Bạn cũng đã thấy hầu hết các trạng thái của vòng đời mà hoạt động trải qua khi bị đóng. Tuy nhiên, khi người dùng tương tác với thiết bị Android, họ chuyển đổi qua lại giữa các ứng dụng, quay về trang chủ, khởi động ứng dụng mới và xử lý những gián đoạn đến từ các hoạt động khác, như cuộc gọi điện thoại.

Hoạt động không bị đóng lại hoàn toàn mỗi khi người dùng rời khỏi hoạt động đó:

  • Khi hoạt động không còn hiện trên màn hình, thì nghĩa là hoạt động chuyển sang trạng thái chạy ở chế độ nền. Ngược lại với điều này là khi hoạt động diễn ra trong nền trước (foreground) hoặc trên màn hình.
  • Khi người dùng quay lại ứng dụng, hoạt động đó sẽ khởi động lại và xuất hiện trở lại. Phần này của vòng đời được gọi là vòng đời hiển thị của ứng dụng.

Khi chạy trong chế độ nền, ứng dụng thường sẽ không chủ động chạy để tiết kiệm tài nguyên hệ thống và thời lượng pin. Bạn sẽ sử dụng vòng đời Activity và lệnh gọi lại để biết được thời điểm ứng dụng chuyển sang chạy ở chế độ nền, để có thể tạm dừng mọi hoạt động đang diễn ra. Sau đó, khởi động lại các thao tác khi ứng dụng của bạn xuất hiện trên nền trước.

Tại bước này, bạn quan sát vòng đời hoạt động khi ứng dụng chuyển sang chạy ở chế độ nền và quay lại nền trước một lần nữa.

  1. Khi ứng dụng Dessert Clicker đang chạy, hãy nhấp vào cupcake một vài lần.
  2. Nhấn nút Home (Màn hình chính) trên thiết bị của bạn và quan sát Logcat trong Android Studio. Thao tác quay lại màn hình chính sẽ chuyển ứng dụng sang chế độ nền thay vì tắt hoàn toàn ứng dụng. Lưu ý là phương thức onPause()onStop() được gọi.
2024-02-20 10:35:26.832  5789-5789  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:35:27.233  5789-5789  MainActivity            com.example.dessertclicker           D  onStop Called

Khi onPause() được gọi, ứng dụng sẽ không còn ở trong tâm điểm nữa. Sau onStop(), ứng dụng không còn hiển thị trên màn hình nữa. Dù hoạt động đã dừng, nhưng đối tượng Activity vẫn còn trong bộ nhớ ở chế độ nền. Hệ điều hành Android chưa huỷ hoạt động đó. Người dùng có thể quay lại ứng dụng, vì vậy, Android vẫn duy trì các tài nguyên hoạt động của bạn.

c470ee28ab7f8a1a.png

  1. Dùng màn hình Recents (Gần đây) để quay lại ứng dụng. Trên trình mô phỏng, bạn có thể truy cập vào màn hình Recents (Gần đây) bằng nút hệ thống hình vuông như trong hình dưới đây.

Lưu ý trong Logcat là hoạt động khởi động lại với onRestart()onStart(), sau đó tiếp tục với onResume().

bc156252d977e5ae.png

2024-02-20 10:36:05.837  5789-5789  MainActivity            com.example.dessertclicker           D  onRestart Called
2024-02-20 10:36:05.839  5789-5789  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:36:05.842  5789-5789  MainActivity            com.example.dessertclicker           D  onResume Called

Khi hoạt động quay lại nền trước, phương thức onCreate() không được gọi lại. Đối tượng hoạt động không bị huỷ, do đó không cần được tạo lại. Thay vì onCreate(), phương thức onRestart() sẽ được gọi. Lần này, hãy lưu ý rằng khi hoạt động quay lại nền trước, số lượng Desserts sold (Món tráng miệng đã bán) sẽ được giữ lại.

  1. Khởi động ít nhất một ứng dụng khác không phải là Dessert Clicker để thiết bị có một vài ứng dụng trong màn hình Gần đây.
  2. Hiển thị màn hình Gần đây và mở một hoạt động khác gần đây. Sau đó, quay lại các ứng dụng gần đây và đưa Dessert Clicker trở lại nền trước.

Xin lưu ý rằng bạn cũng thấy các lệnh gọi lại này trong Logcat tại đây khi nhấn vào nút Home (Màn hình chính). onPause()onStop() được gọi khi ứng dụng chuyển sang chạy ở chế độ nền, sau đó onRestart(), onStart()onResume() được gọi khi ứng dụng quay lại nền trước.

Các phương thức này được gọi khi ứng dụng dừng và chuyển sang chạy ở chế độ nền, hoặc khi ứng dụng khởi động lại và quay lại nền trước. Nếu bạn cần thực hiện một số thao tác trong ứng dụng trong những trường hợp này, hãy ghi đè phương thức gọi lại liên quan trong vòng đời.

Trường hợp sử dụng 3: Ẩn một phần hoạt động

Bạn đã nhận thấy rằng khi một ứng dụng khởi động và onStart() được gọi, ứng dụng đó sẽ xuất hiện trên màn hình. Khi onResume() được gọi, ứng dụng sẽ thu hút tâm điểm chú ý của người dùng, nghĩa là người dùng có thể tương tác với ứng dụng. Khoảng thời gian thuộc vòng đời mà khi đó ứng dụng xuất hiện trên toàn màn hình và thu hút tâm điểm chú ý của người dùng, được gọi là thời gian tồn tại ở nền trước.

Khi ứng dụng chuyển sang chạy trong chế độ nền, tâm điểm sẽ mất đi sau onPause() và ứng dụng không còn xuất hiện sau onStop() nữa.

Sự khác biệt giữa tâm điểm và chế độ hiển thị rất quan trọng. Một hoạt động có thể hiển thị một phần trên màn hình nhưng không có tiêu điểm của người dùng (không được người dùng chú ý). Ở bước này, bạn xem xét trường hợp trong đó một hoạt động được hiển thị một phần nhưng không được người dùng chú ý.

  1. Khi ứng dụng Dessert Clicker đang chạy, hãy nhấp vào nút Share (Chia sẻ) ở góc trên bên phải màn hình.

Hoạt động chia sẻ xuất hiện ở nửa dưới của màn hình nhưng hoạt động vẫn hiển thị ở nửa trên.

677c190d94e57447.pngca6285cbbe3801cf.png

  1. Kiểm tra Logcat và lưu ý rằng chỉ onPause() mới được gọi.
2024-02-20 10:36:42.886  5789-5789  MainActivity            com.example.dessertclicker           D  onPause Called

Trong trường hợp sử dụng này, onStop() không được gọi vì hoạt động vẫn chỉ được hiển thị một phần. Tuy nhiên, hoạt động không được người dùng chú ý và người dùng không thể tương tác với hoạt động đó. Hoạt động "chia sẻ" ở nền trước (foreground) có được sự chú ý của người dùng.

Tại sao sự khác biệt này lại quan trọng? Quá trình gián đoạn với riêng onPause() thường kéo dài trong một thời gian ngắn trước khi quay lại hoạt động của bạn hoặc chuyển đến hoạt động hay ứng dụng khác. Thông thường, bạn muốn tiếp tục cập nhật giao diện người dùng để phần còn lại của ứng dụng không tỏ ra bị treo.

Dù mã nào chạy trong onPause() thì đều chặn nội dung khác hiển thị, vì vậy, hãy bảo đảm mã trong onPause() có kích thước nhỏ. Ví dụ: nếu có cuộc gọi điện thoại đến, mã trong onPause() có thể trì hoãn thông báo cuộc gọi đến.

  1. Nhấp vào bên ngoài hộp thoại chia sẻ để quay lại ứng dụng và để ý rằng onResume() được gọi.

Cả onResume()onPause() đều liên quan đến tâm điểm. Phương thức onResume() được gọi khi hoạt động nhận được tiêu điểm và onPause() được gọi khi hoạt động mất đi tiêu điểm.

5. Khám phá thay đổi về cấu hình

Có một trường hợp khác trong việc quản lý vòng đời hoạt động 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.

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 xử lý thay đổi là hoàn toàn tắt đi và tạo 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 và độ dài chuỗi. 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) thì bố cục có thể cần thay đổi để phù hợp với hướng mới. Hãy cùng xem cách ứng dụng hoạt động trong tình huống này.

Lệnh gọi lại gần nhất trong vòng đời để minh hoạ là onDestroy(), được gọi sau onStop(). Lệnh này được gọi ngay trước khi hoạt động bị huỷ. Điều này có thể xảy ra khi mã của ứng dụng gọi finish(), hoặc khi hệ thống cần huỷ và tạo lại hoạt động do thay đổi cấu hình.

Việc thay đổi cấu hình khiến onDestroy() được gọi

Xoay màn hình là một dạng thay đổi cấu hình khiến hoạt động bị tắt và khởi động lại. Để mô phỏng và kiểm tra mức độ ảnh hưởng của sự thay đổi về cấu hình này, hãy hoàn thành các bước sau:

  1. Biên dịch và chạy ứng dụng của bạn.
  2. Đảm bảo bạn đã tắt tuỳ chọn khoá xoay màn hình trong trình mô phỏng.
  3. 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.
  4. Kiểm tra Logcat và hiểu rằng khi hoạt động tắt, Logcat sẽ gọi onPause(), onStop()onDestroy() theo thứ tự đó.
2024-02-20 10:37:57.078  5987-5987  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:37:57.087  5987-5987  MainActivity            com.example.dessertclicker           D  onStop Called
2024-02-20 10:37:57.102  5987-5987  MainActivity            com.example.dessertclicker           D  onDestroy Called

Mất dữ liệu khi xoay thiết bị

  1. Biên dịch và chạy ứng dụng của bạn, sau đó mở Logcat.
  2. Nhấp vào cupcake vài lần và lưu ý rằng các món tráng miệng đã bán và tổng doanh thu là một số khác 0.
  3. Đảm bảo bạn đã tắt tuỳ chọn khoá xoay màn hình trong trình mô phỏng.
  4. 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.

f745d0e2697415fd.png

  1. Kiểm tra kết quả đầu ra trong Logcat. Lọc kết quả đầu ra trên MainActivity.
2024-02-20 10:39:22.724  6087-6087  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:39:22.752  6087-6087  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:39:22.753  6087-6087  MainActivity            com.example.dessertclicker           D  onResume Called
2024-02-20 10:39:40.508  6087-6087  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:39:40.540  6087-6087  MainActivity            com.example.dessertclicker           D  onStop Called
2024-02-20 10:39:40.549  6087-6087  MainActivity            com.example.dessertclicker           D  onDestroy Called
2024-02-20 10:39:40.582  6087-6087  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:39:40.584  6087-6087  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:39:40.585  6087-6087  MainActivity            com.example.dessertclicker           D  onResume Called

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.

Khi xoay thiết bị, hoạt động bị tắt và được tạo lại, hoạt động khởi động lại với các giá trị mặc định, hình ảnh món tráng miệng, số lượng món tráng miệng đã bán và tổng doanh thu được đặt lại về 0.

Để tìm hiểu lý do khiến các giá trị được đặt lại và cách chỉnh sửa, bạn cần tìm hiểu về vòng đời của một thành phần kết hợp, cũng như cách thành phần này quan sát và bảo tồn trạng thái.

Vòng đời của một thành phần kết hợp

Giao diện người dùng của ứng dụng ban đầu được xây dựng từ việc chạy các hàm có khả năng kết hợp trong một quy trình có tên là Cấu trúc (thành phần Compose).

Khi trạng thái của ứng dụng thay đổi, quá trình kết hợp lại sẽ được lên lịch. Quá trình kết hợp lại xảy ra khi Compose thực thi lại các hàm có khả năng kết hợp mà có thể đã thay đổi trạng thái, đồng thời tạo giao diện người dùng được cập nhật. Cấu trúc (Composition) được cập nhật để phản ánh những thay đổi này.

Cách duy nhất để tạo hoặc cập nhật một Thành phần (Composition) là sử dụng thành phần kết hợp ban đầu cùng với các thành phần kết hợp lại sau đó.

Các hàm có khả năng kết hợp có vòng đời riêng độc lập với Vòng đời hoạt động. Vòng đời của các hàm này bao gồm các sự kiện: nhập vào Thành phần, kết hợp lại từ 0 lần trở lên, sau đó rời khỏi Thành phần.

Để Compose có thể theo dõi và kích hoạt quá trình kết hợp lại, Compose cần biết thời điểm trạng thái thay đổi. Để cho Compose biết rằng cần phải theo dõi trạng thái của một đối tượng, thì đối tượng đó phải thuộc loại State hoặc MutableState. State là loại không thể thay đổi và chỉ có thể đọc được. MutableState là loại có thể thay đổi đồng thời cho phép đọc và ghi.

Bạn đã xem và sử dụng MutableState trong ứng dụng Lemonadeứng dụng Tip Time ở các lớp học lập trình trước đó.

Để tạo biến có thể thay đổi revenue, bạn khai báo biến này bằng cách sử dụng mutableStateOf. 0 là giá trị mặc định ban đầu của biến.

var revenue = mutableStateOf(0)

Tuy thao tác này có thể giúp Compose kích hoạt quá trình kết hợp lại khi giá trị doanh thu thay đổi, nhưng vẫn chưa đủ khả năng để giữ lại giá trị cập nhật. Do đó, mỗi lần thực thi lại, thành phần kết hợp sẽ khởi tạo lại giá trị doanh thu về giá trị mặc định ban đầu là 0.

Để hướng dẫn Compose giữ lại và tái sử dụng giá trị trong quá trình kết hợp lại, bạn cần khai báo giá trị này bằng remember API.

var revenue by remember { mutableStateOf(0) }

Nếu giá trị của revenue thay đổi, thì Compose sẽ lên lịch cho tất cả hàm có khả năng kết hợp đọc giá trị này để kết hợp lại.

Mặc dù Compose ghi nhớ trạng thái doanh thu trong quá trình kết hợp lại nhưng nó sẽ không giữ lại trạng thái này trong quá trình thay đổi cấu hình. Để Compose giữ lại trạng thái trong quá trình thay đổi cấu hình, bạn phải sử dụng rememberSaveable.

Để biết thêm thông tin và thực hành, vui lòng tham khảo lớp học lập trình Giới thiệu về trạng thái trong Compose.

Sử dụng rememberSaveable để lưu giá trị qua các lần thay đổi cấu hình

Bạn sẽ sử dụng hàm rememberSaveable để lưu các giá trị mình cần nếu hệ điều hành Android huỷ bỏ và tạo lại hoạt động.

Để lưu giá trị trong quá trình kết hợp lại, bạn cần sử dụng remember. Dùng rememberSaveable để lưu giá trị trong quá trình kết hợp lại VÀ thay đổi cấu hình.

Việc lưu giá trị bằng cách sử dụng rememberSaveable bảo đảm rằng giá trị này luôn có sẵn ngay khi hoạt động được khôi phục.

  1. Trong MainActivity, hãy cập nhật nhóm 5 biến hiện đang sử dụng remember thành rememberSaveable.
var revenue by remember { mutableStateOf(0) }
...
var currentDessertImageId by remember {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}
var revenue by rememberSaveable { mutableStateOf(0) }
...
var currentDessertImageId by rememberSaveable {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}
  1. Biên dịch và chạy ứng dụng của bạn.
  2. Nhấp vào cupcake vài lần và lưu ý rằng các món tráng miệng đã bán và tổng doanh thu là một số khác 0.
  3. Xoay thiết bị hoặc trình mô phỏng sang chế độ ngang.
  4. Quan sát thấy sau khi hoạt động bị huỷ bỏ và được tạo lại, hình ảnh món tráng miệng, món tráng miệng đã bán và tổng doanh thu được khôi phục về giá trị trước đó.

6. Mã giải pháp

7. Tóm tắt

Vòng đời hoạt động

  • Vòng đời hoạt động là một tập hợp các trạng thái mà qua đó một hoạt động sẽ chuyển đổi. Vòng đời hoạt động bắt đầu khi hệ điều hành Android tạo hoạt động lần đầu tiên và kết thúc khi hệ điều hành huỷ hoạt động.
  • Khi người dùng di chuyển giữa các hoạt động, bên trong và bên ngoài ứng dụng, mỗi hoạt động sẽ di chuyển giữa các trạng thái trong vòng đời hoạt động.
  • Mỗi trạng thái trong vòng đời hoạt động đều có một phương thức gọi lại tương ứng mà bạn có thể ghi đè trong lớp Activity. Tập hợp cốt lõi các phương thức vòng đời bao gồm: onCreate(), onRestart(), onStart(), onResume(), onPause(), onStop(), onDestroy().
  • Để thêm hành vi xảy ra khi hoạt động chuyển đổi sang trạng thái vòng đời, hãy ghi đè phương thức gọi lại của trạng thái.
  • Để thêm phương thức ghi đè skeleton vào các lớp trong Android Studio, hãy chọn Code (Mã) > Override Methods (Phương thức ghi đè) hoặc nhấn vào Control+O.

Ghi nhật ký bằng Nhật ký

  • API ghi nhật ký Android, đặc biệt là lớp Log, cho phép bạn viết các thông báo ngắn được hiển thị trong Logcat bên trong Android Studio.
  • Sử dụng Log.d() để viết thông báo gỡ lỗi. Phương thức này sẽ nhận hai đối số: thẻ nhật ký, thường là tên lớp và nhật ký thông báo, thường là một chuỗi ngắn.
  • Sử dụng cửa sổ Logcat trong Android Studio để xem nhật ký hệ thống, bao gồm cả các thông báo bạn viết.

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à huỷ 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 ngôn ngữ của thiết bị thay đổi hoặc người dùng 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ả lệnh gọi lại tắt (shutdown callback) của vòng đời hoạt động. Sau đó, Android sẽ 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 onCreate().
  • Để lưu một giá trị cần tồn tại sau khi thay đổi cấu hình, hãy khai báo các biến của giá trị bằng rememberSaveable.

Tìm hiểu thêm