Xử lý vòng đời bằng các thành phần nhận biết vòng đời   Một phần của Android Jetpack.

Các thành phần nhận biết vòng đời thực hiện các hành động để phản hồi một thay đổi trong trạng thái vòng đời của một thành phần khác, chẳng hạn như các hoạt động và các mảnh. Các thành phần này giúp bạn tạo ra mã có tổ chức tốt hơn và thường có trọng số nhẹ hơn nên cũng dễ duy trì hơn.

Một mẫu phổ biến là triển khai hành động của các thành phần phụ thuộc trong phương thức vòng đời của các hoạt động và mảnh. Tuy nhiên, mẫu này lại dẫn đến sự thiếu tổ chức của mã và khiến số lỗi tăng mạnh. Bằng cách sử dụng các thành phần nhận biết được vòng đời, bạn có thể di chuyển mã của các thành phần phụ thuộc ra khỏi các phương thức vòng đời và đưa vào chính các thành phần đó.

Gói androidx.lifecycle cung cấp các lớp và giao diện cho phép bạn tạo các thành phần nhận biết được vòng đời–là các thành phần có thể tự động điều chỉnh hành vi dựa trên trạng thái vòng đời hiện tại của hoạt động hoặc mảnh.

Hầu hết các thành phần ứng dụng được xác định trong Khung Android đều có các chu kỳ hoạt động đi kèm. Vòng đời do hệ điều hành hoặc mã khung chạy trong quy trình của bạn quản lý. Các yếu tố này đóng vai trò cốt lõi trong cách hoạt động của Android và ứng dụng của bạn phải tuân theo các hoạt động đó. Nếu không, bộ nhớ có thể bị rò rỉ hoặc thậm chí là gặp phải các sự cố ứng dụng.

Hãy tưởng tượng rằng chúng ta có một hoạt động hiển thị vị trí thiết bị trên màn hình. Cách triển khai phổ biến có thể như sau:

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val callback: (Location) -> Unit
) {

    fun start() {
        // connect to system location service
    }

    fun stop() {
        // disconnect from system location service
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        myLocationListener.start()
        // manage other components that need to respond
        // to the activity lifecycle
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Java

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Mặc dù mã mẫu này có vẻ ổn, nhưng trong một ứng dụng thực tế, bạn sẽ gặp tình trạng có quá nhiều lệnh gọi quản lý giao diện người dùng và các thành phần khác để phản hồi trạng thái vòng đời hiện tại. Việc quản lý nhiều thành phần sẽ tạo ra một lượng mã đáng kể trong các phương thức vòng đời, chẳng hạn như onStart()onStop(), khiến việc duy trì các mã này gặp khó khăn.

Hơn nữa, không thể đảm bảo rằng thành phần đó sẽ bắt đầu trước khi các hoạt động hoặc mảnh bị ngừng. Điều này đặc biệt đúng nếu chúng ta cần thực hiện một thao tác chạy trong thời gian dài, chẳng hạn như một số hoạt động kiểm tra cấu hình trong onStart(). Điều này có thể gây ra tình huống tương tranh, trong đó phương thức onStop() kết thúc trước onStart(), khiến các thành phần tồn tại lâu hơn cần thiết.

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        Util.checkUserStatus { result ->
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start()
            }
        }
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
    }

}

Java

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}

Gói androidx.lifecycle cung cấp các lớp và giao diện giúp bạn giải quyết các vấn đề này một cách linh hoạt và riêng biệt.

Vòng đời

Lifecycle là một lớp chứa thông tin về trạng thái vòng đời của một thành phần (như hoạt động hoặc mảnh) và cho phép các đối tượng khác quan sát trạng thái này.

Lifecycle sử dụng hai kiểu dữ liệu liệt kê chính để theo dõi trạng thái vòng đời của thành phần liên kết:

Sự kiện
Các sự kiện trong vòng đời được điều phối từ khung và lớp Lifecycle. Các sự kiện này liên kết tới các sự kiện gọi lại trong các hoạt động và mảnh.
Trạng thái
Trạng thái hiện tại của thành phần mà đối tượng Lifecycle theo dõi.
Sơ đồ về các trạng thái vòng đời
Hình 1. Các trạng thái và sự kiện bao gồm hoạt động vòng đời trên Android

