Vòng đời của activity (Khung hiển thị)

Các khái niệm và cách triển khai Jetpack Compose

Khi người dùng di chuyển qua, rời khỏi và quay lại ứng dụng của bạn, các thực thể Activity trong ứng dụng đó sẽ chuyển đổi qua nhiều trạng thái trong vòng đời của chúng. Lớp Activity cung cấp một số lệnh gọi lại cho phép hoạt động biết khi nào trạng thái thay đổi hoặc hệ thống đang tạo, dừng, tiếp tục một hoạt động hoặc huỷ quy trình mà hoạt động đó nằm trong.

Trong các phương thức gọi lại trong vòng đời, bạn có thể khai báo cách hoạt động của mình hoạt động khi người dùng rời khỏi và truy cập lại vào hoạt động. Ví dụ: nếu đang tạo một trình phát video trực tuyến, bạn có thể tạm dừng video và chấm dứt kết nối mạng khi người dùng chuyển sang một ứng dụng khác. Khi người dùng quay lại, bạn có thể kết nối lại với mạng và cho phép người dùng tiếp tục xem video từ cùng một vị trí.

Mỗi lệnh gọi lại cho phép bạn thực hiện công việc cụ thể phù hợp với một thay đổi trạng thái nhất định. Thực hiện đúng công việc vào đúng thời điểm và xử lý các quá trình chuyển đổi đúng cách sẽ giúp ứng dụng của bạn mạnh mẽ và hiệu quả hơn. Ví dụ: việc triển khai tốt các lệnh gọi lại vòng đời có thể giúp ứng dụng của bạn tránh được những vấn đề sau:

  • Gặp sự cố nếu người dùng nhận được cuộc gọi điện thoại hoặc chuyển sang một ứng dụng khác trong khi đang dùng ứng dụng của bạn.
  • Tiêu thụ tài nguyên hệ thống có giá trị khi người dùng không sử dụng ứng dụng một cách tích cực.
  • Mất tiến trình của người dùng nếu họ rời khỏi ứng dụng của bạn và quay lại vào lúc khác.
  • Gặp sự cố hoặc mất tiến trình của người dùng khi màn hình xoay giữa hướng ngang và hướng dọc.

Tài liệu này giải thích chi tiết về vòng đời của activity. Tài liệu này bắt đầu bằng cách mô tả mô hình vòng đời. Tiếp theo, phần này giải thích từng lệnh gọi lại: những gì xảy ra nội bộ trong khi các lệnh gọi lại thực thi và những gì bạn cần triển khai trong các lệnh gọi lại đó.

Sau đó, phần này giới thiệu ngắn gọn về mối quan hệ giữa trạng thái của activity và khả năng một quy trình bị hệ thống huỷ. Cuối cùng, phần này thảo luận về một số chủ đề liên quan đến quá trình chuyển đổi giữa các trạng thái hoạt động.

Để biết thông tin về cách xử lý vòng đời, bao gồm cả hướng dẫn về các phương pháp hay nhất, hãy xem phần Xử lý vòng đời bằng các thành phần nhận biết vòng đờiLưu trạng thái giao diện người dùng. Để tìm hiểu cách thiết kế một ứng dụng mạnh mẽ, đủ tiêu chuẩn phát hành công khai bằng cách sử dụng các hoạt động kết hợp với các thành phần kiến trúc, hãy xem Hướng dẫn về cấu trúc ứng dụng.

Các khái niệm về vòng đời hoạt động

Để điều hướng các quá trình chuyển đổi giữa các giai đoạn của vòng đời của activity, lớp Activity cung cấp một tập hợp cốt lõi gồm 6 lệnh gọi lại: onCreate, onStart, onResume, onPause, onStoponDestroy. Hệ thống sẽ gọi từng lệnh gọi lại này khi hoạt động chuyển sang một trạng thái mới.

Hình 1 trình bày một hình ảnh minh hoạ về mô hình này.

Hình 1. Hình minh hoạ đơn giản về vòng đời của activity.

Khi người dùng bắt đầu rời khỏi hoạt động, hệ thống sẽ gọi các phương thức để tháo dỡ hoạt động. Trong một số trường hợp, hoạt động chỉ bị tháo dỡ một phần và vẫn nằm trong bộ nhớ, chẳng hạn như khi người dùng chuyển sang một ứng dụng khác. Trong những trường hợp này, hoạt động vẫn có thể quay lại nền trước.

Nếu người dùng quay lại hoạt động, hoạt động đó sẽ tiếp tục từ nơi người dùng rời đi. Với một số trường hợp ngoại lệ, các ứng dụng bị hạn chế bắt đầu hoạt động khi chạy ở chế độ nền.

Khả năng hệ thống sẽ huỷ một quy trình nhất định, cùng với các hoạt động trong quy trình đó, phụ thuộc vào trạng thái của hoạt động tại thời điểm đó. Để biết thêm thông tin về mối quan hệ giữa trạng thái của activity và khả năng bị loại bỏ, hãy xem phần về trạng thái của activity và việc loại bỏ khỏi bộ nhớ.

Tuỳ thuộc vào độ phức tạp của hoạt động, có thể bạn không cần triển khai tất cả các phương thức vòng đời. Tuy nhiên, bạn cần hiểu rõ từng loại và triển khai những loại khiến ứng dụng của bạn hoạt động theo cách mà người dùng mong đợi.

