Establish a release baseline
Build the variant you ship with its intended signing and optimization settings. Record the source revision, dependency lock state, artifact type and native architectures. Analyze DEX, resources, assets and native libraries separately; an optimization aimed at Java bytecode will not meaningfully reduce a package dominated by video files.
./gradlew :app:assembleRelease
apkanalyzer apk file-size app/build/outputs/apk/release/app-release.apk
apkanalyzer files list app/build/outputs/apk/release/app-release.apk
Output paths vary by flavors and module names. Use the actual output produced by your build, not a stale file from an earlier run.
Pick the highest-value change
| Dominant contributor | Investigate first |
|---|---|
| DEX code | Unused dependencies, R8 optimization and overly broad keep rules |
| Raster images | Excess resolution, duplicate formats and unnecessary variants |
| Native libraries | Required ABIs, duplicated SDK runtimes and unused plugins |
| Assets | Test data, bundled originals and content suitable for asset delivery |
| Resources | Unused resources and configuration choices appropriate to the audience |
Do not remove language or density support merely because it is visible in a chart. Decide what the product supports, then measure the effect on real delivery. Android size guidance.
Enable optimization deliberately
In a Kotlin DSL Android application module, the basic release settings are:
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
This fragment is not a complete build file. Add narrowly scoped rules only when reflection, serialization or native entry points require them. Blanket rules keeping entire dependency trees can eliminate much of the benefit. R8 configuration.
Measure one intervention at a time
Rebuild after each meaningful change and compare identical artifact types. Keep a short note such as “removed unused image editor dependency; native library category decreased; image import smoke test passed in our environment.” Do not invent a percentage before measuring it.
For native apps, consider device-targeted delivery before deleting an ABI. The ABI documentation explains architecture packaging; supporting fewer devices is a product decision, not a free optimization.
Test the failure-prone release paths
Run login, purchase restoration, deep links, serialization, background work and any dynamically loaded features relevant to the app. These can behave differently after shrinking. Preserve the matching mapping file for crash diagnosis.
Use APKLint's Size Analyzer to identify large packaged entries and its Size Optimization Guide to organize changes. APKLint does not rewrite your APK into a smaller, re-signed production build; the final fix belongs in the source and build pipeline.
Choose between three measured candidates
Suppose a release inventory identifies a 6 MB set of unused tutorial videos, a 2 MB image library used by one screen and several small icons. Start with the videos if their lack of production use is confirmed. Replacing all the icons may cost more engineering time while affecting far fewer bytes. Keep each hypothesis in a small change record with the paths, owning feature and expected benefit; these example numbers are a prioritization exercise, not a promised saving for your app.
Remove the videos through the relevant source-set or asset-generation input. Build the same release variant and verify that those exact entries disappear. Then exercise the onboarding flow both online and offline, because an apparently unused tutorial might be a fallback for a failed network request. If the product still needs an offline tutorial, redesign its delivery or encoding instead of silently deleting the feature.
Investigate a release-only failure after shrinking
If the optimized candidate becomes smaller but fails to deserialize a saved record, capture the exception and identify the affected model class. Inspect whether a reflection or serialization entry point needs a specific keep rule. Broadly keeping every class can hide the failure while forfeiting most optimization. The Android keep-rule guidance explains the boundary that these rules describe.
Use a synthetic record saved by the previous release as an upgrade fixture. Test reading it with the candidate, creating a new record and reopening that record after a process restart. A successful fresh install would miss the first case. Preserve the matching mapping file so a failure in the minified candidate can be diagnosed without guessing which source method produced it.
Accept a size change only with its support conditions recorded. If a smaller artifact results from excluding a language, ABI or feature, state that changed product coverage explicitly. If the same users and features remain supported, attach the reproducible artifact comparison and the relevant release tests. This makes the optimization useful to the next maintainer as well as to the current release.
Sources and further reading
- Android Developers: Reduce your app size
- Android Developers: Enable optimization with R8
- Android NDK: Android ABIs
Reference review: 29 September 2026. Examples illustrate the workflow; check your installed versions, release artifact and account-specific Console requirements before applying them. This guide is not a claim that APKLint executed your project or verified your private account.


