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

Một số cấu hình thiết bị có thể thay đổi trong thời gian chạy (chẳng hạn như hướng màn hình, khả năng sử dụng bàn phím và khi người dùng bật chế độ nhiều cửa sổ). Khi có thay đổi như vậy xảy ra, Android sẽ khởi động lại Activity đang chạy (onDestroy() được gọi, tiếp theo là onCreate()). Hành vi khởi động lại được thiết kế để 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 bằng các tài nguyên thay thế khớp với cấu hình thiết bị mới.

Để xử lý đúng cách việc khởi động lại, điều quan trọng là hoạt động của bạn phải khôi phục trạng thái trước đó. Bạn có thể sử dụng kết hợp các đối tượng onSaveInstanceState(), ViewModel và bộ nhớ liên tục để lưu và khôi phục trạng thái giao diện người dùng hoạt động của bạn qua các thay đổi về cấu hình. Để biết thêm thông tin về cách lưu trạng thái Hoạt động, hãy đọc bài viết Lưu trạng thái giao diện người dùng.

Để kiểm tra xem ứng dụng có tự khởi động lại với trạng thái ứng dụng nguyên vẹn hay không, bạn nên gọi các thay đổi về cấu hình (chẳng hạn như thay đổi hướng màn hình) trong khi thực hiện nhiều tác vụ trong ứng dụng. Ứng dụng phải có thể khởi động lại bất cứ lúc nào mà không bị mất dữ liệu hoặc trạng thái người dùng để xử lý các sự kiện như thay đổi cấu hình, hoặc khi người dùng nhận một cuộc gọi đến, sau đó quay lại ứng dụng của bạn sau khi quá trình đăng ký có thể đã bị huỷ bỏ. Để tìm hiểu cách khôi phục trạng thái hoạt động của bạn, hãy đọc bài viết về Vòng đời hoạt động.

Tuy nhiên, bạn có thể gặp phải một tình huống trong đó việc khởi động lại ứng dụng và khôi phục lượng dữ liệu đáng kể có thể gây tốn kém và tạo ra trải nghiệm không tốt cho người dùng. Trong tình huống như vậy, bạn sẽ có hai tuỳ chọn khác:

  1. Giữ lại một đối tượng trong quá trình thay đổi cấu hình

    Cho phép hoạt động của bạn khởi động lại khi cấu hình thay đổi, nhưng chuyển một đối tượng trạng thái vào thực thể mới của hoạt động.

  2. Tự xử lý thay đổi về cấu hình

    Bạn không nên tự xử lý các thay đổi về cấu hình do việc này tiềm ẩn nhiều khía cạnh phức tạp. Tuy nhiên, nếu không thể duy trì trạng thái giao diện người dùng bằng các lựa chọn ưu tiên (các đối tượng onSaveInstanceState(), ViewModel và bộ nhớ liên tục), bạn có thể ngăn hệ thống khởi động lại hoạt động của mình trong một số thay đổi nhất định về cấu hình. Ứng dụng của bạn sẽ nhận được một lệnh gọi lại khi cấu hình thay đổi để bạn có thể tự mình cập nhật hoạt động khi cần.

Giữ lại một đối tượng trong quá trình thay đổi cấu hình

Nếu việc khởi động lại hoạt động yêu cầu bạn khôi phục các tập dữ liệu lớn, thiết lập lại kết nối mạng hoặc thực hiện các thao tác chuyên sâu khác, thì việc khởi động lại hoàn toàn do thay đổi cấu hình có thể khiến trải nghiệm người dùng bị chậm. Ngoài ra, bạn có thể không khôi phục được hoàn toàn trạng thái hoạt động bằng Bundle mà hệ thống lưu bằng lệnh gọi lại onSaveInstanceState() – hàm không được thiết kế để chứa các đối tượng lớn (chẳng hạn như bitmap) và dữ liệu trong đó phải được chuyển đổi tuần tự rồi được huỷ tuần tự hoá trên luồng chính. Việc này có thể tốn nhiều dung lượng bộ nhớ và làm chậm quá trình thay đổi cấu hình. Trong trường hợp như vậy, bạn có thể giảm bớt gánh nặng khi khởi động lại một phần hoạt động bằng cách sử dụng ViewModel. Các đối tượng ViewModel được giữ nguyên trong các thay đổi về cấu hình, vì vậy, đây là nơi phù hợp để lưu giữ dữ liệu trên giao diện người dùng mà không cần phải truy vấn lại các đối tượng đó. Để biết thêm thông tin về cách sử dụng lớp ViewModel trong ứng dụng, hãy đọc bài viết Tổng quan về ViewModel.

Tự xử lý thay đổi về cấu hình

Nếu ứng dụng không cần cập nhật tài nguyên trong một quá trình thay đổi cấu hình cụ thể nhưng bạn cần tránh khởi động lại hoạt động do sự hạn chế về hiệu suất, thì 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, điều này ngăn cản hệ thống khởi động lại hoạt động.