Phương thức gọi lại trong vòng đời

Phần này cung cấp thông tin về khái niệm và cách triển khai đối với các phương thức gọi lại được dùng trong vòng đời của activity.

Một số thao tác thuộc về các phương thức vòng đời của activity. Tuy nhiên, hãy đặt mã triển khai các hành động của một thành phần phụ thuộc trong thành phần, thay vì phương thức vòng đời của activity. Để đạt được điều này, bạn cần phải làm cho thành phần phụ thuộc nhận biết được vòng đời. Để tìm hiểu cách giúp các thành phần phụ thuộc nhận biết được vòng đời, hãy xem phần Xử lý vòng đời bằng các thành phần nhận biết vòng đời.

onCreate

Bạn phải triển khai lệnh gọi lại này. Lệnh gọi lại này sẽ kích hoạt khi hệ thống tạo hoạt động lần đầu tiên. Khi được tạo, hoạt động sẽ chuyển sang trạng thái Đã tạo. Trong phương thức onCreate, hãy thực hiện logic khởi động ứng dụng cơ bản chỉ xảy ra một lần trong toàn bộ vòng đời của hoạt động.

Ví dụ: việc triển khai onCreate có thể liên kết dữ liệu với danh sách, liên kết hoạt động với ViewModel và khởi tạo một số biến phạm vi lớp. Phương thức này nhận tham số savedInstanceState, là một đối tượng Bundle chứa trạng thái đã lưu trước đó của hoạt động. Nếu hoạt động chưa từng tồn tại trước đây, thì giá trị của đối tượng Bundle sẽ là giá trị rỗng.

Nếu bạn có một thành phần nhận biết vòng đời được kết nối với vòng đời của hoạt động, thì thành phần đó sẽ nhận được sự kiện ON_CREATE. Phương thức được chú thích bằng @OnLifecycleEvent sẽ được gọi để thành phần nhận biết vòng đời của bạn có thể thực hiện mọi mã thiết lập cần thiết cho trạng thái đã tạo.

Ví dụ sau đây về phương thức onCreate cho thấy chế độ thiết lập cơ bản cho hoạt động, chẳng hạn như khai báo giao diện người dùng (được xác định trong tệp bố cục XML), xác định các biến thành phần và định cấu hình một số giao diện người dùng. Trong ví dụ này, tệp bố cục XML sẽ truyền mã nhận dạng tài nguyên R.layout.main_activity của tệp đến setContentView.

Kotlin

lateinit var textView: TextView

// Some transient state for the activity instance.
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

    // Recover the instance state.
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity)

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState)
}

Java

TextView textView;
// Some transient state for the activity instance.
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // Call the superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

    // Recover the instance state.
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // Set the user interface layout for this activity.
    // The layout is defined in the project res/layout/main_activity.xml file.
    setContentView(R.layout.main_activity);

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// Invoked when the activity might be temporarily destroyed; save the instance state here.
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // Call superclass to save any view hierarchy.
    super.onSaveInstanceState(outState);
}

Ngoài cách xác định tệp XML và truyền tệp đó đến setContentView, bạn có thể tạo các đối tượng View mới trong mã hoạt động và tạo một hệ phân cấp view bằng cách chèn các đối tượng View mới vào ViewGroup. Sau đó, bạn dùng bố cục đó bằng cách truyền ViewGroup gốc đến setContentView. Để biết thêm thông tin về cách tạo giao diện người dùng, hãy xem tài liệu về giao diện người dùng.

Hoạt động của bạn không duy trì ở trạng thái Đã tạo. Sau khi phương thức onCreate hoàn tất quá trình thực thi, hoạt động sẽ chuyển sang trạng thái Đã bắt đầu và hệ thống sẽ gọi các phương thức onStartonResume một cách nhanh chóng.

onStart

Khi hoạt động chuyển sang trạng thái Đã bắt đầu, hệ thống sẽ gọi onStart. Lệnh gọi này giúp người dùng nhìn thấy hoạt động khi ứng dụng chuẩn bị cho hoạt động chuyển sang nền trước và trở nên có thể tương tác. Ví dụ: phương thức này là nơi khởi chạy mã duy trì giao diện người dùng.

Khi hoạt động chuyển sang trạng thái Bắt đầu, mọi thành phần có nhận biết vòng đời được liên kết với vòng đời của hoạt động sẽ nhận được sự kiện ON_START.

Phương thức onStart hoàn tất nhanh chóng và cũng như trạng thái Đã tạo, hoạt động không duy trì ở trạng thái Đã bắt đầu. Sau khi lệnh gọi lại này hoàn tất, hoạt động sẽ chuyển sang trạng thái Đã tiếp tục và hệ thống sẽ gọi phương thức onResume.

onResume

Khi hoạt động chuyển sang trạng thái Đã tiếp tục, hoạt động sẽ chuyển sang nền trước và hệ thống sẽ gọi lệnh gọi lại onResume. Đây là trạng thái mà ứng dụng tương tác với người dùng. Ứng dụng vẫn ở trạng thái này cho đến khi có điều gì đó xảy ra khiến ứng dụng mất tiêu điểm, chẳng hạn như thiết bị nhận được cuộc gọi điện thoại, người dùng chuyển đến một hoạt động khác hoặc màn hình thiết bị tắt.

