- Requirements
- Quick start
- Verify integration
- Manually start/stop the SDK
- Features
- Configuration options
- Concepts
- Performance impact
Name | Version |
---|---|
Android Gradle Plugin | 7.4 |
Min SDK | 21 (Lollipop) |
Target SDK | 31 |
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 |
Once you have access to the dashboard, create a new app and follow the steps below:
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>
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
tofalse
will disable the plugin for that variant. This prevents the plugin to collectmapping.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
}
}
}
Add the following to your app's build.gradle.kts
file.
implementation("sh.measure:measure-android:0.9.0")
or, add the following to your app's build.gradle
file.
implementation 'sh.measure:measure-android:0.9.0'
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,
)
)
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!
If you see no data on the dashboard. Here's how to investigate:
Enable logging during SDK initialization. All Meaure SDK logs use the tag Measure
.
MeasureConfig(enableLogging = true)
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
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.
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.
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.
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.
The following data is automatically collected by Measure. Read the individual docs for more details.
- Crash tracking
- ANR tracking
- Network monitoring
- Network changes
- Gesture tracking
- Layout Snapshots
- Navigation & Lifecycle
- App launch
- App exit info
- CPU monitoring
- Memory monitoring
- App size
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.
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)
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.
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)
}
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")
See all the configuration options available.
Sampling controls what percentage of data is collected and sent to the server, helping balance data quality with system performance and storage costs.
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.
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.
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()
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.
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 whenMeasure.start
is called.msr-stop
— — time spent on the main thread whenMeasure.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.