Android ProcessLifecycleOwner by example

Alex Zhukovich
ProAndroidDev
Published in
4 min readAug 20, 2019

--

This article was updated on 2 July 2023. The original article was published on 19 August 2019.

Nowadays, users have many installed applications. It means that users often interact with many applications during a short amount of time. For example, when the user interacts with an application, receives a notification, then goes to another app.

Different states of the application

However, as being developers, we would like to know when our application moved to the background. It’s a very popular use case for analytics. Different analytics solutions define sessions in different ways, some solutions allow developers to define “session” meaning. Let’s create a simple analytics abstraction which informs us about the duration of the session, which will be calculated in the following way: when we start the application, the session is also started and when the application moves to the background, our session is finished. Finally, we would like to display this information in logs.

The ProcessLifecycleOwner can help us get information about changing the state of the application.

So, let’s start with adding dependencies for lifecycle framework:

dependencies {
...

implementation 'androidx.lifecycle:lifecycle-process:2.6.1'
}

Let’s start with creating an ApplicationObserver class which helps us to handle the changing state of the application and configure our analytics solution in a proper way. So we want to know when our application moved to the background and to the foreground. We can use lifecycle event for it:

  • when application moved to background ON_STOP event will be triggered;
  • when application moved to foreground ON_START event will be triggered.

We can create an ApplicationObserver class which simplifies defining sessions for the analytics.

class ApplicationObserver(val analytics: Analytics) : DefaultLifecycleObserver {

override fun onStart(owner: LifecycleOwner) {

}

override fun onStop(owner: LifecycleOwner) {

}
}

The next step is attaching the ApplicationObserver to our application. We should register this observer in the application class. In my case, it's a MapNotesApp class. So, we should register it inside onCreate method. We can use the following code for initialization of ProcessLifecycleOwner and registering ApplicationObserver component.

class MapNotesApp : Application() {
override fun onCreate() {
super.onCreate()
...

val analytics = Analytics()
analytics.addReporter(LogReporter())

ProcessLifecycleOwner
.get()
.lifecycle
.addObserver(ApplicationObserver(analytics))
}
...
}

At the moment we can move to creating an abstraction for the analytics. I suggest to create a simple solution which can be extended, but even this implementation will be enough for demo purpose.

The Analytics class allows us to collect session information.

class Analytics {
private var startSessionTimestamp: Long = -1
private val reporters = mutableListOf<AnalyticsReporter>()

fun addReporter(reporter: AnalyticsReporter) {
reporters.add(reporter)
}

fun startSession() {
startSessionTimestamp = Date().time
}

fun stopSession() {
reportSession()
sendAllEvents()
startSessionTimestamp = -1
}

private fun reportSession() {
reporters.forEach {reporter ->
val currentTime = Date().time
// we should check if session was started and stopped correctly
val sessionTime = (currentTime - startSessionTimestamp) / 1000
reporter.report("Session time: $sessionTime sec" )
}
}

private fun sendAllEvents() {
reporters.forEach {reporter ->
reporter.sendAllEvents()
}
}
}

The AnalyticsReporter interface is an abstraction for all reporters, like LogReporter.

interface AnalyticsReporter {

fun report(event: String)

fun sendAllEvents()
}

The LogReposter is implementation for the AnalyticsReporter interface. It displays all logs in the console.

class LogReporter : AnalyticsReporter {
private val events = mutableListOf<String>()
override fun report(event: String) {
events.add(event)
}
override fun sendAllEvents() {
events.forEach { event ->
Log.d(this.javaClass.simpleName, event)
}
events.clear()
}
}

The LogReposter is implementation for the AnalyticsReporter interface. It displays all logs in the console.

class LogReporter : AnalyticsReporter {
private val events = mutableListOf<String>()

override fun report(event: String) {
events.add(event)
}

override fun sendAllEvents() {
events.forEach { event ->
Log.d(this.javaClass.simpleName, event)
}
events.clear()
}
}

Finally, we can update the ApplicationObserver class for starting and stopping the session.

class ApplicationObserver(val analytics: Analytics) : DefaultLifecycleObserver {

override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
analytics.startSession()
}

override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
analytics.stopSession()
}
}

Afterwards, we can run our application and check the log. When the application moves to the foreground, our session starts, but when the application moves to the background, we finish our session with small delay and display this information in logs.

com.alex.mapnotes D/LogAnalyticsReporter: Session time: 28 sec

The main reason for this delay is the internal implementation of ProcessLifecycleOwner and we will talk about it in a moment.

So, let’s move between different screens because the duration of our session is independent on the current activity. As you can see, we can easily handle case when the application moves from foreground and background and vice versa. Analytics it just one of the example when we need this information.

Let’s check how ProcessLifecycleOwner works under the hood. Everything starts when we add the "lifecycle-process" dependency to the project. After building it, the ProcessLifecycleInitializer will be added to AndroidManifest.xml as a provider which allows the framework to collect the information about changing states of the activities. The main helper here is ReportFragment, which sends information about changes in the lifecycle of each activity to LifecycleRegistry class. That is a hub for collecting information about the lifecycle and it can notify an interested observer about changes in the lifecycle of LifecycleOwners. However, the framework has one trick that is a delay and it is used for verification that no new activity was created, and this delay is equal to 700 milliseconds.

An implementation can be found at Github and it was originally posted here.

If you have any questions please reach out on Twitter or leave a comment.

--

--