DEV Community

Cover image for Android Session Tracking — A Senior Engineer’s Perspective: When the OS Promises You Nothing
ViO Tech
ViO Tech

Posted on

Android Session Tracking — A Senior Engineer’s Perspective: When the OS Promises You Nothing

Android makes no promise about telling you when a user leaves your app.
Yet analytics, ads, and business logic are forced to assume such a thing exists.

This article is not about tricks. It is about thinking correctly about session tracking on Android — at production and SDK level.


1. The real problem is not APIs — it’s illusion

Most Android developers have believed at least one of these at some point:

  • onStop() means the user left
  • onDestroy() means the app ended
  • Swiping from Recents ends a session

All of these are fundamentally wrong.

Android does not run apps for you.
Android runs itself. Your app is a guest.


2. OS truths senior engineers must accept

  • Processes can be killed without callbacks
  • SIGKILL / LMK offer zero cleanup guarantees
  • Lifecycle events only exist while the process is alive

In other words:

If you are waiting for a callback to detect session end,
you already lost — conceptually.


3. How real SDKs actually work

Firebase, AdMob, Adjust, AppsFlyer do not “detect kills”.

They do three things:

  1. Track process foreground / background
  2. Apply timeout heuristics
  3. Infer session end on the next launch

No events. No guarantees. Only probabilistic modeling.

This is senior thinking: accept uncertainty and design around it.


4. A session is not an event — it’s a state machine

A real session has states:

  • Created
  • Active
  • Background
  • Expired
  • Restored (inferred)

A session does not end when the app dies.
It ends when we decide it is no longer valid.

That decision is based on:

  • Background duration
  • Next launch timing
  • Persisted metadata

5. ProcessLifecycleOwner: the right tool, not a magic one

ProcessLifecycleOwner gives you:

  • ON_START: process enters foreground
  • ON_STOP: process goes to background

It does not give you:

  • App kill
  • Swipe from Recents
  • Native crashes

And it shouldn’t.

Senior engineers do not demand APIs do the impossible.


6. Timeout is not a workaround — it’s a definition

Timeout is not a hack.

Timeout is a business rule:

“If the user stays away longer than X ms, the session is considered ended.”

30 seconds? 1 minute? 5 minutes?

There is no correct value — only product-appropriate ones.


7. App kill: no callback, only traces

When the app is killed:

  • No onSessionEnd
  • No cleanup
  • No flush

What remains:

  • Last background timestamp
  • Session ID
  • Inferred reason

On the next launch, the SDK must:

  • Load persisted state
  • Infer the previous session ended
  • Emit a logical session end

This is a two-phase design — familiar to senior engineers.


8. Exit reasons: be honest, not confident

Exit reasons should be modeled as:

  • USER_BACKGROUND_TIMEOUT
  • PROCESS_KILLED_INFERRED
  • APP_UPGRADE
  • CRASH_DETECTED

The key word is inferred.

Good SDKs do not say “the user did X”.
They say “based on evidence, we infer X”.


9. Why this module is testable — and why that matters

If session tracking:

  • depends on Activities
  • relies on system callbacks
  • depends on real time

→ it is not testable, and therefore not trustworthy.

A proper SessionTracker:

  • is pure logic
  • injects its clock
  • fakes lifecycle signals

Testable means understandable.
Understandable means reliable.


10. How to use the Session Tracking SDK

The theory matters. Now the practice.

This SDK is designed to:

  • avoid Activity dependencies
  • never crash on process kill
  • never block the main thread

10.1 Initialize in Application

The SDK must be initialized in Application.onCreate().

class App : Application() {

    private lateinit var sessionObserver: AndroidSessionObserver

    override fun onCreate() {
        super.onCreate()

        sessionObserver = AndroidSessionObserver(
            context = this,
            timeoutMs = 30_000L,
            callback = object : SessionTracker.Callback {
                override fun onSessionStart(session: Session) {
                    // analytics / ads init
                }

                override fun onSessionEnd(
                    session: Session,
                    reason: ExitReason
                ) {
                    // flush analytics / revenue sync
                }
            }
        )

        ProcessLifecycleOwner.get()
            .lifecycle
            .addObserver(sessionObserver)
    }
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • No Activity lifecycle usage
  • No UI references
  • Callbacks may never fire if the app is killed

The SDK is designed with this assumption.


10.2 What happens when the app is killed?

No callback is invoked.

Instead, the SDK:

  • persists session state on background
  • stores timestamp and session ID

On the next launch, it:

  • restores previous state
  • infers the session ended
  • emits ExitReason.PROCESS_KILLED_INFERRED

This behavior is intentional.


10.3 Interpreting ExitReason correctly

Exit reasons are not absolute truth.

Examples:

  • USER_BACKGROUND_TIMEOUT → time-based evidence
  • PROCESS_KILLED_INFERRED → missing resume inference

Analytics and backend systems must treat these as inferred data.

The SDK does not hide this.


10.4 Unit testing: why you should trust this SDK

All session logic:

  • is framework-independent
  • injects time
  • is fully testable

Example:

@Test
fun `session ends after timeout`() {
    val fakeClock = FakeClock()
    val tracker = SessionTracker(fakeClock, timeoutMs = 30_000)

    tracker.onForeground()
    fakeClock.advance(31_000)
    tracker.onForeground()

    assertTrue(tracker.lastExitReason is ExitReason.Timeout)
}
Enter fullscreen mode Exit fullscreen mode

Testable logic produces deterministic behavior.


11. Full source code

The complete SDK, including:

  • Pure SessionTracker logic
  • AndroidSessionObserver
  • ExitReason model
  • Unit tests

is available on GitHub:

🔗 https://github.com/vinhvox/ViO---Android-Session-Tracker

This repository is designed to:

  • read like documentation
  • be copy-paste friendly
  • and, most importantly, not pretend Android is controllable

12. Final words for senior engineers

Session tracking on Android is never perfect.

Good SDKs do not hide that.
They:

  • state their limits clearly
  • model uncertainty explicitly
  • help the business make correct, not just pretty, decisions

Android does not give you absolute truth.
But it gives enough signals to infer — if you design correctly.

Top comments (0)