Xử lý thay đổi về cấu hình

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

Một số cấu hình thiết bị có thể thay đổi khi ứng dụng đang chạy. Những trường hợp như vậy bao gồm, nhưng không giới hạn ở:

  • Kích thước màn hình ứng dụng
  • Hướng màn hình
  • Cỡ chữ và độ đậm
  • Ngôn ngữ
  • Chế độ tối so với chế độ sáng
  • Khả năng sử dụng bàn phím

Hầu hết các thay đổi về cấu hình này xảy ra do một số tương tác của người dùng. Ví dụ: việc xoay hoặc gập thiết bị sẽ thay đổi không gian màn hình hiện có cho ứng dụng. Tương tự, việc thay đổi các chế độ cài đặt của thiết bị như cỡ chữ, ngôn ngữ hoặc giao diện ưu tiên sẽ thay đổi các giá trị tương ứng trong đối tượng Configuration.

Những tham số này thường yêu cầu các thay đổi đủ lớn đối với giao diện người dùng của ứng dụng để nền tảng Android có cơ chế riêng khi những tham số này thay đổi. Cơ chế này là Tạo lại hoạt động.

Tạo lại hoạt động

Hệ thống sẽ tạo lại một Hoạt động khi cấu hình thay đổi. Hệ thống sẽ gọi onDestroy() và huỷ bỏ thực thể hiện có của Hoạt động. Sau đó, hệ thống sẽ tạo một thực thể mới bằng onCreate(). Thực thể Hoạt động mới này khởi chạy bằng cấu hình mới, đã được cập nhật. Việc này cũng có nghĩa là hệ thống cũng tạo lại giao diện người dùng bằng cấu hình mới.

Hành vi tạo lại giúp ứng dụng của bạn thích ứng với các cấu hình mới bằng cách tự động tải lại ứng dụng có những tài nguyên thay thế khớp với cấu hình thiết bị mới.

Ví dụ về việc tạo lại

Hãy cân nhắc dùng TextView để hiển thị tiêu đề tĩnh bằng android:text="@string/title", như xác định trong tệp XML bố cục. Khi được tạo, khung hiển thị sẽ đặt văn bản chính xác một lần dựa trên ngôn ngữ hiện tại. Nếu ngôn ngữ thay đổi, hệ thống sẽ tạo lại hoạt động. Do đó, hệ thống cũng sẽ tạo lại khung hiển thị và khởi động khung hiển thị đó theo đúng giá trị dựa trên ngôn ngữ mới.

Việc tạo lại cũng sẽ xoá mọi trạng thái bạn đã giữ lại ở dạng trường trong Hoạt động hoặc trong bất kỳ Mảnh, Khung hiển thị và đối tượng nào khác có trong đó.. Nguyên nhân là vì việc tạo lại Hoạt động sẽ tạo ra một thực thể hoàn toàn mới của Hoạt động và giao diện người dùng. Hơn nữa, Hoạt động cũ không còn hiển thị hoặc hợp lệ nữa. Do đó, mọi dữ liệu tham chiếu còn lại đến Hoạt động đó hoặc các đối tượng có chứa hoạt động đó đều đã lỗi thời. Chúng có thể gây ra lỗi, rò rỉ bộ nhớ và sự cố.

Kỳ vọng của người dùng

Người dùng ứng dụng mong muốn trạng thái được giữ nguyên. Nếu người dùng điền vào biểu mẫu và mở một ứng dụng khác ở chế độ nhiều cửa sổ để tham chiếu thông tin, thì họ sẽ gặp trải nghiệm không tốt nếu quay lại một biểu mẫu bị xoá, hoặc chuyển đến nơi hoàn toàn khác trong ứng dụng. Bạn phải đảm bảo trải nghiệm nhất quán thông qua việc thay đổi cấu hình và tạo lại hoạt động.

Để xác minh xem trạng thái có được giữ nguyên trong ứng dụng hay không, bạn có thể thực hiện các thao tác khiến cấu hình thay đổi cả khi ứng dụng đang chạy ở nền trước lẫn khi chạy trong nền. Các hoạt động này bao gồm:

  • Xoay thiết bị
  • Chuyển sang chế độ nhiều cửa sổ
  • Đổi kích thước của ứng dụng khi ở chế độ nhiều cửa sổ hoặc cửa sổ tuỳ ý
  • Gập thiết bị có thể gập lại với nhiều màn hình
  • Thay đổi giao diện của hệ thống, chẳng hạn như chế độ tối so với chế độ sáng
  • Thay đổi cỡ chữ
  • Thay đổi ngôn ngữ hệ thống hoặc ngôn ngữ ứng dụng
  • Kết nối hoặc ngắt kết nối bàn phím phần cứng
  • Kết nối hoặc ngắt kết nối đế sạc

