Anatomy of an APK
An APK is a ZIP file with a strict internal contract: binary manifest, compiled resources, DEX code, native libraries, and a signature that covers the whole archive. Learn the parts and install failures stop being mysterious.
Strip away the tooling and an APK is a ZIP archive. What makes it an APK is the contract about what is inside: a compiled manifest, a compiled resource table, DEX files, native libraries in ABI-named folders, and a signature that covers the archive rather than individual files.
TL;DR#
- An APK is a ZIP with a fixed set of parts:
AndroidManifest.xml,classes*.dex,resources.arsc,res/,lib/\<abi\>/,assets/,META-INF/, and the signing block. - The manifest and resources are binary XML and a binary table, not text. The platform parses them without a general XML parser.
classes*.dexexist because a single DEX can reference at most 65,536 methods; beyond that you need multidex or, better, code shrinking.- Native libraries are stored uncompressed and page-aligned (
zipalign) so they can be mapped rather than copied. - The APK Signing Block sits between the last entry and the central directory; v2 and later sign the archive, which is why tampering is detectable without unzipping.
Mental model#
Think of a shipping container.
The container itself is standard (ZIP). Inside, every item has a declared place: the manifest is the customs declaration, the DEX files are the machinery, the resources are the consumables, the native libraries are the specialist tools, and the signature is the seal on the door.
A container with a broken seal is not inspected item by item at the destination — it is refused. That
is roughly what PackageManager does.
Where the analogy breaks#
A container is opened and its contents used. An APK is often memory-mapped rather than extracted: the platform reads the central directory, maps the pieces it needs, and never unpacks the whole thing. That is why alignment and compression choices matter for performance.
The big picture#
Text description of this diagram
An interactive treemap of a real APK measured from the Zygote template app in this repository. The largest area is DEX code — classes.dex and its siblings — which in a debug build is over 28 MB across five files. The resource table resources.arsc follows, then res/ with compiled layouts and drawables, lib/ with native libraries, META-INF/ with signature metadata, and AndroidManifest.xml, which is tiny but decisive. With R8 enabled for release the same app is roughly seventeen times smaller: one DEX file of about 1.1 MB instead of five totalling 28 MB. Clicking a group explains what it holds and gives both the debug and release sizes.
Those proportions are measured, not invented. Debug APK 28.9 MB uncompressed; the same app with R8 enabled for release is 1.7 MB — 16.8× smaller. The debug DEX is 28.2 MB across 5 files; release shrinks it to 1.1 MB in 1 file. Sizes are uncompressed sizes of the entries inside each APK.
Two cautions. The template is a Compose hello-world, so its shape is the shape of framework code, not of a real product: a game can be 90% assets, a small utility can be almost entirely dex, and an app shipping four ABIs can double its size in native libraries alone. And the figures are uncompressed entry sizes inside the APK, which differ from the download size because the archive itself is deflated.
Deep dive#
AndroidManifest.xml#
The manifest is the app's declaration to the platform: package name, components, permissions, required hardware features, the minimum and target SDK, and metadata. It is compiled to binary XML, a compact format that the platform can parse without a general-purpose XML parser.
Two consequences follow. First, you cannot meaningfully edit it after building — it is compiled. Second, its size is tiny compared with everything else, but it is the file that decides whether the app can install at all.1
classes.dex and the 64K limit#
DEX is Android's bytecode format. A single DEX file can reference at most 65,536 methods, because method references are indexed with 16 bits. An app that exceeds that must either shrink its code or split it into additional DEX files.2
On modern Android, multidex is largely handled by the platform: a debug build can use legacy multidex, and release builds with a minSdk at or above 21 do not need the support library at all.
resources.arsc and res/#
This pair is the most misunderstood part of the APK.
res/holds the actual resource files, organised into configuration-specific folders:res/values-fr/,res/drawable-xxhdpi/,res/layout-land/.resources.arscis the table that maps every resource ID to its value for each configuration.
The split exists so that a lookup at runtime is a table read, not a directory search. When
getString(R.string.title) runs, it has an integer ID; the table tells it which values-* entry to
use for the current configuration.4
The next page takes this apart properly, including why IDs start with 0x7f.
lib/<abi>/#
Native libraries live under a folder named after the ABI: arm64-v8a, armeabi-v7a, x86,
x86_64. An app that ships all four can be several times larger than one that ships only the ABI a
device needs — which is exactly the problem App Bundles solve.
Native libraries are stored uncompressed and aligned to a page boundary. The reason is mmap:
the loader can map the library directly from the APK instead of extracting it to disk, which saves
space and install time. That is what zipalign guarantees, and why an aligned APK is a requirement
for modern installs.5
assets/#
Files shipped verbatim. Unlike res/, they are not compiled, not given resource IDs and not
configuration-aware. You read them with AssetManager by path. Game data, ML models and fonts
typically live here.
The distinction from res/raw/ is subtle but real: res/raw/ files get resource IDs and are
configuration-aware; assets/ files do not.
META-INF/ and the signing block#
This is where the APK's integrity story lives, and it has two eras.
The v1 scheme (JAR signing) puts a digest of every file into META-INF/MANIFEST.MF, signs that
manifest, and stores the signature. It verifies per file.
The v2 scheme introduced the APK Signing Block, a region between the last ZIP entry and the central directory, holding digests of the whole archive. v3 adds key rotation, and v4 adds a separate signature file for incremental installation.678
The reason for moving beyond v1 is that per-file verification is slow and has known weaknesses: an attacker can add files that v1 does not cover, or exploit ambiguities in the ZIP structure. Signing the whole archive closes those gaps.
What makes an APK large, and what to do#
The four usual culprits, in order:
- Native libraries for several ABIs. Ship one, or use an App Bundle.
- Density-specific images. Ship vector drawables, or use an App Bundle's density splits.
- DEX from unused dependencies. Enable R8 shrinking.
- Assets. Compress them, download them, or move them to an asset pack.
Android Studio's APK Analyzer shows the same breakdown as the treemap above, with real numbers.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 3 | 1.5 Cupcake |
|
| 12 | 3.1 Honeycomb |
|
| 15 | 4.0.3 Ice Cream Sandwich |
|
| 19 | 4.4 KitKat |
|
| 24 | 7.0 Nougat |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 33 | 13 Tiramisu |
|
| 34 | 14 Upside Down Cake |
|
| 37 | 17 Cinnamon Bun |
|
Under the hood#
- PackageParser.javahow the manifest is parsed
- AssetManager.javahow resources are loaded
- the resource compiler and linker
- the alignment tool
Curiosity corner#
- An APK is a valid ZIP, but a ZIP is not a valid APK. You can unzip one; you cannot install a plain ZIP.
unzip -ldoes not show the signing block. It is not a file entry, which is why people are surprised to find it withapksigner verify --print-certs.- A 1-byte change invalidates the signature. The whole-archive scheme means there is no such thing as a harmless post-signing edit.
Try it#
unzip -l app-release.apk | head -30
apksigner verify --print-certs app-release.apk
aapt2 dump badging app-release.apk
Example output
AndroidManifest.xml
classes.dex
classes2.dex
resources.arsc
res/drawable-xxhdpi/ic_launcher.png
lib/arm64-v8a/libexample.so
assets/model.tflite
META-INF/CERT.RSAunzip -l shows the entries; apksigner verify shows the signature scheme and the signer;
aapt2 dump badging shows what the platform will actually read from the manifest.
Build it#
Build a tiny app, unzip it, and match each folder to the diagram above. Then enable R8 and compare the DEX sizes — the difference is usually larger than people expect.
Check yourself#
Check yourself
An APK is fundamentally which kind of file?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩
- docsEnable multidexdeveloper.android.comaccessed 2026-10-27↩
- docsShrink, obfuscate, and optimize your appdeveloper.android.comaccessed 2026-10-27↩
- docsApp resources overviewdeveloper.android.comaccessed 2026-10-27↩
- docsapksignerdeveloper.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v2source.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v3source.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v4source.android.comaccessed 2026-10-27↩
- docsAPK signature schemesource.android.comaccessed 2026-10-27↩
- docsAbout Android App Bundlesdeveloper.android.comaccessed 2026-10-27↩