Problems arising from the coupling of business logic and life cycle
The "component" mentioned here refers to those classes that contain the life cycle in the Android SDK API, such as Activity, Service, Fragment, etc.
A component has its life cycle, such as onCreate, onStop, onStart, etc. of an Activity
When we write components, many operations often depend on these life cycles of components. For example, we need to turn on location monitoring when Activity onStart and turn off location monitoring when onStop. We are likely to write such code:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // ... locationListener = MyLocationListener(this) { // do something else... } } override fun onStart() { super.onStart() locationListener.start() } override fun onStop() { super.onStop() locationListener.stop() } }
There are three problems with writing such code
The first is that if the business complexity increases, there will soon be too much life-cycle dependent code in components to control. According to the separation of concerns principle, this code should be moved elsewhere.
override fun onStart() { super.onStart() locationListener.start() xxxListener.start() yyyListener.start() zzzListener.start() } override fun onStop() { super.onStop() locationListener.stop() xxxListener.stop() yyyListener.stop() zzzListener.stop() } override fun onXXX() { //... } override fun onYYY() { //... }
Second, if the same logic is required in other components, the same code should be written in multiple copies
class FirstActivity : AppCompatActivity() { // ... override fun onStart() { super.onStart() locationListener.start() } override fun onStop() { super.onStop() locationListener.stop() } }
class SecondActivity : AppCompatActivity() { // ... override fun onStart() { super.onStart() locationListener.start() } override fun onStop() { super.onStop() locationListener.stop() } }
The third question is how to ensure that the calling sequence is really called according to the life cycle?
public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // What happens if this callback is called after 'onStop'? if (result) { myLocationListener.start() } } } public override fun onStop() { super.onStop() myLocationListener.stop() }
The above code is likely to cause a memory leak or directly crash the program.
Lifecycle Library
Lifecycle is a library in Jetpack. Its purpose is to solve a series of problems caused by the tight coupling between our business logic and the life cycle of components.
Lifecycle class
The Lifecycle class is used to represent the Lifecycle state of components, and allows other objects to observe this state. There are three common methods:
That is to say, some observers can be added to the Lifecycle. The observers will receive Lifecycle events and can obtain the current events through the getCurrentState method.
We can directly use the lifecycle attribute to obtain this lifecycle object from components with a lifecycle. In Java, we can obtain it through the getLifecycle method.
LifecycleOwner
When we click on the source code of the getLifecycle method above, we find that it is an overloaded method.
This method comes from the lifecycle owner interface
Fragment, AppCompatActivity and other components in Android X have implemented the lifecycle owner interface, so we can directly use the getLifecycle method in it just now.
Official statement: Fragment and Activity in support library 26.1.0 and later have implemented the lifecycle owner interface.
What's the advantage of abstracting such a thing? Great benefits! I'll take a wisp.
What we need before is to decouple the operations and components related to the life cycle in the component. For example, we don't want to see such code in the Activity:
fun onStart() { xxxListener.start() yyyListener.start() zzzListener.start() }
The problem with these codes is that components such as Activity must actively undertake all the work of adjusting other class libraries according to their life cycle. According to the principle of separation of concerns, these operations should be completed by other classes.
The Lifecycle instance held by each Lifecycle owner is exposing the addObserver method, so that other classes in the program can add observers to it, so that other classes can actively observe the changes in the component life cycle.
// It inherits from AppCompatActivity in Android x, so it is born as lifecycle owner class MainActivity : AppCompatActivity() { lateinit var locationListener: MyLocationListener override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Adding a lifecycle observer, ` locationListener 'can now observe and follow the lifecycle of' MainActivity ' lifecycle.addObserver(locationListener) } }
LifecycleObserver
Let's take a look at the last piece in the Lifecycle layout, the observer - Lifecycle observer.
LifecycleObserver is an interface that looks like this:
Yes, it looks like this... There's no way in it, so watch a fart?
Huh? Forget the top note, which says:
Instead of directly using this interface to mark a class as a lifecycle observer, implement defaultlifecycle observer or lifecycle eventobserver to receive notifications of lifecycle events.
There is a method in DefaultLifecycleObserver. It provides callbacks for all life cycle events and does nothing by default.
public interface DefaultLifecycleObserver extends FullLifecycleObserver { @Override default void onCreate(@NonNull LifecycleOwner owner) { } @Override default void onStart(@NonNull LifecycleOwner owner) { } @Override default void onResume(@NonNull LifecycleOwner owner) { } @Override default void onPause(@NonNull LifecycleOwner owner) { } @Override default void onStop(@NonNull LifecycleOwner owner) { } @Override default void onDestroy(@NonNull LifecycleOwner owner) { } }
The lifecycle eventobserver is more direct. It directly provides an onStateChanged method, where all lifecycle events will be accepted
public interface LifecycleEventObserver extends LifecycleObserver { void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event); }
This works well when there is some common code in several lifecycle events.
In short, through these core classes, the Lifecycle library actively undertakes the deployment of components such as previous activities. The behavior of the class library under the specified life cycle is transformed into the class library actively undertakes these responsibilities. In this way, it will decouple and separate the concerns.
Sample code
class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { super.onStart(owner) // do something } override fun onStop(owner: LifecycleOwner) { super.onStop(owner) // do something } }
class MainActivity : AppCompatActivity() { lateinit var locationListener: MyLocationListener override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) locationListener = MyLocationListener(this) { // update ui } lifecycle.addObserver(locationListener) } }
Here we successfully moved the focus from MainActivity to MyLocationListener
Customize lifecycle owner (official example)
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 } }
Best practices for lifecycle aware components (official)
- Keep interface controllers (Activity and Fragment) as lean as possible. Instead of trying to get their own data, they should do so using the ViewModel and observe the LiveData object to reflect the changes in the view.
- Try to write data-driven interfaces. For such interfaces, the responsibility of the interface controller is to update the view as the data changes, or notify the ViewModel of user actions.
- Place the data logic in the ViewModel class. The ViewModel should act as a connector between the interface controller and the rest of the application. Note, however, that ViewModel is not responsible for obtaining data (for example, from the network). However, the ViewModel should call the corresponding components to obtain the data, and then provide the results to the interface controller.
- Use data binding to maintain a clean interface between the view and the interface controller. In this way, you can make the view more declarative and minimize the update code that needs to be written in Activity and Fragment. If you prefer to do this in the Java programming language, use libraries such as butterknife to avoid boilerplate code and achieve better abstraction.
- If the interface is complex, consider creating a presenter class to handle the modification of the interface. This can be a difficult task, but doing so makes the interface components easier to test.
- Avoid referencing the View or Activity context in the ViewModel. If the ViewModel exists longer than the Activity (in case of configuration changes), the Activity will leak and will not be properly disposed of by the garbage collector.
- Use Kotlin collaboration to manage long-running tasks and other operations that can run asynchronously.
Use cases for lifecycle aware components (official)
Lifecycle aware components make it easier for you to manage the lifecycle in a variety of situations. Here are some examples:
- Switch between coarse-grained and fine-grained location updates. Lifecycle aware components enable fine-grained location updates when the location application is visible and switch to coarse-grained updates when the application is in the background. With the life cycle aware component LiveData, applications can automatically update the interface when the user's use location changes.
- Stop and start video buffering. Using life cycle aware components can start video buffering as soon as possible, but playback will be delayed until the application is fully started. In addition, after the application is destroyed, you can use lifecycle aware components to terminate the buffer.
- Start and stop network connections. With the help of life cycle aware components, the real-time update (streaming) of network data can be enabled when the application is in the foreground and automatically suspended when the application enters the background.
- Pause and resume animation to paint assets. With lifecycle aware components, you can pause animation paintable resources when the application is in the background, and restore paintable resources when the application is in the foreground.