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

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

  • Kích thước trên màn hình của ứ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 Activity.

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

Hệ thống sẽ tạo lại một Activity khi cấu hình thay đổi. Để thực hiện việc này, hệ thống sẽ gọi onDestroy() và huỷ bỏ thực thể Activity hiện có. Sau đó, hệ thống sẽ tạo một thực thể mới bằng cách sử dụng onCreate() và thực thể mới của Activity này sẽ được khởi động bằng cấu hình mới, 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 được giữ lại ở dạng trường trong Activity hoặc trong bất kỳ Fragment, View hoặc đối tượng nào khác có trong đó. Nguyên nhân là vì việc tạo lại Activity sẽ tạo ra một thực thể hoàn toàn mới của Activity và giao diện người dùng. Hơn nữa, Activity 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 mã đó hoặc các đối tượng có chứa mã đó đề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. Là nhà phát triển, bạn phải mang đến trải nghiệm nhất quán cho người dùng khi 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 thao tác 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 khi tạo lại Activity. Việc sử dụng cách tiếp cận nào tuỳ thuộc vào loại trạng thái mà bạn muốn duy trì:

  • 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ư các thực thể ViewModel để 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 hệ thống buộc tắt và giữ trạng thái tạm thời tuỳ thuộc vào hoạt động đầu vào hoặc thao tác của người dùng.

Để đọc 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ãy xem bài viết Lưu trạng thái giao diện người dùng.

Hạn chế việc 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 Activity 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ừ Activity. 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.

Để 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, hãy thêm loại cấu hình vào android:configChanges trong mục <activity> ở tệp AndroidManifest.xml của bạn. 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 Activity 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 động được ra mắt trong Android 12L (API cấp 32).

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 View, khi cấu hình thay đổi mà bạn đã tắt tính năng tạo lại Activity, 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 đố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 không:

Kotlin

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

    // Checks whether a 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 a 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 gắn 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 tránh được: các thay đổi về cấu hình mà bạn không ngăn chặn được 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 phải có khả năng xử lý trường hợp hệ thống buộc tắt. 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 bạn tắt tính năng tạo lại Activity cho mọi thay đổi về cấu hình, ứng dụng của bạn vẫn phải 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 cục bộ LocalConfiguration. 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 hoạt động của thành phần kết hợp cục bộ, hãy xem bài viết 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 Activity khi ngôn ngữ thay đổi, Activity 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 đặt android:configChanges thành locale|layoutDirection.

Các thay đổi về cấu hình: Các khái niệm chính và phương pháp hay nhất

Dưới đây là các khái niệm chính mà 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 xuất hiện với người dùng, chẳng hạn như kích thước trên màn hình của ứng dụng, ngôn ngữ hoặc giao diện hệ thống.
  • Các thay đổi về cấu hình: cấu hình thay đổi khi người dùng tương tác. 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ị. Không có cách nào để ngăn các thay đổi về cấu hình.
  • Tạo lại Activity: theo mặc định, các thay đổi về cấu hình sẽ dẫn đến việc tạo lại Activity. Đâ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ỏ Activity: việc tạo lại Activity sẽ khiến hệ thống huỷ bỏ thực thể cũ của Activity 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 thực thể đó 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 Activity không có trong thực thể mới của Activity, 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 ứng dụng của bạn cập nhật đúng cách theo cấu hình mới.

Để mang lại trải nghiệm tốt cho người dùng, hãy tuân thủ các phương pháp hay nhất sau:

  • Phải chuẩn bị cho các thay đổi thường xuyên về cấu hình: đừng giả định rằng các 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 hoặc 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 Activity. 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 Activity ở 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 bạn 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 Activity 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 Activity. 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 các 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 Activity. 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

Các thay đổi về cấu hình dựa trên kích thước có thể xảy ra bất cứ lúc nào và có nhiều khả năng là khi ứng dụng của bạn chạy trên một thiết bị màn hình lớn. Tại đây, 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à những tài nguyên từ bất kỳ thư viện nào của 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 Activity đố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 Activity. 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().

Tính năng tạo lại Activity bị tắt đối với các thay đổi về cấu hình dựa trên kích thước khi bạn có android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" trong tệp kê khai.

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 Android 7.0 (API cấp 24) trở lên, việc tạo lại Activity 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 sự thay đổi về kích thước là đáng kể. Khi không tạo lại Activity do không đủ kích thước, hệ thống có thể gọi Activity.onConfigurationChanged()View.onConfigurationChanged().

Có một số điểm cần lưu ý liên quan đến lệnh gọi lại ActivityView khi Activity không được tạo lại:

  • Trên Android 11 (API cấp 30) đến Android 13 (API cấp 33), Activity.onConfigurationChanged() không được gọi.
  • Có một sự cố đã biết, đó là View.onConfigurationChanged() có thể không được gọi trong một số trường hợp trên Android 12L (API cấp 32) và các phiên bản ban đầu của Android 13 (API cấp 33). Để biết thêm thông tin, hãy xem sự cố công khai này. Vấn đề này đã được giải quyết trong các bản phát hành Android 13 và Android 14 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 sử dụng View tiện ích có View.onConfigurationChanged() được ghi đè thay vì dựa vào việc tạo lại Activity hoặc Activity.onConfigurationChanged().