Android Lifecycle

Android ProcessLifecycleOwner by example

Alex Zhukovich 4 min read
Android ProcessLifecycleOwner by example
Table of Contents
✏️
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.

Interaction between application and analytics

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()
    }
}

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.


Mobile development with Alex

A blog about Android development & testing, Best Practices, Tips and Tricks

Share

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Mobile development with Alex.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.