Skip to main content
R8 shrinks and renames an Android release build: a crash’s stack then reads at a.b.b(SourceFile:2). Apsio retraces it to com.example.shop.Cart.checkNotEmpty in Cart.kt:31, inlined frames included, with the mapping R8 wrote for that exact build. See Symbols and dSYMs for how the build and its mapping are matched.

Before you start

  • Install the apsio CLI (0.3.0 or later) and store your project’s upload token with apsio login --with-token, or set APSIO_TOKEN.
  • Your release build runs R8 (isMinifyEnabled = true). R8 writes the mapping to app/build/outputs/mapping/<variant>/mapping.txt.
The Apsio Gradle plugin, which does the steps below on every release build, is coming with the Android SDK. Until it ships, a build script runs them.

1. Give the build an id

Every build gets its own build id, a UUID made before the build:
Write it into the app’s manifest, where the Apsio Android SDK reads it and reports it with every crash as app.build_id:
A manifest placeholder keeps the id out of the file: android:value="${apsioBuildId}" in the manifest, and manifestPlaceholders["apsioBuildId"] = <the id> in the release build type. The id is made before the build, not computed from the mapping, because Gradle merges the manifest before R8 writes the mapping. One id per build: make a new one for every build. Apsio refuses a different mapping under an id it already has (the CLI exits with code 4 and says so), since that build’s crashes would be retraced with the wrong mapping. Uploading the same mapping again is harmless. --force replaces the mapping, for an id you know was reused.

2. Upload the mapping after the build

The CLI checks that the file is an R8 or ProGuard mapping, asks Apsio whether it already has a mapping for that build id (and whether it is the same file), and uploads it compressed. Mappings are limited to 512 MB. --dry-run checks the file without a token or an upload, --force uploads it again, and --json prints {build_id, path, name, classes, has_line_info, size, status}, where status is uploaded, present or checked. Crashes that arrived before their mapping are retraced when it lands, and grouped again with the original names.

In CI

Here an earlier step ran apsio build-id, saved the id as BUILD_ID and passed it to Gradle for the manifest.

When a crash stays obfuscated

  • The manifest has no io.apsio.build_id. The SDK then reports <package>@<versionName>+<versionCode>, which no mapping matches; the release’s missing symbols list it as it is.
  • The mapping of that build was never uploaded, or was uploaded under another id.
  • The build was made with another id than the one uploaded, for example a local rebuild.
Builds without R8 need no mapping: their stacks already have the original names. Native crashes in C or C++ libraries need the libraries’ debug files instead: see Upload NDK symbols.