Thận trọng: Việc tự xử lý thay đổi về cấu hình có thể cản trở đáng kể việc sử dụng tài nguyên thay thế vì hệ thống không tự động áp dụng các tài nguyên đó cho bạn. Kỹ thuật này nên được xem là phương án cuối cùng khi cần phải tránh việc khởi động lại do thay đổi cấu hình và không nên dùng cho hầu hết ứng dụng.

Để khai báo rằng hoạt động của bạn xử lý trường hợp thay đổi về cấu hình, hãy chỉnh sửa phần tử <activity> thích hợp trong tệp kê khai để bao gồm thuộc tính android:configChanges có giá trị đại diện cho cấu hình mà bạn muốn xử lý. Các giá trị có thể có được liệt kê trong tài liệu về thuộc tính android:configChanges. Các giá trị thường dùng nhất là "orientation", "screenSize", "screenLayout""keyboardHidden":

  • Giá trị "orientation" ngăn việc khởi động lại khi hướng màn hình thay đổi.
  • Giá trị "screenSize" cũng ngăn việc khởi động lại khi hướng thay đổi, nhưng chỉ dành cho Android 3.2 (API cấp 13) trở lên.
  • Giá trị "screenLayout" cần thiết để phát hiện các thay đổi có thể được kích hoạt bởi các thiết bị như điện thoại có thể gập lại và Chromebook có thể chuyển đổi.
  • Giá trị "keyboardHidden" ngăn việc khởi động lại khi khả năng sử dụng bàn phím thay đổi.

Nếu muốn xử lý các thay đổi về hướng theo cách thủ công trong ứng dụng, bạn phải khai báo các giá trị "orientation", "screenSize""screenLayout" trong các thuộc tính android:configChanges. Bạn có thể khai báo nhiều giá trị cấu hình trong thuộc tính bằng cách phân tách các giá trị đó bằng một dấu gạch đứng |.

Ví dụ: mã tệp kê khai sau đây khai báo một hoạt động xử lý cả thay đổi về hướng màn hình lẫn thay đổi về khả năng sử dụng bàn phím:

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

Bây giờ, khi thay đổi xảy ra ở một trong các cấu hình này, MyActivity sẽ không khởi động lại. Thay vào đó, MyActivity sẽ nhận được một lệnh gọi đến onConfigurationChanged().

Phương thức onConfigurationChanged() được truyền một đối tượng Configuration chỉ định cấu hình thiết bị mới. Bằng cách đọc các trường trong đối tượng Configuration, bạn có thể xác định cấu hình mới và thực hiện các thay đổi thích hợp bằng cách cập nhật tài nguyên dùng trong giao diện. Tại thời điểm phương thức này được gọi, đố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, vì vậy bạn có thể dễ dàng đặ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 mình.

Ví dụ: quy trình triển khai onConfigurationChanged() sau đây kiểm tra hướng thiết bị hiện tại:

Kotlin

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

    // Checks the orientation of the screen
    if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show()
    } else if (newConfig.orientation === Configuration.ORIENTATION_PORTRAIT) {
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show()
    }
}

Java

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

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Đối tượng Configuration đại diện cho tất cả các cấu hình hiện tại chứ không chỉ những cấu hình đã thay đổi. Trong hầu hết trường hợp, bạn sẽ không quan tâm chính xác cách cấu hình đã thay đổi, mà có thể chỉ cần chỉ định lại tất cả các tài nguyên cung cấp lựa chọn thay thế cho cấu hình mà bạn đang xử lý. Ví dụ: vì đối tượng Resources hiện đã được cập nhật, bạn có thể đặt lại mọi thực thể ImageView bằng setImageResource() và tài nguyên thích hợp cho cấu hình mới được sử dụng (như được mô tả trong phần Tổng quan về tài nguyên ứng dụng).

Lưu ý rằng các giá trị trong các trường Configuration là số nguyên khớp với các hằng số cụ thể trong lớp Configuration. Để xem tài liệu về hằng số cần sử dụng với mỗi trường, vui lòng tham khảo trường thích hợp trong tài liệu tham khảo Configuration.

Lưu ý: Khi khai báo hoạt động để xử lý thay đổi về cấu hình, bạn có trách nhiệm đặt lại mọi phần tử mà bạn cung cấp các lựa chọn thay thế. Nếu bạn khai báo hoạt động để xử lý việc thay đổi hướng và có hình ảnh thay đổi giữa chế độ ngang và dọc, bạn phải chỉ định lại từng tài nguyên cho từng phần tử trong onConfigurationChanged().

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 đó, tất cả các 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.

Tuy nhiên, bạn không nên sử dụng kỹ thuật này để thoát khỏi việc giữ lại trạng thái trong vòng đời hoạt động bình thường. Ứng dụng của bạn phải luôn có thể tắt và khởi động lại với trạng thái nguyên vẹn trước đó. 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. Nếu người dùng rời khỏi ứng dụng và ứng dụng được đặt trong nền, thì hệ thống có thể huỷ bỏ ứng dụng.