Khi hoạt động chuyển sang trạng thái Tiếp tục, mọi thành phần có nhận biết vòng đời được liên kết với vòng đời của hoạt động sẽ nhận được sự kiện ON_RESUME. Đây là nơi các thành phần vòng đời có thể bật mọi chức năng cần chạy trong khi thành phần hiển thị và ở nền trước, chẳng hạn như bắt đầu xem trước camera.

Khi một sự kiện gây gián đoạn xảy ra, hoạt động sẽ chuyển sang trạng thái Tạm dừng và hệ thống sẽ gọi lệnh gọi lại onPause.

Nếu hoạt động chuyển từ trạng thái Paused (Tạm dừng) sang trạng thái Resumed (Đã tiếp tục), hệ thống sẽ gọi lại phương thức onResume. Vì lý do này, hãy triển khai onResume để khởi chạy các thành phần mà bạn phát hành trong onPause và thực hiện mọi thao tác khởi chạy khác phải diễn ra mỗi khi hoạt động chuyển sang trạng thái Đã tiếp tục.

Dưới đây là ví dụ về một thành phần nhận biết vòng đời truy cập vào camera khi thành phần nhận được sự kiện ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }
    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }
    ...
}

Đoạn mã trước đó sẽ khởi chạy camera sau khi LifecycleObserver nhận được sự kiện ON_RESUME. Tuy nhiên, ở chế độ nhiều cửa sổ, hoạt động của bạn có thể hiển thị đầy đủ ngay cả khi ở trạng thái Tạm dừng. Ví dụ: khi ứng dụng ở chế độ nhiều cửa sổ và người dùng nhấn vào cửa sổ không chứa hoạt động của bạn, hoạt động đó sẽ chuyển sang trạng thái Tạm dừng.

Nếu bạn chỉ muốn máy ảnh hoạt động khi ứng dụng ở trạng thái Đã tiếp tục (hiển thị và hoạt động ở nền trước), hãy khởi chạy máy ảnh sau sự kiện ON_RESUME như minh hoạ trước đó. Nếu muốn duy trì trạng thái hoạt động của camera trong khi hoạt động ở trạng thái Tạm dừng nhưng vẫn hiển thị (chẳng hạn như ở chế độ nhiều cửa sổ), hãy khởi chạy camera sau sự kiện ON_START.

Tuy nhiên, việc kích hoạt camera trong khi hoạt động của bạn ở trạng thái Tạm dừng có thể từ chối quyền truy cập vào camera đối với một ứng dụng khác ở trạng thái Tiếp tục trong chế độ nhiều cửa sổ. Đôi khi, bạn cần duy trì trạng thái hoạt động của camera trong khi hoạt động của bạn đang ở trạng thái Tạm dừng, nhưng điều này có thể làm giảm trải nghiệm tổng thể của người dùng.

Vì lý do này, hãy cân nhắc kỹ xem thời điểm nào trong vòng đời là phù hợp nhất để kiểm soát các tài nguyên hệ thống dùng chung trong bối cảnh chế độ nhiều cửa sổ. Để tìm hiểu thêm về cách hỗ trợ chế độ nhiều cửa sổ, hãy xem bài viết Hỗ trợ chế độ nhiều cửa sổ.

Bất kể bạn chọn thực hiện thao tác khởi tạo trong sự kiện tạo nào, hãy nhớ sử dụng sự kiện trong vòng đời tương ứng để phát hành tài nguyên. Nếu bạn khởi tạo một thứ gì đó sau sự kiện ON_START, hãy phát hành hoặc chấm dứt sự kiện đó sau sự kiện ON_STOP. Nếu bạn khởi chạy sau sự kiện ON_RESUME, hãy phát hành sau sự kiện ON_PAUSE.

Đoạn mã trước đó đặt mã khởi tạo camera trong một thành phần nhận biết vòng đời. Thay vào đó, bạn có thể đặt mã này trực tiếp vào các phương thức gọi lại trong vòng đời của activity, chẳng hạn như onStartonStop, nhưng chúng tôi không khuyến khích bạn làm như vậy. Việc thêm logic này vào một thành phần độc lập, nhận biết vòng đời cho phép bạn dùng lại thành phần này trên nhiều hoạt động mà không cần phải sao chép mã. Để tìm hiểu cách tạo một thành phần nhận biết vòng đời, hãy xem phần Xử lý vòng đời bằng các thành phần nhận biết vòng đời (Khung hiển thị).

onPause

