Android Architecture Components - Xử lý Lifecycles với Lifecycle-Aware Components
Bài đăng này đã không được cập nhật trong 3 năm
Tiếp theo loạt bài về Android Architecture Components, lần này chúng ta sẽ cùng tìm hiểu về Xử lý Lifecycles với Lifecycle-Aware Components.
Các Lifecycle-Aware Components thực hiện hành động để đáp ứng sự thay đổi tình trạng vòng đời của một thành phần khác, chẳng hạn như các Activity và Fragment. Các thành phần này giúp bạn tạo mã có tổ chức tốt hơn và thường nhẹ hơn, dễ bảo trì hơn.
Một mô hình phổ biến là thực hiện các hành động của các thành phần phụ thuộc trong các phương pháp vòng đời của Activity và Fragment. Tuy nhiên, mô hình này dẫn đến việc tổ chức mã kém và sự gia tăng của các lỗi. Bằng cách sử dụng các Lifecycle-Aware Components, 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 pháp vòng đời và vào các thành phần.
Package android.arch.lifecycle cung cấp các class và interface cho phép bạn tạo các thành phần nhận biết vòng đời - là các thành phần có thể tự động điều chỉnh hành vi của chúng dựa trên trạng thái vòng đời hiện tại của một Activity hoặc Fragment.
Hầu hết các thành phần ứng dụng được định nghĩa trong Android Framework có vòng đời gắn liền với chúng. Vòng đời được quản lý bởi hệ điều hành hoặc mã đang chạy trong process của bạn. Chúng là cốt lõi của cách Android hoạt động và ứng dụng của bạn phải tôn trọng chúng. Không làm như vậy có thể gây rò rỉ bộ nhớ hoặc thậm chí crash ứng dụng.
Hãy tưởng tượng chúng ta có một Activity cho thấy vị trí thiết bị trên màn hình. Thực hiện chung có thể như sau:
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ẫu này có vẻ tốt, trong một ứng dụng thực, bạn sẽ gặp phải quá nhiều cuộc 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 lại trạng thái hiện tại của vòng đời. Quản lý nhiều thành phần đặt một lượng đáng kể mã trong các phương pháp vòng đời, chẳng hạn như onStart() và onStop() , điều này làm cho chúng khó duy trì. (Nói vui 1 chút, code của google mà lại gọi super.onStop() trước khi gọi listener.stop() kìa)
Hơn nữa, không có đảm bảo rằng các thành phần bắt đầu trước khi Activity và Fragment dừng lại. Điều này đặc biệt đúng nếu chúng ta cần thực hiện một xử lý dài, chẳng hạn như kiểm tra cấu hình trong onStart() . Điều này có thể gây ra một điều kiện hiếm nơi phương thức onStop() kết thúc trước khi onStart() , giữ các thành phần sống lâu hơn nó là cần thiết.
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();
}
}
Package android.arch.lifecycle cung cấp các class và interface giúp bạn giải quyết những vấn đề này một cách linh hoạt và cách biệt.
Lifecycle
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ư Activity hoặc Fragment) 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 bảng liệt kê chính để theo dõi trạng thái vòng đời cho thành phần liên quan của nó:
Event
- Các sự kiện vòng đời được gửi từ khuôn khổ và lớp Lifecycle . Các sự kiện này ánh xạ tới các sự kiện gọi lại trong các hoạt động và các đoạn.
State
- Trạng thái hiện tại của thành phần được theo dõi bởi đối tượng Lifecycle .
Hãy nghĩ các State như các nút của một đồ thị và các sự kiện như các cạnh giữa các nút này.
Một class có thể giám sát tình trạng vòng đời của thành phần bằng cách thêm các chú thích vào các phương thức của nó. Sau đó, bạn có thể thêm observer bằng cách gọi phương thức addObserver() của lớp Lifecycle và truyền một thể hiện của observer của bạn, như thể hiện trong ví dụ sau:
public class MyObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void connectListener() {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void disconnectListener() {
...
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());
Trong ví dụ trên, đối tượng myLifecycleOwner thực hiện giao diện LifecycleOwner , được giải thích trong phần sau.
LifecycleOwner
LifecycleOwner là một giao diện duy nhất cho thấy rằng class có Lifecycle . Nó có một phương pháp, getLifecycle() , mà phải được thực hiện bởi lớp. Nếu bạn đang cố gắng quản lý toàn bộ vòng đời của một ứng dụng, xem ProcessLifecycleOwner .
Giao diện này tóm tắt quyền sở hữu của Lifecycle từ các class riêng lẻ, chẳng hạn như Fragment và AppCompatActivity , và cho phép viết các thành phần hoạt động với chúng. Bất kỳ lớp ứng dụng tùy chỉnh nào cũng có thể thực hiện giao diện LifecycleOwner .
Các thành phần thực hiện LifecycleObserver hoạt động liên tục với các thành phần triển khai LifecycleOwner bởi vì một chủ sở hữu có thể cung cấp một vòng đời, mà một observer có thể đăng ký để xem.
Đối với ví dụ về theo dõi vị trí, chúng ta có thể làm cho lớp MyLocationListener thực hiện LifecycleObserver và sau đó khởi tạo nó bằng Lifecycle của Activity trong phương thức onCreate() . Điều này cho phép lớp MyLocationListener tự cung cấp, có nghĩa là logic để phản ứng với những thay đổi trong tình trạng vòng đời được khai báo trong MyLocationListener thay vì Activity. Có các thành phần riêng biệt lưu trữ logic riêng của chúng làm cho logic của các Activity và Fragment dễ dàng hơn để quản lý.
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();
}
});
}
}
Trường hợp sử dụng phổ biến là để tránh gọi các callback nào đó nếu Lifecycle không ở trong trạng thái tốt ngay bây giờ. Ví dụ, nếu callback gọi một fragment transaction sau khi trạng thái Activity được lưu, nó sẽ gây ra crash, vì vậy chúng ta sẽ không bao giờ muốn callback đó gọi xử lý.
Để làm cho trường hợp sử dụng này dễ dàng, lớp Lifecycle cho phép các đối tượng khác truy vấn trạng thái hiện tại.
class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}
public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}
Với việc triển khai này, lớp LocationListener của chúng ta hoàn toàn nhận biết vòng đời. Nếu chúng ta cần sử dụng LocationListener của chúng ta từ một Activity hoặc Fragment khác, chúng ta chỉ cần khởi tạo nó. Tất cả các hoạt động thiết lập và xử lý được quản lý bởi chính class đó.
Nếu thư viện cung cấp các class cần làm việc với vòng đời của Android, chúng tôi khuyên bạn nên sử dụng các Lifecycle-Aware Components. Thư viện của bạn có thể dễ dàng tích hợp các thành phần này mà không cần quản lý vòng đời thủ công ở phía client.
Thực hiện một LifecycleOwner tùy chỉnh
Các Fragment và Activity trong Support lib 26.1.0 trở đi đã triển khai giao diện LifecycleOwner .
Nếu bạn có một lớp tuỳ chỉnh mà bạn muốn tạo ra một LifecycleOwner , bạn có thể sử dụng lớp LifecycleRegistry , nhưng bạn cần chuyển các sự kiện vào lớp đó như thể hiện trong ví dụ sau:
public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
public void onStart() {
super.onStart();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}
@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}
Best practice cho các thành phần nhận thức vòng đời
- Giữ UI controller của bạn (các Activity và Fragment) càng gọn gàng càng tốt. Chúng không nên cố gắng thu thập dữ liệu của chính mình; thay vào đó, sử dụng một ViewModel để thực hiện điều đó, và quan sát một đối tượng LiveData để phản ánh những thay đổi về các View.
- Hãy thử viết UI được điều khiển dữ liệu, nơi trách nhiệm điều khiển UI của bạn là cập nhật View khi dữ liệu thay đổi hoặc thông báo cho hành động của người dùng tới ViewModel .
- Đặt logic dữ liệu của bạn trong lớp ViewModel của bạn. ViewModel phục vụ việc kết nối giữa trình điều khiển giao diện người dùng của bạn và phần còn lại của ứng dụng. Hãy cẩn thận mặc dù, nó không phải là trách nhiệm của ViewModel để lấy dữ liệu (ví dụ, từ mạng). Thay vào đó, ViewModel nên gọi thành phần thích hợp để lấy dữ liệu, sau đó đưa kết quả về bộ điều khiển UI.
- Sử dụng Bind dữ liệu để duy trì một giao diện trong suốt giữa View của bạn và bộ điều khiển UI. Điều này cho phép bạn làm cho View của bạn có tính khai báo và giảm thiểu mã cập nhật bạn cần viết trong các Activity và Fragment của bạn. Nếu bạn thích làm điều này trong ngôn ngữ lập trình Java, sử dụng một thư viện như ButterKnife để có một sự 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 xem xét tạo một lớp presenter để xử lý sửa đổi UI. Đây có thể là một công việc tốn kém, nhưng nó có thể làm cho các thành phần UI của bạn dễ dàng kiểm tra.
- Tránh tham chiếu View hoặc Activity trong ViewModel của bạn. Nếu ViewModel của bạn vẫn sống bên ngoài Activity (trong trường hợp thay đổi cấu hình), Activity của bạn sẽ bị leak và không được xử lý đúng cách bởi GC.
Trường hợp sử dụng cho các thành phần nhận biết vòng đời
Các thành phần nhận biết vòng đời có thể làm cho bạn dễ dàng hơn trong việc quản lý vòng đời trong nhiều trường hợp. Một vài ví dụ là:
- Chuyển đổi giữa coarse và fine location update. Sử dụng các thành phần nhận biết vòng đời để cho phép cập nhật vị trí fine trong khi ứng dụng vị trí của bạn đang ở foreground và chuyển sang coarse khi ứng dụng ẩn. LiveData , một thành phần nhận biết vòng đời, cho phép ứng dụng của bạn tự động cập nhật UI khi bạn sử dụng thay đổi vị trí.
- Ngừng và bắt đầu buffering video. Sử dụng các thành phần nhận biết vòng đời để bắt đầu buffering video càng sớm càng tốt, nhưng trì hoãn playback cho đến khi ứng dụng được bắt đầu. Bạn cũng có thể sử dụng các thành phần nhận biết vòng đời để chấm dứt việc lưu trữ khi ứng dụng của bạn bị destroy.
- Bắt đầu và ngừng kết nối mạng. Sử dụng các thành phần nhận biết vòng đời để cho phép cập nhật trực tuyến (luồng) dữ liệu mạng trong khi một ứng dụng đang ở foreground và cũng để tự động tạm dừng khi ứng dụng đi vào background.
- Tạm dừng và tiếp tục các animated drawables. Sử dụng các thành phần nhận biết vòng đời để xử lý các animated drawables tạm dừng khi ứng dụng ở bên dưới background và tiếp tục vẽ sau khi ứng dụng đang ở foreground.
Xử lý sự kiện onStop
Khi Lifecycle thuộc về một AppCompatActivity hoặc Fragment , trạng thái của Lifecycle thay đổi thành CREATED và sự kiện ONSTOP được gửi đi khi AppCompatActivity hoặc Fragment được gọi onSaveInstanceState().
Khi một trạng thái Fragment hoặc AppCompatActivity được lưu thông qua onSaveInstanceState(), UI của nó được coi là không thay đổi cho đến khi được gọi ONSTART . Cố gắng sửa đổi giao diện người dùng sau khi state được lưu có thể gây ra sự không nhất quán trong trạng thái điều hướng của ứng dụng của bạn, đó là lý do tại sao FragmentManager ném Exception nếu ứng dụng chạy một FragmentTransaction sau khi state được lưu lại. Xem commit() để biết chi tiết.
LiveData ngăn trường hợp này bằng cách kiềm chế không gọi observer của nó nếu Lifecycle của observer không ở trạng thái ít nhất là STARTED . Nó gọi isAtLeast() trước khi quyết định gọi observer của nó.
Thật không may, phương thức onStop() được gọi sau onSaveInstanceState() , để lại một khoảng trống nơi không thay đổi trạng thái giao diện người dùng nhưng Lifecycle vẫn chưa được di chuyển đến trạng thái CREATED .
Để ngăn chặn vấn đề này, lớp Lifecycle trong phiên bản beta2 và thấp hơn đánh dấu trạng thái là CREATED mà không gửi sự kiện để bất kỳ mã nào kiểm tra trạng thái hiện tại nhận được giá trị thực ngay cả khi sự kiện không được gửi cho đến khi onStop() được gọi bởi hệ thống.
Nhưng, giải pháp này có hai vấn đề lớn:
- API 23 trở xuống, hệ thống Android thực sự tiết kiệm trạng thái của một hoạt động ngay cả khi nó được bao phủ một phần bởi một hoạt động khác. Nói cách khác, hệ thống Android sẽ gọi onSaveInstanceState() nhưng nó không nhất thiết phải gọi onStop() . Điều này tạo ra khoảng thời gian dài tiềm ẩn nơi observer vẫn nghĩ rằng vòng đời đang hoạt động mặc dù trạng thái Giao diện người dùng của nó không thể sửa đổi được.
- Bất kỳ lớp nào muốn phơi bày một hành vi tương tự như lớp LiveData phải thực hiện giải pháp tạm thời được cung cấp bởi phiên bản Phiên bản beta 2 và thấp hơn.
Lưu ý: Để làm cho luồng này trở nên đơn giản và cung cấp khả năng tương thích tốt hơn với các phiên bản cũ hơn, bắt đầu từ phiên bản 1.0.0-rc1 , các đối tượng Lifecycle được đánh dấu là CREATED và ON_STOP được gửi đi khi onSaveInstanceState() được gọi mà không cần chờ một cuộc gọi tới onStop(). Điều này không ảnh hưởng đến mã của bạn nhưng đó là điều bạn cần biết vì nó không phù hợp với lệnh gọi trong lớp Activity ở cấp độ API 26 trở xuống.
Nguồn https://developer.android.com/topic/libraries/architecture/lifecycle.html
All rights reserved