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()
và 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.
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ư Fragment
và AppCompatActivity
, đồ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ượngLiveData
để 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ặcActivity
trongViewModel
. NếuViewModel
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ọionStop()
. Đ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 doLifecycle
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
- Mẫu cơ bản về bộ thành phần cấu trúc Android
- 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
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Tổng quan về LiveData
- Sử dụng coroutine của Kotlin với các thành phần nhận biết vòng đời
- Mô-đun trạng thái đã lưu cho ViewModel