Hệ thống gọi phương thức này làm chỉ báo đầu tiên cho biết người dùng đang rời khỏi hoạt động của bạn, mặc dù không phải lúc nào điều này cũng có nghĩa là hoạt động đang bị huỷ. Điều này cho biết hoạt động không còn ở nền trước nữa, nhưng vẫn hiển thị nếu người dùng đang ở chế độ nhiều cửa sổ. Có một số lý do khiến một hoạt động có thể chuyển sang trạng thái này:

  • Một sự kiện làm gián đoạn quá trình thực thi ứng dụng, như mô tả trong phần về lệnh gọi lại onResume, sẽ tạm dừng hoạt động hiện tại. Đây là trường hợp phổ biến nhất.
  • Ở chế độ nhiều cửa sổ, chỉ có một ứng dụng được lấy tiêu điểm tại một thời điểm bất kỳ và hệ thống sẽ tạm dừng tất cả các ứng dụng khác.
  • Việc mở một hoạt động mới, bán trong suốt (chẳng hạn như hộp thoại) sẽ tạm dừng hoạt động mà hoạt động đó che phủ. Miễn là hoạt động hiển thị một phần nhưng không được lấy tiêu điểm, hoạt động đó vẫn ở trạng thái tạm dừng.

Khi một hoạt động chuyển sang trạng thái Tạm dừng, mọi thành phần biết vòng đời được liên kết với vòng đời của hoạt động đó sẽ nhận được sự kiện ON_PAUSE. Đây là nơi các thành phần vòng đời có thể dừng mọi chức năng không cần chạy khi thành phần không ở nền trước, chẳng hạn như dừng bản xem trước của camera.

Sử dụng phương thức onPause để tạm dừng hoặc điều chỉnh các thao tác không thể tiếp tục hoặc có thể tiếp tục ở mức độ vừa phải, trong khi Activity ở trạng thái Tạm dừng và bạn dự kiến sẽ tiếp tục trong thời gian ngắn.

Bạn cũng có thể dùng phương thức onPause để giải phóng tài nguyên hệ thống, các thao tác đối với cảm biến (chẳng hạn như GPS) hoặc mọi tài nguyên ảnh hưởng đến thời lượng pin trong khi hoạt động của bạn ở trạng thái Tạm dừng và người dùng không cần đến những tài nguyên đó.

Tuy nhiên, như đã đề cập trong phần về onResume, một hoạt động bị Tạm dừng vẫn có thể hiển thị đầy đủ nếu ứng dụng ở chế độ nhiều cửa sổ. Hãy cân nhắc sử dụng onStop thay vì onPause để phát hành hoàn toàn hoặc điều chỉnh các tài nguyên và thao tác liên quan đến giao diện người dùng nhằm hỗ trợ tốt hơn chế độ nhiều cửa sổ.

Ví dụ sau đây về LifecycleObserver phản ứng với sự kiện ON_PAUSE là đối tượng tương ứng với ví dụ về sự kiện ON_RESUME trước đó, giải phóng máy ảnh khởi động sau khi nhận được sự kiện ON_RESUME:

Kotlin

class CameraComponent : LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }
    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {
    ...
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }
    ...
}

Ví dụ này đặt mã phát hành camera sau khi LifecycleObserver nhận được sự kiện ON_PAUSE.

Việc thực thi onPause diễn ra rất nhanh và không nhất thiết cung cấp đủ thời gian để thực hiện các thao tác lưu. Vì lý do này, đừng dùng onPause để lưu dữ liệu ứng dụng hoặc dữ liệu người dùng, thực hiện các lệnh gọi mạng hoặc thực thi giao dịch cơ sở dữ liệu. Công việc như vậy có thể không hoàn tất trước khi phương thức hoàn tất.

Thay vào đó, hãy thực hiện các thao tác tắt khi tải nặng trong onStop. Để biết thêm thông tin về các thao tác phù hợp cần thực hiện trong onStop, hãy xem phần tiếp theo. Để biết thêm thông tin về cách lưu dữ liệu, hãy xem phần lưu và khôi phục trạng thái.

Việc hoàn tất phương thức onPause không có nghĩa là hoạt động sẽ rời khỏi trạng thái Tạm dừng. Thay vào đó, hoạt động vẫn ở trạng thái này cho đến khi hoạt động tiếp tục hoặc hoàn toàn không hiển thị với người dùng. Nếu hoạt động tiếp tục, hệ thống sẽ gọi lại lệnh gọi lại onResume.

Nếu hoạt động quay lại từ trạng thái Tạm dừng sang trạng thái Đã tiếp tục, hệ thống sẽ giữ phiên bản Activity thường trú trong bộ nhớ, gọi lại phiên bản đó khi hệ thống gọi onResume. Trong trường hợp này, bạn không cần khởi động lại các thành phần được tạo trong bất kỳ phương thức gọi lại nào dẫn đến trạng thái Resumed. Nếu hoạt động hoàn toàn không hiển thị, hệ thống sẽ gọi onStop.

onStop

Khi hoạt động không còn hiển thị với người dùng, hoạt động đó sẽ chuyển sang trạng thái Đã dừng và hệ thống sẽ gọi lệnh gọi lại onStop. Điều này có thể xảy ra khi một hoạt động mới ra mắt bao phủ toàn bộ màn hình. Hệ thống cũng gọi onStop khi hoạt động kết thúc và sắp bị chấm dứt.

Khi hoạt động chuyển sang trạng thái Dừng, mọi thành phần có nhận biết vòng đời được liên kết với vòng đời của hoạt động sẽ nhận được sự kiện ON_STOP. Đây là nơi các thành phần vòng đời có thể dừng mọi chức năng không cần chạy trong khi thành phần không xuất hiện trên màn hình.

