ApkSize Analyzer is a command line tool to analyze the size of APK and AAB (Android App Bundle) files and give reports in multiple formats which you can then use in CI/CD environments (Jenkins etc) to get deeper insights for your app.
It can also be used to compare two APKs and generate a report of the differences. You can do it in Android Studio also right? This will give you differences on source packages level so that you know what code exactly increased and not just differences in dex files.
Available as: JVM JAR (requires JDK 21+) or native binary (no JDK needed, instant startup).
- Analyzes APK and AAB files for files, dex, resources.
- Package level differences between two APKs.
- The report generates JSON, HTML, pdf for easy integration and reading.
- You can provide a specific app package prefix which will be reported separately for easy classification.
- The package sizes are calculated as combination of all the dex files.
- Included a separate list for the images listed according to decreasing size.
- Most of the lists are sorted in the decreasing order of their sizes.
AAB-specific features:
- Per-module breakdown (base, feature modules) with component sizes (DEX, resources, assets, native libs)
- Resource counting directly from resources.pb (no aapt2 needed)
- Manifest info extraction (package name, version, min/target SDK)
- Bundle config analysis (split dimensions, compression settings)
- Maven dependency list from bundle metadata
Grab the latest release from GitHub Releases. Each release includes:
| Artifact | Description |
|---|---|
apkSize-*-full.jar |
Fat JAR with bundletool embedded (~48 MB). Analyzes both APK and AAB standalone. |
apkSize-*-lite-r8.jar |
R8-minified JAR (~5 MB). For AAB, supply an external bundletool JAR. |
apksize-linux-x64 |
Native binary for Linux x64 (no JDK needed). |
apksize-macos-arm64 |
Native binary for macOS Apple Silicon (no JDK needed). |
apksize-windows-x64.exe |
Native binary for Windows x64 (no JDK needed). |
This needs a JSON config file as input which contains all the arguments as defined below.
You can find sample configs in the samples/configs/ directory.
Full JAR (APK or AAB):
java -jar apkSize-0.4.0-alpha-full.jar abs --config=input/config.json
Lite JAR (AAB with external bundletool):
java -jar apkSize-0.4.0-alpha-lite-r8.jar abs --config=input/config.json --bundletoolJarPath=/path/to/bundletool-all.jar
Native binary (no JDK needed):
./apksize abs --config=input/config.json --bundletoolJarPath=/path/to/bundletool-all.jar
Note: JARs require JDK 21+ to run. Native binaries have no runtime dependencies.
How to compare 2 apks?
For comparing 2 apks, along with apk paths, there are arguments for comparing.
isDiffMode must be true. You can find the sample config for comparing in samples directory.
Arguments:
0. (REQUIRED) relative/abs -> Tells how to look for the path you have given whether relative to the current folder or absolute paths.
- (REQUIRED) --config -> json file path containing all the required arguments.
/**
* Must
* Input APK or AAB file to analyze.
* File type is auto-detected from the extension (.apk or .aab).
*/
inputFilePath = ""
/**
* Input proguard mapping file to test with. (Optional)
*/
inputFileProguardPath = ""
/**
* Output folder path to put all the output files. (This is must)
*/
outputFolderPath = ""
/**
* The app name to print in the html & pdf report
* @default empty
*/
appName = ""
/**
* Boolean whether to generate a HTML report for this analyzer.
* @Default true
*/
generateHtmlReport = true
/**
* This is the size filter for top images, top files.
* This is the minimum size above which these data will be recorded and others below this will be discarded.
* This is in bytes.
*/
topFilesImagesSizeLimiter = 10240L
/**
* This is the size filter for filtered files.
* This is the minimum size above which these data will be recorded and others below this will be discarded.
* This is in bytes.
*/
filteredFilesSizeLimiter = 51200L
/**
* This is the max number of count that will be added to json data.
* These are for top images, top files, filtered files.
* If the list of items is less than 30, full list is returned else sliced to only this count.
* This will be a positive integer.
*/
filesListMaxCount = 20
//dex constants
/**
* We classify few special packages (app related mostly) and show them as a separate item in the output json.
* This is just to filter out and see data for your our code.
* This checks if the package name starts with this prefix.
* If you have any code other than this package, it might miss this filtering.
*/
appPackagePrefix = ""
/**
* Every dex package & file we process has a depth to it.
* Like com -> 1, com.apkDemo -> 2, com.apkDemo.home -> 3
* It starts with 1.
* We filter the packages that we output in json by this constants.
* This is the minimum depth that will be filtered out to reduce the noise.
* We don't want packages like 'com' to come in our result.
* Change it to filter out even more.
* A positive integer.
* Depth starts with 1.
*/
dexPackagesMinDepth = 2
/**
* This is the max count for the list of app packages (special appPackagePrefix filtered list)
* Positive integer.
*/
appPackagesMaxCount = 20
/**
* This is the max count of dex packages in the output json.
* Positive integer.
*/
dexPackagesMaxCount = 30
/**
* This is the minimum size in bytes that we use to filter.
* Any packages/filter below this size is filtered and not included in the output json.
* Size in bytes.
*/
dexPackagesSizeLimiter = 51200L
/**
* aapt2 executable file path according to the system.
* If this path is given then only resources stats are generated.
* This is absolute path of aapt2 file.
*/
aapt2Executor = ""
/**
* Set this value to true if you want to compare 2 apks.
* Program will ignore second apk file if this is false
* Default value is false.
*/
isDiffMode = false
/**
* Apk path according to abs/relative argument of the apk which needs to be compared to 1st one.
* Default path is empty.
* isDiffMode must be true for this comparison mode to enable.
*/
compareFilePath = ""
/**
* Apk proguard mapping file for the second/comparing file.
* Default path is empty.
* isDiffMode must be true for this comparison mode to enable.
*/
compareFileProguardPath = ""
/**
* This is the size limiter for differences of dex packages.
* Any package size increase/decrease below this value will be discarded from report.
* Default : 10000 (~10kb).
* Value must be in bytes.
*/
diffSizeLimiter = 10000L
/**
* This is a check to enable/disable file-to-file comparison of both the apks.
* This comparison usually takes really long with big apks (Around 4-8 mins for 50 MB apks)
* Dex package comparison will still run.
*/
disableFileByFileComparison = false
AAB Example:
java -jar apkSize-0.4.0-alpha-full.jar abs --config=samples/configs/config-aab.json
Just point inputFilePath to an .aab file instead of an .apk file. The tool auto-detects the file type.
For AAB files, resource analysis is done via the bundletool library (no aapt2Executor needed).
Tip: If you use the lite JAR or native binary, set
bundletoolJarPatheither as a CLI argument (--bundletoolJarPath=/path/to/bundletool-all.jar) or directly in the config JSON:{ "bundletoolJarPath": "/path/to/bundletool-all.jar" }
Note: Comparison (diff) mode is not yet supported for AAB files.
LOB analysis attributes every byte in your APK/AAB to a functional unit (LOB) — such as "Hotels", "Flights", "Payments", etc. This lets you answer: "How much of the app size does each team/feature own?"
It requires three mapping files produced by a Gradle plugin that runs during your app build:
| File | Purpose |
|---|---|
module-metadata.json |
Lists all Gradle modules and groups them into functional units (LOBs). |
resource-mapping.json |
Maps every file path (res, assets, native libs) to module indices. |
package-mapping.json |
Maps every DEX package to [moduleIndex, classCount] pairs. |
How to enable:
Set moduleMappingsPath in your config JSON to the directory (or .zip) containing the three files:
{
"inputFilePath": "/path/to/app-release.aab",
"outputFolderPath": "/path/to/output",
"moduleMappingsPath": "/path/to/module-mappings/",
"appPackagePrefix": ["com.yourapp"],
"appModulePrefixes": ["com.yourapp"]
}appPackagePrefix and appModulePrefixes improve accuracy by letting the analyzer distinguish your
app's own code/files from third-party libraries during fallback attribution.
How it works:
-
File attribution — Each file in the APK/AAB is matched against
resource-mapping.jsonto find which module (and therefore which LOB) owns it. Files are categorized asresources,assets,nativeLibs, orother. -
DEX attribution — Each DEX package is matched against
package-mapping.json. When a package maps to multiple modules, bytes are split proportionally by class count. The analyzer walks up the package hierarchy (e.g.a.b.c.d→a.b.c→a.b) to find ancestor mappings for unmapped leaf packages. -
Fallback attribution — Known platform packages (
android,androidx,dagger, etc.) are auto-attributed toandroid_platform. Non-app packages are attributed tothirdparty. This reduces unmatched noise without mapping files for third-party code. -
DEX normalization — Package-level sizes from dexlib2 may not perfectly match raw
.dexfile bytes. The analyzer applies proportional normalization so LOB totals reconcile with actual file sizes.
Output:
When moduleMappingsPath is set, the output directory will contain:
| File | Content |
|---|---|
apkstats.json |
Main report — includes lobAnalysis with per-LOB size breakdown (code, resources, assets, nativeLibs, other, total) and a coverage summary. |
lob-unmatched-details.json |
Files and DEX packages that could not be attributed to any LOB — useful for improving mapping coverage. |
lob-attributed-details.json |
Per-LOB detailed list of every file and DEX package attributed — useful for auditing. |
lob-dex-overhead-details.json |
DEX structural overhead breakdown (headers, string pools, etc.) not attributable to individual packages. |
Example lobAnalysis output:
{
"lobSizes": {
"hotels": { "code": 5242880, "resources": 1048576, "assets": 0, "nativeLibs": 0, "other": 524288, "total": 6815744 },
"flights": { "code": 3145728, "resources": 524288, "assets": 262144, "nativeLibs": 0, "other": 131072, "total": 4063232 }
},
"summary": {
"totalAttributedBytes": 10878976,
"totalUnattributedBytes": 524288,
"coveragePercent": 95.4
}
}./gradlew clean assemble # Builds full + lite-r8 JARs
./gradlew nativeImageLite # Builds native binary (requires GRAALVM_HOME)- JDK 21+ (auto-provisioned by the Gradle wrapper via foojay toolchain resolver)
- Gradle 8.14.4 (bundled via wrapper)
- Kotlin 2.3.10
- GraalVM CE 21+ (only for native binary builds; set
GRAALVM_HOME)
See README-DEVELOPMENT.md for full development setup, GraalVM config regeneration, and CI/CD details.
- Add Test Cases
- Make HTML report for appPackages more readable
- Make it as a Gradle plugin
- AAB comparison (diff) mode support
- PDF report generation
Note: These items are not in the order

