Skip to content

Latest commit

 

History

History
502 lines (362 loc) · 15.8 KB

README.md

File metadata and controls

502 lines (362 loc) · 15.8 KB

Measure Android SDK

Requirements

Minimum requirements

Name Version
Android Gradle Plugin 7.4
Min SDK 21 (Lollipop)
Target SDK 31

Self-host compatibility

Before updating to Android SDK version 0.9.0, make sure the deployed self-host version is atleast 0.5.0. For more details, checkout the self-host guide.

SDK version Minimum required self-host version
0.1.0 - 0.8.2 0.1.1
0.9.0 0.5.0

Quick start

Once you have access to the dashboard, create a new app and follow the steps below:

1. Add the API Key & API URL

Copy the API Key and API URL from the dashboard and add it to AndroidManifest.xml file.

<application>
    <meta-data android:name="sh.measure.android.API_KEY" android:value="YOUR_API_KEY" />
    <meta-data android:name="sh.measure.android.API_URL" android:value="API_URL" />
</application>
Configure API Keys for different build types

You can also use manifestPlaceholders to configure different values for different build types or flavors.

In the build.gradle.kts file:

android {
    buildTypes {
        debug {
            manifestPlaceholders["measureApiKey"] = "YOUR_API_KEY"
            manifestPlaceholders["measureUrlKey"] = "API_URL"
        }
        release {
            manifestPlaceholders["measureApiKey"] = "YOUR_API_KEY"
            manifestPlaceholders["measureUrlKey"] = "API_URL"
        }
    }
}

or in the build.gradle file:

android {
    buildTypes {
        debug {
            manifestPlaceholders = ["measureApiKey": "YOUR_API_KEY"]
            manifestPlaceholders = ["measureUrlKey": "API_URL"]
        }
        release {
            manifestPlaceholders = ["measureApiKey": "YOUR_API_KEY"]
            manifestPlaceholders = ["measureUrlKey": "API_URL"]
        }
    }
}

Then add the following in the AndroidManifest.xml file:

<application>
    <meta-data android:name="sh.measure.android.API_KEY" android:value="${measureApiKey}" />
    <meta-data android:name="sh.measure.android.API_URL" android:value="${measureUrlKey}" />
</application>

2. Add the Measure gradle plugin

Add the following plugin to your project.

plugins {
    id("sh.measure.android.gradle") version "0.7.0"
}

or, use the following if you're using build.gradle.

plugins {
    id 'sh.measure.android.gradle' version '0.7.0'
}

Read more about Measure gradle plugin.

Configure variants

By default, the plugin is applied to all variants. To disable plugin for specific variants, use the measure block in your build file.

[!IMPORTANT] Setting enabled to false will disable the plugin for that variant. This prevents the plugin to collect mapping.txt file and other build information about the app. Features like tracking app size, de-obfuscating stack traces, etc. will not work.

For example to disable the plugin for debug variants, add the following to your build.gradle.kts file:

measure {
  variantFilter {
    if (name.contains("debug")) {
      enabled = false
    }
  }
}

or in the build.gradle file:

measure {
  variantFilter {
    if (name.contains("debug")) {
      enabled = false
    }
  }
}

3. Add Measure SDK

Add the following to your app's build.gradle.ktsfile.

implementation("sh.measure:measure-android:0.9.0")

or, add the following to your app's build.gradlefile.

implementation 'sh.measure:measure-android:0.9.0'

4. Initialize the SDK

Add the following to your app's Application class onCreate method.

Important

To be able to detect early crashes and accurate launch time metrics, initialize the SDK as soon as possible in Application onCreate method.

Measure.init(context, MeasureConfig(
        // Set to 1 to track all sessions, useful for debug builds.
        samplingRateForErrorFreeSessions = 1f, 
    )
)

Verify integration

Launch the app and use it. Data is synced to server every 30 seconds or when the app goes to background. To be sure a sync is triggered, try killing and reopening the app.

Launch the dashboard, you must be able to see some data coming in. Checkout the sessions page.

🎉 Congratulations, you have successfully integrated Measure into your app!

Troubleshoot

If you see no data on the dashboard. Here's how to investigate:

Enable logs

Enable logging during SDK initialization. All Meaure SDK logs use the tag Measure.

MeasureConfig(enableLogging = true)

Verify missing configuration

If logs show any of the following errors, then review Step 1: Add API Key & URL.

sh.measure.android.API_URL is missing in the manifest
sh.measure.android.API_KEY is missing in the manifest

Verify sampling rate

Try setting samplingRateForErrorFreeSessions to 1, which would enforce all sessions to be sent to the server. It's typically a good idea to set this to 1 for debug builds.

Verify server connection

If logs contain Failed to send batch or Request failed with unknown error:

  • Verify the API_URL in the AndroidManifest is correct
  • Check server status to ensure it is reachable

In case you face any issue, feel free to reach out to us on Discord.

Manually start or stop the SDK

By default, Measure.init starts collection of events. To delay start to a different point in your app use configuration options. This can be used to control the scope of where Measure is active in your application.

Measure.init(
  context, MeasureConfig(
    // delay starting of collection
    autoStart = false,
  )
)

// Start collecting
Measure.start()

// Stop collecting
Measure.stop()

Important

Some SDK instrumentation remains active even when stopped. This is to maintain state and ensure seamless data collection when it is started. However, no actual data is collected or sent to the server when the SDK is stopped.

Features

Measure SDK operates on an event-based architecture, automatically collecting key debugging events while letting you track custom events, performance traces, screenshots and layout snapshots, etc. Read along for more details.

Automatic collection

The following data is automatically collected by Measure. Read the individual docs for more details.