Trong phương thức onStop, hãy giải phóng hoặc điều chỉnh những tài nguyên không cần thiết khi người dùng không thấy ứng dụng. Ví dụ: ứng dụng của bạn có thể tạm dừng ảnh động hoặc chuyển từ cập nhật vị trí chi tiết sang cập nhật vị trí tương đối. Việc sử dụng onStop thay vì onPause có nghĩa là công việc liên quan đến giao diện người dùng vẫn tiếp tục, ngay cả khi người dùng đang xem hoạt động của bạn ở chế độ nhiều cửa sổ.

Ngoài ra, hãy dùng onStop để thực hiện các thao tác tắt tương đối tốn nhiều CPU. Ví dụ: nếu không tìm được thời điểm thích hợp hơn để lưu thông tin vào cơ sở dữ liệu, bạn có thể thực hiện việc này trong onStop. Ví dụ sau đây minh hoạ cách triển khai onStop để lưu nội dung của một bản nháp ghi chú vào bộ nhớ liên tục:

Kotlin

override fun onStop() {
    // Call the superclass method first.
    super.onStop()

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // Call the superclass method first.
    super.onStop();

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // Do this update in background on an AsyncQueryHandler or equivalent.
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

Đoạn mã mẫu trước đó sử dụng trực tiếp SQLite. Tuy nhiên, bạn nên sử dụng Room, một thư viện lưu trữ cung cấp một lớp trừu tượng thông qua SQLite. Để tìm hiểu thêm về lợi ích của việc sử dụng Room và cách triển khai Room trong ứng dụng, hãy xem hướng dẫn về thư viện lưu trữ dữ liệu Room.

Khi hoạt động chuyển sang trạng thái Đã dừng, đối tượng Activity sẽ được lưu giữ trong bộ nhớ: đối tượng này duy trì tất cả thông tin về trạng thái và thành viên, nhưng không được đính kèm vào trình quản lý cửa sổ. Khi hoạt động tiếp tục, hoạt động này sẽ gọi lại thông tin này.

Bạn không cần khởi động lại các thành phần được tạo trong bất kỳ phương thức gọi lại nào dẫn đến trạng thái Đã tiếp tục. Hệ thống cũng theo dõi trạng thái hiện tại của từng đối tượng View trong bố cục. Vì vậy, nếu người dùng nhập văn bản vào một tiện ích EditText, nội dung đó sẽ được giữ lại để bạn không cần lưu và khôi phục.

Từ trạng thái Đã dừng, hoạt động sẽ quay lại để tương tác với người dùng hoặc hoạt động đã chạy xong và biến mất. Nếu hoạt động quay lại, hệ thống sẽ gọi onRestart. Nếu Activity đã chạy xong, hệ thống sẽ gọi onDestroy.

onDestroy

onDestroy được gọi trước khi hoạt động bị huỷ. Hệ thống gọi lệnh gọi lại này vì một trong hai lý do:

  1. Hoạt động đang kết thúc, do người dùng đóng hoàn toàn hoạt động hoặc do finish được gọi trên hoạt động.
  2. Hệ thống tạm thời huỷ hoạt động do thay đổi cấu hình, chẳng hạn như xoay thiết bị hoặc chuyển sang chế độ nhiều cửa sổ.

Khi hoạt động chuyển sang trạng thái bị huỷ, mọi thành phần có nhận biết vòng đời được liên kết với vòng đời của hoạt động sẽ nhận được sự kiện ON_DESTROY. Đây là nơi các thành phần vòng đời có thể dọn dẹp mọi thứ cần thiết trước khi Activity bị huỷ.

Thay vì đặt logic vào Activity để xác định lý do khiến Activity bị huỷ, hãy sử dụng đối tượng ViewModel để chứa dữ liệu khung hiển thị có liên quan cho Activity. Nếu Activity được tạo lại do có sự thay đổi về cấu hình, thì ViewModel không cần làm gì cả, vì ViewModel được giữ lại và chuyển cho thực thể Activity tiếp theo.

Nếu Activity không được tạo lại, thì ViewModel sẽ gọi phương thức onCleared, trong đó ViewModel có thể dọn dẹp mọi dữ liệu cần thiết trước khi bị huỷ. Bạn có thể phân biệt hai trường hợp này bằng phương thức isFinishing.

Nếu hoạt động đang hoàn tất, onDestroy là phương thức gọi lại trong vòng đời cuối cùng mà hoạt động nhận được. Nếu onDestroy được gọi do có sự thay đổi về cấu hình, thì hệ thống sẽ ngay lập tức tạo một phiên bản hoạt động mới rồi gọi onCreate trên phiên bản mới đó trong cấu hình mới.

Lệnh gọi lại onDestroy sẽ giải phóng tất cả tài nguyên chưa được giải phóng bởi các lệnh gọi lại trước đó, chẳng hạn như onStop.

Lưu và khôi phục trạng thái giao diện người dùng tạm thời

Người dùng muốn trạng thái giao diện người dùng của một hoạt động vẫn giữ nguyên trong suốt quá trình thay đổi cấu hình, chẳng hạn như khi thực hiện thao tác xoay hoặc khi chuyển sang chế độ nhiều cửa sổ. Tuy nhiên, theo mặc định, hệ thống sẽ huỷ hoạt động khi xảy ra thay đổi về cấu hình như vậy, xoá sạch mọi trạng thái giao diện người dùng đã lưu trong thực thể của hoạt động đó.

Tương tự, người dùng muốn trạng thái giao diện người dùng vẫn giữ nguyên nếu họ tạm thời chuyển từ ứng dụng của bạn sang một ứng dụng khác rồi sau đó quay lại ứng dụng của bạn. Tuy nhiên, hệ thống có thể huỷ bỏ quy trình của ứng dụng trong khi người dùng thoát ra ngoài và hoạt động của bạn bị dừng.

Khi các hạn chế của hệ thống hủy bỏ hoạt động, hãy duy trì trạng thái giao diện người dùng tạm thời của người dùng bằng cách kết hợp ViewModel, onSaveInstanceState và/hoặc bộ nhớ cục bộ. Để tìm hiểu thêm về kỳ vọng của người dùng so với hành vi của hệ thống và cách tốt nhất để duy trì dữ liệu trạng thái giao diện người dùng phức tạp trong trường hợp hệ thống buộc tắt hoạt động và quy trình bị buộc tắt, hãy xem bài viết Lưu trạng thái giao diện người dùng.

Phần này trình bày trạng thái của thực thể và cách triển khai phương thức onSaveInstance, đây là một lệnh gọi lại trên chính hoạt động. Nếu dữ liệu giao diện người dùng của bạn có kích thước nhỏ, bạn có thể chỉ dùng onSaveInstance để duy trì trạng thái giao diện người dùng trong cả trường hợp thay đổi cấu hình và trường hợp hệ thống bị buộc tắt. Tuy nhiên, vì onSaveInstance phải chịu chi phí chuyển đổi tuần tự/giải tuần tự, nên trong hầu hết các trường hợp, bạn sẽ sử dụng cả ViewModelonSaveInstance, như trình bày trong bài viết Lưu trạng thái giao diện người dùng.

Trạng thái phiên bản

Có một số trường hợp hoạt động của bạn bị huỷ do hành vi bình thường của ứng dụng, chẳng hạn như khi người dùng nhấn nút Quay lại hoặc hoạt động của bạn báo hiệu việc huỷ chính nó bằng cách gọi phương thức finish.

Khi hoạt động của bạn bị huỷ do người dùng nhấn vào nút Quay lại hoặc hoạt động tự hoàn tất, cả khái niệm của hệ thống và người dùng về thực thể Activity đó sẽ biến mất vĩnh viễn. Trong những trường hợp này, kỳ vọng của người dùng sẽ khớp với hành vi của hệ thống và bạn không cần phải làm gì thêm.

Tuy nhiên, nếu hệ thống huỷ hoạt động do các hạn chế của hệ thống (chẳng hạn như thay đổi về cấu hình hoặc áp lực bộ nhớ), thì mặc dù phiên bản Activity thực tế đã biến mất, nhưng hệ thống vẫn nhớ rằng phiên bản đó đã từng tồn tại. Nếu người dùng cố gắng quay lại hoạt động này, hệ thống sẽ tạo một phiên bản mới của hoạt động đó bằng cách sử dụng một tập hợp dữ liệu đã lưu mô tả trạng thái của hoạt động khi hoạt động đó bị huỷ.

Dữ liệu đã lưu mà hệ thống dùng để khôi phục trạng thái trước đó được gọi là trạng thái thực thể. Đây là một tập hợp các cặp khoá-giá trị được lưu trữ trong đối tượng Bundle. Theo mặc định, hệ thống sử dụng trạng thái phiên bản Bundle để lưu thông tin về từng đối tượng View trong bố cục hoạt động của bạn, chẳng hạn như giá trị văn bản đã nhập vào một tiện ích EditText.

Vì vậy, nếu phiên bản hoạt động của bạn bị huỷ bỏ và được tạo lại, thì trạng thái của bố cục sẽ được khôi phục về trạng thái trước đó mà bạn không cần mã. Tuy nhiên, hoạt động của bạn có thể có nhiều thông tin về trạng thái mà bạn muốn khôi phục, chẳng hạn như các biến thành phần theo dõi tiến trình của người dùng trong hoạt động.

Đối tượng Bundle không phù hợp để lưu giữ nhiều dữ liệu, vì đối tượng này yêu cầu quá trình chuyển đổi tuần tự trên luồng chính và tiêu thụ bộ nhớ quy trình hệ thống. Để duy trì nhiều dữ liệu hơn một lượng rất nhỏ, hãy kết hợp các phương pháp duy trì dữ liệu, sử dụng bộ nhớ cục bộ liên tục, phương thức onSaveInstanceState và lớp ViewModel, như mô tả trong phần Lưu trạng thái giao diện người dùng.

Lưu trạng thái giao diện người dùng đơn giản, gọn nhẹ bằng onSaveInstanceState

Khi hoạt động của bạn bắt đầu dừng, hệ thống sẽ gọi phương thức onSaveInstanceState để hoạt động của bạn có thể lưu thông tin trạng thái vào một gói trạng thái thực thể. Cách triển khai mặc định của phương thức này sẽ lưu thông tin tạm thời về trạng thái của hệ phân cấp view của hoạt động, chẳng hạn như văn bản trong tiện ích EditText hoặc vị trí cuộn của tiện ích ListView.

Để lưu thêm thông tin về trạng thái phiên bản cho hoạt động của bạn, hãy ghi đè onSaveInstanceState và thêm các cặp khoá-giá trị vào đối tượng Bundle được lưu trong trường hợp hoạt động của bạn bị huỷ ngoài ý muốn. Khi ghi đè onSaveInstanceState, bạn cần gọi quá trình triển khai lớp trên nếu muốn quá trình triển khai mặc định lưu trạng thái của hệ phân cấp view. Lệnh này được minh hoạ trong ví dụ sau:

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state.
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state.
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state.
    super.onSaveInstanceState(savedInstanceState);
}