Có 3 cách tiếp cận chính mà bạn có thể thực hiện để duy trì trạng thái liên quan thông qua việc tạo lại hoạt động. Trạng thái có liên quan sẽ tuỳ thuộc vào loại trạng thái bạn muốn giữ nguyên:

  • Cố định cục bộ để xử lý trường hợp bị buộc tắt đối với dữ liệu lớn hoặc phức tạp. Bộ nhớ cục bộ ổn định bao gồm cơ sở dữ liệu hoặc DataStore.
  • Các đối tượng được giữ lại như ViewModels để xử lý trạng thái liên quan đến giao diện người dùng trong bộ nhớ khi người dùng đang dùng ứng dụng.
  • Trạng thái đã lưu của thực thể để xử lý trường hợp bị buộc tắt do hệ thống gây ra và giữ trạng thái tạm thời tuỳ thuộc vào hoạt động đầu vào hoặc hoạt động điều hướng của người dùng.

Trang Lưu trạng thái giao diện người dùng mô tả chi tiết về các API cho từng trạng thái trong số này và thời điểm phù hợp để dùng mỗi trạng thái đó.

Hạn chế tạo lại Hoạt động

Bạn có thể ngăn việc tự động tạo lại hoạt động đối với một số thay đổi về cấu hình. Việc tạo lại hoạt động sẽ tạo lại toàn bộ giao diện người dùng và mọi đối tượng bắt nguồn từ Hoạt động. Có thể bạn có lý do chính đáng để tránh làm như vậy. Ví dụ: ứng dụng của bạn có thể không cần cập nhật tài nguyên trong một thay đổi cụ thể về cấu hình, hoặc bạn có thể bị giới hạn về hiệu suất. Trong trường hợp đó, bạn có thể khai báo rằng hoạt động của bạn tự xử lý thay đổi về cấu hình và ngăn hệ thống khởi động lại hoạt động.

Bạn có thể tắt tính năng tạo lại hoạt động cho các thay đổi cụ thể về cấu hình. Để thực hiện việc này, hãy thêm loại cấu hình vào android:configChanges ở mục <activity> trong AndroidManifest.xml. Các giá trị có thể có xuất hiện trong tài liệu về thuộc tính android:configChanges.

Mã tệp kê khai sau đây sẽ tắt tính năng tạo lại Hoạt động cho MyActivity khi hướng màn hình và khả năng sử dụng bàn phím thay đổi:

<activity
    android:name=".MyActivity"
    android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
    android:label="@string/app_name">

Một số thay đổi về cấu hình luôn khiến hoạt động khởi động lại. Bạn không thể tắt chúng. Ví dụ: bạn không thể tắt tính năng thay đổi màu linh động có trong API 32. Các thay đổi khác về cấu hình có thể hoạt động tương tự trong tương lai.

Phản ứng với các thay đổi về cấu hình trong hệ thống Khung hiển thị

Trong hệ thống Khung hiển thị, khi cấu hình thay đổi mà bạn đã tắt tính năng tạo lại hoạt động, hoạt động đó sẽ nhận được một lệnh gọi đến Activity.onConfigurationChanged(). Bất kỳ khung hiển thị đính kèm nào cũng sẽ nhận được lệnh gọi đến View.onConfigurationChanged(). Đối với các thay đổi về cấu hình mà bạn chưa thêm vào android:configChanges, hệ thống sẽ tạo lại hoạt động như bình thường.

Phương thức gọi lại onConfigurationChanged() nhận được đối tượng Configuration chỉ định cấu hình mới cho thiết bị. Đọc các trường trong đối tượng Configuration để xác định cấu hình mới của bạn. Để thực hiện các thay đổi tiếp theo, hãy cập nhật tài nguyên bạn sử dụng trong giao diện. Khi hệ thống gọi phương thức này, đối tượng Resources của hoạt động sẽ được cập nhật để trả về tài nguyên dựa trên cấu hình mới. Việc này cho phép bạn đặt lại các phần tử trên giao diện người dùng mà không cần hệ thống khởi động lại hoạt động của bạn.

Ví dụ: quy trình triển khai onConfigurationChanged() sau đây sẽ kiểm tra xem có bàn phím nào không:

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show()
    } else if (newConfig.keyboardHidden === Configuration.KEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show()
    }
}

Java

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks whether any keyboard is available
    if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "Keyboard available", Toast.LENGTH_SHORT).show();
    } else if (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO){
        Toast.makeText(this, "No keyboard", Toast.LENGTH_SHORT).show();
    }
}

