Skip to main content
A performance report compares your pull request build with the build it merges into, on the same real phone. With the build file alone, the report shows how long startup and each flow took, the operating system’s own startup phases, and what changed in your network requests. To also see which functions got slower, for example AnalyticsClient.<init>: 6 ms -> 188 ms, MobileBoost records sampled CPU profiles while your app runs and needs two things from your CI to read them: Both are a one-time change to your build setup plus one upload step. Most teams already produce the symbol files for Google Play or a crash reporter.

What every performance build needs

  • Release-like. Build with the same compiler optimisation, minification and SDKs as production. A debug build is several times slower in places your users never see, so its numbers would mislead you.
  • The same setup for both sides. The base build (your main branch) and the head build (the pull request) have to come from the same build type and flags, or the report measures your build configuration instead of your change. Build main with the same job on every merge so a base build always exists.
  • Its own symbol files. Symbols only match the build they were produced with. Upload the mapping file or dSYMs from the same CI job as the build, never from an earlier one.
  • The backend your tests already use. Performance runs use the same environment as your functional tests. Recorded responses are replayed for the mocked network runs, and the live runs talk to that backend.

Android

1

Add a perf build type

Copy your release build type, so the performance build is optimised and minified exactly like production, but sign it with your debug key so CI does not need the upload key.
app/build.gradle.kts
If your release build type sets isDebuggable = true, leave it out of perf. A debuggable build runs slower than what your users get, so its numbers would not be representative.
2

Make only the perf build profileable

Create a manifest that only the perf build type merges in. Your production release stays unchanged.
app/src/perf/AndroidManifest.xml
profileable lets profiling tools sample a release build without the overhead of a debug build. It takes effect on Android 10 (API 29) and later, which covers every MobileBoost device.
3

Keep the R8 mapping file

R8 renames your classes and methods in release builds, so a profile shows names like a.b.c() until it is mapped back. The mapping is written next to the build:
Keep obfuscation on in perf, as in production. Turning it off with -dontobfuscate saves the upload step but also changes the code you are measuring.
4

Ship your Baseline Profile with the build

If your app has a Baseline Profile, add the Profile Installer library so the profile is applied when MobileBoost installs the APK directly. Google Play applies it for your users; a direct install does not, and startup would look slower than it is for them.
app/build.gradle.kts
5

Build an APK

MobileBoost installs APKs. Build the perf variant:
If your pipeline only produces an App Bundle, turn it into one universal APK with bundletool:
6

Check the build before uploading

apkanalyzer ships with the Android SDK command-line tools:

GitHub Actions for Android

Run the same job on pull requests and on every merge to main, so each pull request has a base build to compare against.
.github/workflows/mobileboost-perf-android.yml

iOS

1

Build the Release configuration for a device

Profile the Release configuration, or a Perf configuration duplicated from it (Project > Info > Configurations > + > Duplicate “Release” Configuration). Use a separate configuration only if you need to switch something off for performance runs, see What changes after re-signing.The build must be for a real device (iphoneos). Simulator builds run on the Mac’s CPU and say nothing about phone performance.
2

Produce dSYMs for that configuration

In the target’s Build Settings, set Debug Information Format to DWARF with dSYM File for the configuration you upload. New projects already do this for Release; check targets that were added later, and frameworks you build from source.
Stripping the app binary is fine, and it is what production does: the dSYM keeps the symbols. For binary frameworks from vendors, symbols exist only if the vendor ships dSYMs. Their code then shows as an address with the framework’s name, which still tells you whose code it is.
3

Archive and export an IPA

Use the export method your CI already uses for internal testers (Ad Hoc, called Release Testing in Xcode 15 and later, or Development). MobileBoost re-signs the app for its own devices, so the export method does not change what is measured.
4

Collect the dSYMs from the archive

The archive holds the dSYMs of the app and of every embedded framework built with it:
5

Check that the dSYM matches the app

Each build has its own UUID, and a dSYM only works for the binary with the same one:
Both commands must print the same UUID.

What changes after re-signing

MobileBoost re-signs your IPA with its own team so it installs on MobileBoost devices and can be profiled. Re-signing keeps the app identifier, the team identifier, get-task-allow and your keychain access groups, and drops every other entitlement. Plan your performance build around that:
  • Push notifications, App Groups, associated domains and other capabilities are not available. Flows that depend on them will not complete on a performance run.
  • Integrity checks that verify your own signature must be off. Jailbreak detection, code-signature checks or a check of the team ID make a re-signed build quit or behave differently. Switch them off in a Perf configuration with a compilation condition:
  • Keep your other SDKs as in production. Analytics, crash reporting and feature flag SDKs are part of what your users wait for, and measuring them is the point.

GitHub Actions for iOS

.github/workflows/mobileboost-perf-ios.yml

How MobileBoost handles your symbol files

Mapping files and dSYMs describe the structure of your code. MobileBoost uses them only to put function names on the profiles of the build they came with, and keeps them with that build.

Checklist

Exact timings for your own milestones

With the setup above, flows end when the screen stops changing and profiles are sampled. To measure exactly from and to the points you choose, mark them in your code: reportFullyDrawn() or trace("name") { ... } on Android, and os_signpost on iOS. The report then shows those spans by name.