Để lưu dữ liệu cố định, chẳng hạn như lựa chọn ưu tiên của người dùng hoặc dữ liệu cho cơ sở dữ liệu, hãy tận dụng các cơ hội thích hợp khi hoạt động của bạn ở nền trước. Nếu không có cơ hội như vậy, hãy lưu dữ liệu liên tục trong phương thức onStop.

Khôi phục trạng thái giao diện người dùng của hoạt động bằng trạng thái của thực thể đã lưu

Khi hoạt động của bạn được tạo lại sau khi bị huỷ trước đó, bạn có thể khôi phục trạng thái phiên bản đã lưu từ Bundle mà hệ thống truyền đến hoạt động của bạn. Cả phương thức gọi lại onCreateonRestoreInstanceState đều nhận được cùng một Bundle chứa thông tin trạng thái của phiên bản.

Vì phương thức onCreate được gọi cho dù hệ thống đang tạo một phiên bản mới của hoạt động hay tạo lại một phiên bản trước đó, nên bạn cần kiểm tra xem trạng thái Bundle có phải là giá trị rỗng hay không trước khi cố gắng đọc trạng thái đó. Nếu giá trị này là null, thì hệ thống đang tạo một phiên bản mới của hoạt động thay vì khôi phục một phiên bản trước đó đã bị huỷ.