Identify users

Corelating sessions with users is crutial for debugging certain issues. Measure allows setting a user ID which can then be used to query sessions and events on the dashboard. User Id is persisted across app launches.

Measure.setUserId("user-id")

To clear a user ID.

Measure.clearUserId()

It is recommended to avoid the use of PII (Personally Identifiable Information) in the user ID like email, phone number or any other sensitive information. Instead, use a hashed or anonymized user ID to protect user privacy.

Track custom events

Custom events provide more context on top of automatically collected events. They provide the context specific to the app to debug issues and analyze impact.

To track a custom event use trackEvent method.

Measure.trackEvent("event_name")

A custom event can also contain attributes which are key value paris.

  • Attribute keys must be strings with max length of 256 chars.
  • Attribute values must be one of the primitive types: int, long, double, float or boolean.
  • String attribute values can have a max length of 256 chars.
val attributes = AttributesBuilder()
  .put("is_premium_user", true)
  .build()
Measure.trackEvent("event_name", attributes = attributes)

A custom event can also be triggered with a timestamp to allow tracking events which might have happened before the app or SDK was initialized. The timestamp must be in format milliseconds since epoch.

Measure.trackEvent("event_name", timestamp = 1734443973879L)

Performance tracing

Use the performance tracing APIs to track performance of any part of your application - API calls,

DB queries, any function, user journey, etc. The SDK supports nested spans to track hierarchical operations. Following are some simplified examples:

Example - track a user flow

val onboardingSpan = Measure.startSpan("onboarding-flow")
try {
    val signupSpan = Measure.startSpan("signup", parent = onboardingSpan)
    userSignup()
    signupSpan.end()

    val tutorialSpan = Measure.startSpan("tutorial", parent = onboardingSpan)
    showTutorial()
    tutorialSpan.end(SpanStatus.Ok)
} finally {
    onboardingSpan.end(SpanStatus.Error)
}

This will result in a trace like the following:

onboarding-flow ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [2.4s] ✓
┃
┣━ signup ━━━━━━━━━ [800ms]
┃
┗━ tutorial ━━━━━━━━━━━━━━━━ [1.6s]

Example - track HTTP calls using an interceptor

class HttpInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val span = Measure.startSpan("${request.method} /${request.url.redact()}")

        return try {
            val response = chain.proceed(request)
            span.end(SpanStatus.SUCCESS)
            response
        } catch (e: Exception) {
            span.end(SpanStatus.ERROR)
            throw e
        }
    }
}

This will result in a trace like the following:

GET /orders ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ [2.4s] ✗

For more detailed API documentation and examples, checkout performance tracing docs.

Handled exceptions

To track exceptions which were caught and handled by the app, use the trackHandledException method. While your app gracefully recovers from these exceptions, tracking them helps identify potential degraded app experience.

try {
    methodThatThrows()
} catch (e: Exception) {
    Measure.trackHandledException(e)
}

Screen View

Measure automatically tracks screen_view events for androidx.navigation library. It also tracks lifecycle_activity events and lifecycle_fragment events.

However, screen_view events can also be triggered manually using the following method to keep a track of the user flow.

Measure.trackScreenView("checkout")

Configuration options

See all the configuration options available.

Concepts

Sampling

Sampling controls what percentage of data is collected and sent to the server, helping balance data quality with system performance and storage costs.

Session Sampling

Set samplingRateForErrorFreeSessions to control event collection from sessions without errors. By default, the SDK sends all events from crashed sessions to the server, while collecting no events from error-free sessions.

  • 0.0 — No events from error-free sessions (default)
  • 0.1 — 10% of error-free sessions
  • 1.0 — All sessions

Session sampling helps optimize data collection for crash and error analysis.

Trace Sampling

traceSamplingRate controls performance trace collection independently of session sampling. While session sampling determines which session-level events are sent, trace sampling specifically controls performance monitoring data.

This separation ensures:

  • Performance traces are collected based on their own sampling rate.
  • Critical performance data is captured regardless of session errors.
  • Session data remains focused on crash analysis and debugging.

Session

A session represents a continuous period of activity in the app. A new session begins when an app is launched for the first time, or when there's been no activity for a 20-minute period. A single session can continue across multiple app background and foreground events; brief interruptions will not cause a new session to be created. This approach is helpful when reviewing session replays, as it shows the app switching between background and foreground states within the same session.

The current session can be retrived by using getSessionId method.

val sessionId = Measure.getSessionId()

Performance Impact

Benchmarks

We benchmark the SDK's performance impact using a Pixel 4a running Android 13 (API 33). Each test runs 35 times using macro-benchmark. For detailed methodology, see android/benchmarks.

Important

Benchmark results are specific to the device and the app. It is recommended to run the benchmarks for your app to get results specific to your app. These numbers are published to provide a reference point and are used internally to detect any performance regressions.

Benchmarks results for v0.9.0:

  • Adds 26.258ms-34.416ms to the app startup time (Time to Initial Display) for a simple app.
  • Adds 0.57ms for view-based layouts, and 0.65ms for compose based layouts to every gesture.

Profiling

To measure the SDK's impact on your app, we've added traces to key areas of the code. These traces help you track performance using Macro Benchmark or by using Perfetto directly.

  • msr-init — time spent on the main thread while initializing.
  • msr-start — time spent on the main thread when Measure.start is called.
  • msr-stop — — time spent on the main thread when Measure.stop is called.
  • msr-trackEvent — time spent in storing an event to local storage. Almost all of this time is spent off the main thread.
  • msr-trackGesture — time spent on the main thread to track a gesture.
  • msr-generateSvgAttachment — time spent on background thread to generate a SVG layout.