Hãy coi các trạng thái như các nút của một biểu đồ và các sự kiện như các cạnh giữa các nút này.

Một lớp có thể theo dõi trạng thái vòng đời của thành phần bằng cách triển khai DefaultLifecycleObserver và ghi đè các phương thức tương ứng như onCreate, onStart, v.v. Sau đó, bạn có thể thêm trình quan sát bằng cách gọi phương thức addObserver() của lớp Lifecycle và truyền một thực thể của trình quan sát như trong ví dụ sau:

Kotlin

class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(MyObserver())

Java

public class MyObserver implements DefaultLifecycleObserver {
    @Override
    public void onResume(LifecycleOwner owner) {
        connect()
    }

    @Override
    public void onPause(LifecycleOwner owner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

Trong ví dụ trên, đối tượng myLifecycleOwner triển khai giao diện LifecycleOwner, điều này được giải thích ở phần sau.

LifecycleOwner

LifecycleOwner là một giao diện một phương thức cho biết rằng lớp này có một Lifecycle. Giao diện này có một phương thức getLifecycle() mà lớp phải triển khai. Thay vào đó, nếu bạn đang cố gắng quản lý vòng đời của toàn bộ quy trình đăng ký, hãy xem ProcessLifecycleOwner.

Giao diện này tóm tắt quyền sở hữu của Lifecycle từ các lớp riêng lẻ, chẳng hạn như FragmentAppCompatActivity, đồng thời cho phép ghi các thành phần hoạt động cùng với các lớp đó. Mọi lớp đăng ký tuỳ chỉnh đều có thể triển khai giao diện LifecycleOwner.

Các thành phần triển khai DefaultLifecycleObserver sẽ hoạt động liền mạch với các thành phần triển khai LifecycleOwner vì chủ sở hữu có thể cung cấp vòng đời mà trình quan sát có thể đăng ký để xem.

Đối với ví dụ về tính năng theo dõi vị trí, chúng ta có thể khiến lớp MyLocationListener triển khai DefaultLifecycleObserver và sau đó khởi chạy phương thức này bằng Lifecycle của hoạt động trong phương thức onCreate(). Điều này cho phép lớp MyLocationListener tự cung cấp thông tin, nghĩa là logic để phản ứng với các thay đổi trong trạng thái vòng đời được khai báo trong MyLocationListener thay vì trong hoạt động. Việc có các thành phần riêng lẻ tự lưu trữ logic giúp quản lý các logic trong hoạt động và trong mảnh dễ dàng hơn.

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this, lifecycle) { location ->
            // update UI
        }
        Util.checkUserStatus { result ->
            if (result) {
                myLocationListener.enable()
            }
        }
    }
}

Java

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
            // update UI
        });
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.enable();
            }
        });
  }
}

Một trường hợp sử dụng phổ biến là để tránh gọi ra một số lệnh gọi lại nếu Lifecycle đang ở trạng thái không tốt. Ví dụ, nếu lệnh gọi lại chạy một giao dịch mảnh sau khi lưu trạng thái hoạt động, lệnh gọi lại sẽ gây ra sự cố. Do đó, chúng ta sẽ không bao giờ muốn gọi lệnh gọi lại đó.

Để trường hợp sử dụng này trở nên dễ dàng, lớp Lifecycle sẽ cho phép các đối tượng khác truy vấn trạng thái hiện tại.

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val lifecycle: Lifecycle,
        private val callback: (Location) -> Unit
): DefaultLifecycleObserver {

    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            // connect
        }
    }

    fun enable() {
        enabled = true
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            // connect if not connected
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        // disconnect if connected
    }
}

Java

class MyLocationListener implements DefaultLifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @Override
    public void onStart(LifecycleOwner owner) {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @Override
    public void onStop(LifecycleOwner owner) {
        // disconnect if connected
    }
}

Với cách triển khai này, lớp LocationListener của chúng ta sẽ hoàn toàn nhận biết được vòng đời. Nếu cần sử dụng LocationListener từ một hoạt động hoặc mảnh khác, chúng ta chỉ cần khởi chạy phương thức đó. Tất cả các thao tác thiết lập và chia nhỏ đều do lớp này tự quản lý.