Đoạn mã sau đây cho biết cách bạn có thể khôi phục một số dữ liệu trạng thái trong onCreate:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state.
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance.
    if (savedInstanceState != null) {
        // Restore value of members from saved state.
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance.
    }
    // ...
}

Thay vì khôi phục trạng thái trong onCreate, bạn có thể chọn triển khai onRestoreInstanceState. Hệ thống sẽ gọi phương thức này sau phương thức onStart. Hệ thống chỉ gọi onRestoreInstanceState nếu có trạng thái đã lưu để khôi phục, vì vậy, bạn không cần kiểm tra xem Bundle có giá trị rỗng hay không.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance.
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy.
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance.
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

Ứng dụng có thể vào và thoát một Hoạt động, có thể nhiều lần, trong suốt Vòng đời của ứng dụng, chẳng hạn như khi người dùng nhấn nút quay lại trên Thiết bị hoặc Hoạt động đó chạy một Hoạt động khác.

Phần này đề cập đến những chủ đề bạn cần biết để triển khai thành công các hiệu ứng chuyển đổi hoạt động. Các chủ đề này bao gồm việc bắt đầu một hoạt động từ một hoạt động khác, lưu trạng thái của activity và khôi phục trạng thái của activity.

Bắt đầu một hoạt động từ một hoạt động khác

Một hoạt động thường cần bắt đầu một hoạt động khác tại một thời điểm nào đó. Nhu cầu này phát sinh, chẳng hạn như khi một ứng dụng cần di chuyển từ màn hình hiện tại sang một màn hình mới.

Tuỳ thuộc vào việc hoạt động của bạn có muốn nhận lại kết quả từ hoạt động mới mà hoạt động đó sắp bắt đầu hay không, bạn sẽ bắt đầu hoạt động mới bằng phương thức startActivity hoặc phương thức startActivityForResult. Trong cả hai trường hợp, bạn đều truyền vào một đối tượng Intent.

Đối tượng Intent chỉ định chính xác hoạt động mà bạn muốn bắt đầu hoặc mô tả loại hành động mà bạn muốn thực hiện. Hệ thống sẽ chọn hoạt động phù hợp cho bạn, thậm chí có thể là từ một ứng dụng khác. Đối tượng Intent cũng có thể mang theo một lượng nhỏ dữ liệu để hoạt động đã bắt đầu sử dụng. Để biết thêm thông tin về lớp Intent, hãy xem bài viết Ý định và bộ lọc ý định.

startActivity

Nếu hoạt động mới bắt đầu không cần trả về kết quả, hoạt động hiện tại có thể bắt đầu bằng cách gọi phương thức startActivity.

Khi làm việc trong ứng dụng của riêng mình, bạn thường chỉ cần chạy một hoạt động đã biết. Ví dụ: đoạn mã sau đây cho thấy cách khởi chạy một hoạt động có tên là SignInActivity.

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

Ứng dụng của bạn cũng có thể muốn thực hiện một số thao tác, chẳng hạn như gửi email, tin nhắn văn bản hoặc thông tin cập nhật trạng thái, bằng cách sử dụng dữ liệu từ hoạt động của bạn. Trong trường hợp này, ứng dụng của bạn có thể không có các hoạt động riêng để thực hiện những thao tác như vậy, vì vậy, bạn có thể tận dụng các hoạt động do những ứng dụng khác cung cấp trên thiết bị. Các hoạt động này có thể thực hiện các thao tác cho bạn.