Nếu không cần cập nhật ứng dụng dựa trên những thay đổi về cấu hình này, bạn có thể không cần triển khai onConfigurationChanged(). Trong trường hợp đó, mọi tài nguyên được dùng trước khi thay đổi cấu hình vẫn được sử dụng và bạn chỉ tránh được việc khởi động lại hoạt động của mình. Ví dụ: một ứng dụng TV không nên phản ứng khi bàn phím Bluetooth được đính kèm hoặc tháo rời.

Giữ lại trạng thái

Khi sử dụng kỹ thuật này, bạn vẫn phải giữ nguyên trạng thái trong vòng đời hoạt động bình thường. Lý do là:

  • Các thay đổi không thể tránh: Các thay đổi về cấu hình mà bạn không thể ngăn chặn có thể khởi động lại ứng dụng của bạn.
  • Trường hợp bị buộc tắt: Ứng dụng của bạn có thể phải xử lý trường hợp bị buộc tắt do hệ thống gây ra. Nếu người dùng rời khỏi ứng dụng của bạn và ứng dụng chuyển sang chạy trong nền, thì hệ thống có thể huỷ bỏ ứng dụng đó.

Phản ứng với các thay đổi về cấu hình trong Jetpack Compose

Jetpack Compose giúp ứng dụng của bạn dễ dàng phản ứng với các thay đổi về cấu hình. Tuy nhiên, nếu tắt tính năng tạo lại Hoạt động cho mọi thay đổi về cấu hình, bạn vẫn phải đảm bảo rằng ứng dụng của mình xử lý đúng cách các thay đổi về cấu hình nếu có thể.

Đối tượng Configuration có trong hệ phân cấp giao diện người dùng Compose với Thành phần kết hợp LocalConfiguration cục bộ. Bất cứ khi nào giá trị này thay đổi, các hàm có khả năng kết hợp đọc từ LocalConfiguration.current sẽ kết hợp lại. Để biết thông tin về cách thức hoạt động của Thành phần kết hợp cục bộ, hãy tham khảo tài liệu về Dữ liệu trong phạm vi cục bộ với CompositionLocal.

Ví dụ

Trong ví dụ sau, một thành phần kết hợp hiển thị một ngày có định dạng cụ thể. Thành phần kết hợp phản ứng với các thay đổi về cấu hình ngôn ngữ hệ thống bằng cách gọi ConfigurationCompat.getLocales() thông qua LocalConfiguration.current.

@Composable
fun DateText(year: Int, dayOfYear: Int) {
    val dateTimeFormatter = DateTimeFormatter.ofPattern(
        "MMM dd",
        ConfigurationCompat.getLocales(LocalConfiguration.current)[0]
    )
    Text(
        dateTimeFormatter.format(LocalDate.ofYearDay(year, dayOfYear))
    )
}

Để tránh việc tạo lại Hoạt động khi ngôn ngữ thay đổi, Hoạt động lưu trữ mã Compose cần chọn không thay đổi cấu hình ngôn ngữ. Để làm như vậy, bạn nên đặt android:configChanges thành locale|layoutDirection.

Cấu hình thay đổi ý tưởng và khái niệm chính

Tóm lại, sau đây là những điều bạn cần biết khi xử lý các thay đổi về cấu hình:

  • Cấu hình: Cấu hình thiết bị xác định cách giao diện người dùng hiển thị cho người dùng, chẳng hạn như kích thước màn hình hiển thị của ứng dụng, ngôn ngữ hoặc giao diện hệ thống.
  • Thay đổi về cấu hình: Cấu hình thay đổi thông qua tương tác của người dùng. Ví dụ: người dùng có thể thay đổi chế độ cài đặt của thiết bị hoặc cách họ tương tác thực tế với thiết bị. Cấu hình sẽ thay đổi; không có cách nào để "ngăn chặn" những thay đổi về cấu hình.
  • Tạo lại hoạt động: Theo mặc định, các thay đổi về cấu hình sẽ dẫn đến việc tạo lại Hoạt động. Đây là cơ chế tích hợp sẵn để khởi động lại trạng thái ứng dụng cho cấu hình mới.
  • Huỷ bỏ hoạt động: Việc tạo lại hoạt động khiến hệ thống huỷ bỏ thực thể cũ của Hoạt động và tạo một thực thể mới tại vị trí đó. Thực thể cũ hiện đã lỗi thời; mọi tham chiếu còn lại đến tham số đó sẽ dẫn đến rò rỉ bộ nhớ, lỗi hoặc sự cố.
  • Trạng thái: Trạng thái trong thực thể cũ của Hoạt động không có trong thực thể mới của Hoạt động, vì đây là 2 thực thể khác nhau của đối tượng. Duy trì trạng thái của ứng dụng và người dùng như mô tả trong bài viết Lưu trạng thái giao diện người dùng.
  • Chọn không sử dụng: Việc chọn không tạo lại hoạt động cho một loại thay đổi về cấu hình là khả năng tối ưu hoá. Phương thức này yêu cầu bạn đảm bảo rằng ứng dụng của bạn cập nhật đúng cách theo cấu hình mới.