Nếu thư viện cung cấp các lớp cần làm việc với vòng đời của Android, bạn nên sử dụng các thành phần nhận biết được vòng đời. Người dùng thư viện của bạn có thể dễ dàng tích hợp các thành phần đó mà không cần quản lý vòng đời thủ công ở phía máy khách.

Triển khai một LifecycleOwner tuỳ chỉnh

Các lớp Fragment và Activity trong Thư viện hỗ trợ 26.1.0 trở lên đã triển khai giao diện LifecycleOwner.

Nếu có một lớp tuỳ chỉnh mà bạn muốn tạo LifecycleOwner, bạn có thể sử dụng lớp LifecycleRegistry, nhưng cần chuyển tiếp các sự kiện vào lớp đó, như trong ví dụ sau:

Kotlin

class MyActivity : Activity(), LifecycleOwner {

    private lateinit var lifecycleRegistry: LifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }

    public override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class MyActivity extends Activity implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    @Override
    public void onStart() {
        super.onStart();
        lifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

Phương pháp tốt nhất cho thành phần nhận biết vòng đời

  • Hãy giữ cho bộ điều khiển giao diện người dùng (các hoạt động và mảnh) tinh gọn nhất có thể. Các lớp này không được thu thập dữ liệu của chính mình; thay vì vậy, hãy sử dụng ViewModel để làm việc đó và quan sát đối tượng LiveData để phản ánh các thay đổi trở lại đối với chế độ xem.
  • Hãy viết giao diện người dùng theo hướng dữ liệu, trong đó trách nhiệm của bộ điều khiển giao diện người dùng là cập nhật chế độ xem dưới dạng các thay đổi về dữ liệu, hoặc thông báo cho người dùng về ViewModel.
  • Đưa logic dữ liệu vào lớp ViewModel. ViewModel sẽ đóng vai trò là trình kết nối giữa bộ điều khiển giao diện người dùng và phần còn lại của ứng dụng. Tuy nhiên, hãy thận trọng vì ViewModel không có trách nhiệm tìm nạp dữ liệu (ví dụ như từ mạng). Thay vào đó, ViewModel phải gọi thành phần thích hợp để tìm nạp dữ liệu, sau đó cung cấp lại kết quả cho bộ điều khiển giao diện người dùng.
  • Sử dụng Liên kết dữ liệu để duy trì một giao diện tinh gọn giữa các chế độ xem và bộ điều khiển giao diện người dùng. Điều này giúp các chế độ xem của bạn rõ ràng hơn và giảm bớt mã cập nhật cần viết cho các hoạt động và mảnh. Nếu bạn muốn làm việc này bằng ngôn ngữ lập trình Java, hãy sử dụng một thư viện như Butter Knife để tránh mã nguyên mẫu và có yếu tố trừu tượng tốt hơn.
  • Nếu giao diện người dùng của bạn phức tạp, hãy tạo một lớp trình thể hiện để xử lý các thao tác sửa đổi giao diện người dùng. Đây có thể là một tác vụ tốn nhiều công sức nhưng có thể giúp bạn dễ dàng kiểm thử các thành phần giao diện người dùng hơn.
  • Tránh tham chiếu ngữ cảnh View hoặc Activity trong ViewModel. Nếu ViewModel diễn ra lâu hơn hoạt động (trong trường hợp cấu hình thay đổi), hoạt động của bạn sẽ bị rò rỉ và sẽ không được trình thu gom rác xử lý đúng cách.
  • Sử dụng các coroutine Kotlin để quản lý các tác vụ chạy trong thời gian dài và các thao tác khác có thể chạy không đồng bộ.

Trường hợp sử dụng thành phần nhận biết vòng đời

Các thành phần biết vòng đời có thể giúp bạn quản lý vòng đời dễ dàng hơn trong nhiều trường hợp. Một số ví dụ như:

  • Chuyển đổi giữa cập nhật vị trí tương đối và chi tiết. Sử dụng các thành phần nhận biết chu kỳ để cho phép cập nhật vị trí chi tiết trong khi ứng dụng vị trí của bạn hiển thị và chuyển sang cập nhật tương đối khi ứng dụng chạy dưới nền. LiveData là một thành phần nhận biết được vòng đời cho phép ứng dụng của bạn tự động cập nhật giao diện người dùng khi người dùng thay đổi vị trí.
  • Dừng và bắt đầu lưu video vào bộ đệm. Sử dụng các thành phần nhận biết được vòng đời để bắt đầu tải video vào bộ đệm sớm nhất có thể, nhưng sẽ tạm hoãn phát lại cho đến khi ứng dụng khởi động hoàn toàn. Bạn cũng có thể sử dụng các thành phần nhận biết được vòng đời để chấm dứt hoạt động lưu vào bộ đệm khi ứng dụng của bạn bị huỷ.
  • Khởi động và dừng kết nối mạng. Sử dụng các thành phần nhận biết được vòng đời để bật tính năng cập nhật trực tiếp (phát trực tuyến) dữ liệu mạng khi một ứng dụng đang chạy ở nền trước và cũng để tự động tạm dừng khi ứng dụng chuyển sang chế độ nền.
  • Tạm dừng và tiếp tục ảnh động có thể vẽ. Sử dụng các thành phần nhận biết được vòng đời để xử lý việc tạm dừng các ảnh động có thể vẽ khi ứng dụng ở chế độ nền và tiếp tục sau khi ứng dụng chuyển sang chạy trên nền trước.

Xử lý các sự kiện ngừng hoạt động

Khi một Lifecycle thuộc về AppCompatActivity hoặc Fragment, trạng thái của Lifecycle sẽ thay đổi thành CREATED và sự kiện ON_STOP sẽ được gửi đi khi AppCompatActivity hoặc onSaveInstanceState() của Fragment được gọi.

Khi trạng thái của một Fragment hoặc AppCompatActivity được lưu qua onSaveInstanceState(), giao diện người dùng của lớp này được coi là không thể thay đổi cho đến khi ON_START được gọi. Việc cố gắng sửa đổi giao diện người dùng sau khi lưu trạng thái có thể gây ra sự không đồng nhất trong trạng thái điều hướng cho ứng dụng. Đó là lý do FragmentManager sẽ ném một trường hợp ngoại lệ nếu ứng dụng chạy FragmentTransaction sau khi trạng thái được lưu. Hãy xem commit() để biết chi tiết.

LiveData ngăn trường hợp đặc biệt này xuất hiện bằng cách hạn chế gọi cho trình quan sát của lớp này nếu Lifecycle liên kết của trình quan sát không ở trạng thái ít nhất là STARTED. Ở hậu trường, lớp này gọi isAtLeast() trước khi quyết định gọi trình quan sát.

Tiếc là phương thức onStop() của AppCompatActivity được gọi sau onSaveInstanceState(), tạo ra một khoảng trống trong đó không cho phép sự thay đổi trạng thái giao diện người dùng, nhưng Lifecycle lại chưa được chuyển sang trạng thái CREATED.

Để ngăn vấn đề này, lớp Lifecycle trong phiên bản beta2 trở xuống đánh dấu trạng thái này là CREATED mà không cần gửi sự kiện, nhờ đó mọi mã kiểm tra trạng thái hiện tại đều nhận được giá trị thực, ngay cả khi sự kiện không được gửi đi cho đến khi hệ thống gọi onStop().

Không may giải pháp này có hai vấn đề lớn:

  • Ở API cấp 23 trở xuống, hệ thống Android thực sự sẽ lưu trạng thái của một hoạt động ngay cả khi hoạt động đó có một phần thuộc vào phạm vi của hoạt động khác. Nói cách khác, hệ thống Android gọi onSaveInstanceState()nhưng không nhất thiết phải gọi onStop(). Điều này sẽ tạo ra một khoảng thời gian dài tiềm ẩn, trong đó trình quan sát vẫn cho rằng vòng đời đang hoạt động dù không thể sửa đổi trạng thái giao diện người dùng.
  • Bất kỳ lớp nào muốn hiển thị hành vi tương tự hành vi của lớp LiveData phải triển khai giải pháp do Lifecycle phiên bảnbeta 2 trở xuống cung cấp.

Tài nguyên khác

Để tìm hiểu thêm về cách xử lý vòng đời của các thành phần nhận biết được vòng đời, hãy tham khảo các tài nguyên bổ sung sau.

Mẫu

  • Sunflower, một ứng dụng minh hoạ giới thiệu các phương pháp tốt nhất đối với Thành phần cấu trúc

Lớp học lập trình

Blog