Đây là lúc các ý định thực sự có giá trị. Bạn có thể tạo một ý định mô tả thao tác mà bạn muốn thực hiện và hệ thống sẽ chạy hoạt động thích hợp từ một ứng dụng khác. Nếu có nhiều hoạt động có thể xử lý ý định, thì người dùng có thể chọn hoạt động sẽ dùng. Ví dụ: nếu muốn cho phép người dùng gửi nội dung email, bạn có thể tạo ý định sau:

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

EXTRA_EMAIL được thêm vào ý định là một mảng chuỗi gồm các địa chỉ email mà email sẽ được gửi đến. Khi một ứng dụng email phản hồi ý định này, ứng dụng sẽ đọc mảng chuỗi được cung cấp trong phần bổ sung và đặt các địa chỉ trong trường "to" của biểu mẫu soạn email. Trong trường hợp này, hoạt động của ứng dụng email sẽ bắt đầu và khi người dùng hoàn tất, hoạt động của bạn sẽ tiếp tục.

startActivityForResult

Đôi khi, bạn muốn nhận lại kết quả từ một hoạt động khi hoạt động đó kết thúc. Ví dụ: bạn có thể bắt đầu một hoạt động cho phép người dùng chọn một người trong danh sách danh bạ. Khi kết thúc, phương thức này sẽ trả về người được chọn. Để thực hiện việc này, bạn gọi phương thức startActivityForResult(Intent, int), trong đó tham số số nguyên xác định lệnh gọi.

Giá trị nhận dạng này dùng để phân biệt giữa nhiều lệnh gọi đến startActivityForResult(Intent, int) từ cùng một hoạt động. Đây không phải là một giá trị nhận dạng chung và không có nguy cơ xung đột với các ứng dụng hoặc hoạt động khác. Kết quả sẽ được trả về thông qua phương thức onActivityResult(int, int, Intent).

Khi thoát, hoạt động con có thể gọi setResult(int) để trả về dữ liệu cho hoạt động mẹ. Hoạt động con phải cung cấp mã kết quả, có thể là kết quả tiêu chuẩn RESULT_CANCELED, RESULT_OK hoặc bất kỳ giá trị tuỳ chỉnh nào bắt đầu từ RESULT_FIRST_USER.

Ngoài ra, hoạt động của trẻ có thể tuỳ ý trả về một đối tượng Intent chứa mọi dữ liệu bổ sung mà trẻ muốn. Hoạt động gốc sử dụng phương thức onActivityResult(int, int, Intent), cùng với giá trị nhận dạng số nguyên mà hoạt động gốc đã cung cấp ban đầu, để nhận thông tin.

Nếu một hoạt động con không thành công vì bất kỳ lý do nào, chẳng hạn như gặp sự cố, thì hoạt động mẹ sẽ nhận được kết quả có mã RESULT_CANCELED.

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    // A contact was picked. Display it to the user.
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
    // ...

    static final int PICK_CONTACT_REQUEST = 0;

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                new Intent(Intent.ACTION_PICK,
                new Uri("content://contacts")),
                PICK_CONTACT_REQUEST);
            return true;
        }
        return false;
    }

    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {
        if (requestCode == PICK_CONTACT_REQUEST) {
            if (resultCode == RESULT_OK) {
                // A contact was picked. Display it to the user.
                startActivity(new Intent(Intent.ACTION_VIEW, data));
            }
        }
    }
}

Hoạt động phối hợp

Khi một hoạt động bắt đầu một hoạt động khác, cả hai hoạt động đều trải qua các quá trình chuyển đổi vòng đời. Hoạt động đầu tiên dừng hoạt động và chuyển sang trạng thái Tạm dừng hoặc Dừng, trong khi hoạt động còn lại được tạo. Trong trường hợp các hoạt động này chia sẻ dữ liệu đã lưu vào đĩa hoặc nơi khác, bạn cần hiểu rằng hoạt động đầu tiên chưa dừng hoàn toàn trước khi hoạt động thứ hai được tạo. Thay vào đó, quá trình bắt đầu lệnh thứ hai sẽ trùng lặp với quá trình dừng lệnh đầu tiên.

Thứ tự của các lệnh gọi lại vòng đời được xác định rõ ràng, đặc biệt là khi hai hoạt động nằm trong cùng một quy trình (nói cách khác là cùng một ứng dụng) và một hoạt động đang bắt đầu hoạt động kia. Dưới đây là thứ tự các thao tác xảy ra khi Hoạt động A bắt đầu Hoạt động B:

  1. Phương thức onPause của Hoạt động A sẽ thực thi.
  2. Các phương thức onCreate, onStartonResume của Hoạt động B sẽ thực thi theo trình tự. Hoạt động B hiện được người dùng chú ý.
  3. Nếu Hoạt động A không còn hiển thị trên màn hình, phương thức onStop của hoạt động này sẽ thực thi.

Trình tự lệnh gọi lại vòng đời này cho phép bạn quản lý quá trình chuyển đổi thông tin từ hoạt động này sang hoạt động khác.