Các phương pháp hay nhất

Để mang lại trải nghiệm tốt cho người dùng, hãy tuân thủ những điều sau:

  • Thay đổi về cấu hình diễn ra thường xuyên: Đừng giả định rằng thay đổi về cấu hình là hiếm hoặc không bao giờ xảy ra, bất kể cấp độ API, hệ số hình dạng hay bộ công cụ giao diện người dùng. Khi người dùng khiến cấu hình thay đổi, họ mong muốn các ứng dụng cập nhật và tiếp tục hoạt động đúng cách với cấu hình mới này.
  • Duy trì trạng thái: Đừng làm mất trạng thái của người dùng khi tạo lại Hoạt động. Duy trì trạng thái như mô tả trong bài viết Lưu trạng thái giao diện người dùng.
  • Tránh chọn không sử dụng tính năng sửa nhanh: Đừng chọn không sử dụng tính năng tạo lại Hoạt động ở dạng lối tắt để tránh bị mất trạng thái. Việc chọn không tạo lại hoạt động sẽ yêu cầu thực hiện cam kết xử lý thay đổi, và bạn vẫn có thể mất trạng thái do tạo lại Hoạt động từ các thay đổi khác về cấu hình, trường hợp bị buộc tắt hoặc đóng ứng dụng. Bạn không thể tắt hoàn toàn tính năng tạo lại Hoạt động. Duy trì trạng thái như mô tả trong bài viết Lưu trạng thái giao diện người dùng.
  • Đừng tránh thay đổi về cấu hình: Đừng đặt các quy định hạn chế về hướng, tỷ lệ khung hình hoặc khả năng đổi kích thước để tránh các thay đổi về cấu hình và tạo lại Hoạt động. Việc này sẽ tác động tiêu cực đến người dùng muốn sử dụng ứng dụng của bạn theo ý họ.

Xử lý các thay đổi về cấu hình dựa trên kích thước

Bạn có thể thay đổi cấu hình dựa trên kích thước bất cứ lúc nào. Việc này xảy ra thường xuyên hơn khi ứng dụng chạy trên thiết bị có màn hình lớn. Trong đó, người dùng có thể chuyển sang chế độ nhiều cửa sổ. Họ muốn ứng dụng của bạn hoạt động tốt trong môi trường đó.

Có 2 loại thay đổi chung về kích thước: đáng kể và không đáng kể. Thay đổi kích thước "đáng kể" là thay đổi trong đó một nhóm tài nguyên thay thế khác áp dụng cho cấu hình mới do sự khác biệt về kích thước màn hình, chẳng hạn như chiều rộng, chiều cao hoặc chiều rộng nhỏ nhất. Các tài nguyên này bao gồm những tài nguyên mà ứng dụng tự xác định và mọi thư viện chứa tài nguyên đó.

Hạn chế việc tạo lại Hoạt động đối với các thay đổi về cấu hình dựa trên kích thước

Khi bạn tắt tính năng tạo lại Hoạt động đối với các thay đổi về cấu hình dựa trên kích thước, hệ thống sẽ không tạo lại Hoạt động. Thay vào đó, hệ thống sẽ nhận được một lệnh gọi đến Activity.onConfigurationChanged(). Bất kỳ khung hiển thị đính kèm nào cũng sẽ nhận được lệnh gọi đến View.onConfigurationChanged().

Cho phép việc tạo lại Hoạt động đối với các thay đổi về cấu hình dựa trên kích thước

Trên API 24 trở lên, tính năng tạo lại Hoạt động chỉ xảy ra đối với các thay đổi về cấu hình dựa trên kích thước nếu thay đổi kích thước đáng kể. Khi không tạo lại một Hoạt động do không đủ kích thước, hệ thống có thể gọi Activity.onConfigurationChanged()View.onConfigurationChanged().

Bạn nên tuân thủ một số cảnh báo về lệnh gọi lại Hoạt động và Khung hiển thị:

  • Trên API 30 trở lên, lệnh gọi lại Activity.onConfigurationChanged() sẽ không được gọi.
  • Trên API 32 và API 33, View.onConfigurationChanged() sẽ không được gọi. Đây là lỗi đã được khắc phục trong các phiên bản API sau này.

Đối với mã phụ thuộc vào việc theo dõi các thay đổi về cấu hình dựa trên kích thước, bạn nên dùng Khung hiển thị tiện ích có View.onConfigurationChanged() bị ghi đè thay vì dựa vào tính năng tạo lại Hoạt động hoặc Activity.onConfigurationChanged().