APK File Documentation
Summary
An APK (Android Package) file is the installation package for an Android app: a signed ZIP archive holding the app’s compiled code, resources, and manifest. Google introduced it with Android 1.0 in 2008, and its MIME type is application/vnd.android.package-archive. To install a .apk by hand (“sideloading”), open it in your file manager and allow “install unknown apps” for that app.
Technical details
| Feature | Value |
|---|---|
| Full name | Android Package |
| File extension | .apk |
| MIME type | application/vnd.android.package-archive |
| Format type | ZIP-based application package (binary) |
| Developer | Google (Android Open Source Project) |
| Introduced | September 23, 2008 (Android 1.0, HTC Dream) |
| Container / base format | ZIP archive |
| Byte order | Little-endian |
| Magic number (hex) | 50 4B 03 04 (ASCII PK, standard ZIP header) |
| Compression | DEFLATE per entry; some entries STORED and zipaligned (4-byte / 16 KB page) |
| Signing | APK Signature Scheme v2 (Android 7), v3 (Android 9), v4 incremental (Android 11) |
| Signature files | META-INF/MANIFEST.MF, META-INF/CERT.SF, META-INF/CERT.RSA (v1) |
| Manifest | AndroidManifest.xml (binary XML) |
| Compiled code | classes.dex (Dalvik Executable, run by ART) |
| Compiled resources | resources.arsc |
| Resource / library dirs | res/, lib/<abi>/, assets/ |
| Encryption | None — the package is signed for integrity, not encrypted |
| Typical size | 2 MB – 1.5 GB (games with OBB assets at the high end) |
| Max entries | 65,535 (ZIP limit; ZIP64 lifts it) |
| Build tools | Android SDK, Android Studio, zipalign, apksigner |
| Open standard | Yes — part of the Android Open Source Project |
| Superseded by (distribution) | AAB (Android App Bundle) on Google Play since August 2021 |
| Related extensions | .aab, .apks, .xapk, .apkm, .obb |
| Specification | source.android.com/docs/core/architecture/android-app |
What is an APK file?
APK stands for Android Package. It is the container Android uses to distribute and install an application, so it fills the same role on Android that an EXE fills on Windows. Google shipped it with Android 1.0 on 23 September 2008, on the HTC Dream, and every app that arrives from Google Play lands on the device as one or more APK files. Installing one yourself, from a file rather than from a store, is called sideloading.
Structurally an APK is nothing more exotic than a ZIP archive with a fixed internal layout and a signature appended to it. The file begins with the ZIP local-file-header magic 50 4B 03 04 (ASCII “PK”, the initials of ZIP’s author Phil Katz), so a copy renamed from .apk to .zip opens in any archive tool. What makes the ZIP an APK is not a distinct magic number but the presence of specific entries inside it: a compiled AndroidManifest.xml, at least one classes.dex, and a resources.arsc table. The rest of this page walks through those entries field by field, then through how the bytecode runs and how the package is signed.
Inside the ZIP: the APK layout
A ZIP stores each member as a local header followed by its (usually DEFLATE-compressed) data, and ends with a central directory that indexes every entry by name, offset, size and CRC-32. Android reads that central directory to locate each part of the app without decompressing the whole archive. Some entries are deliberately left STORED (uncompressed) and 4-byte aligned by the zipalign tool so the runtime can memory-map them straight from the file; on modern devices the alignment target is the 16 KB memory page. The canonical tree looks like this:
app.apk (a ZIP archive)
├── AndroidManifest.xml compiled *binary* XML (axml) — NOT plain text
├── classes.dex Dalvik Executable bytecode
├── classes2.dex further DEX files under multidex (optional)
├── resources.arsc compiled resource table (strings, styles, IDs)
├── res/ compiled resources: drawables, layouts, XML
│ ├── drawable-xxhdpi/
│ ├── layout/
│ └── values/
├── assets/ raw files, read verbatim via AssetManager (optional)
├── lib/ native .so libraries, one dir per ABI
│ ├── armeabi-v7a/
│ ├── arm64-v8a/
│ └── x86_64/
└── META-INF/ signing metadata (v1 JAR signature)
├── MANIFEST.MF SHA digests of every other entry
├── CERT.SF signed copy of those digests
└── CERT.RSA signer certificate + signature block
The AndroidManifest.xml at the root is not the text file a developer edits. It is compiled binary XML, often called axml: a length-prefixed binary encoding built from resource chunks (a string pool, then start-tag / end-tag / attribute records that reference that pool by index). Attribute values that name a resource are stored as integer IDs pointing into resources.arsc, not as strings, which is why opening the manifest in a plain text editor shows binary noise rather than markup. Tools like aapt2 dump or apktool decode it back to readable XML.
The lib/ directory holds compiled C/C++ code as ELF shared objects, split into one subdirectory per Application Binary Interface (ABI): armeabi-v7a for 32-bit ARM, arm64-v8a for 64-bit ARM, and x86_64 for 64-bit Intel emulators. The runtime loads only the subdirectory matching the device, which is why an APK built for arm64-v8a refuses to run on an x86 device. A ZIP archive tops out at 65,535 entries unless ZIP64 is used, which sets a hard ceiling on how many files an APK can contain.
The DEX file format
The application’s Java or Kotlin source compiles to DEX (Dalvik Executable) bytecode stored in classes.dex. A DEX file starts with an 8-byte magic value, 64 65 78 0A 30 33 35 00 — ASCII "dex\n035\0", where 035 is the format version (later Android releases raised it to 037, 038 and 039). The magic is followed by a fixed 112-byte header:
offset size field
0x00 8 magic "dex\n035\0"
0x08 4 checksum Adler-32 of the rest of the file
0x0C 20 signature SHA-1 hash of the rest of the file
0x20 4 file_size total bytes in the file
0x24 4 header_size always 0x70 (112)
0x28 4 endian_tag 0x12345678 (little-endian)
0x2C 8 link_size / link_off
0x38 8 map_off / string_ids_size
... offsets+counts for the ID and data sections
Unlike a JVM class file, which holds one class each, a single DEX packs an entire app into shared lookup tables that eliminate duplicate constants. The header points at a series of ID lists, each a table of offsets:
string_ids— every string constant, deduplicated across the whole app.type_ids— class and primitive type descriptors (e.g.Ljava/lang/String;), each indexing intostring_ids.proto_ids— method prototypes (return type plus parameter type list).field_ids— field references (defining class, type, name).method_ids— method references (defining class, prototype, name).class_defs— the actual class definitions: superclass, interfaces, access flags, and pointers to the encoded field and method data.
After the ID tables comes the data section holding the variable-length payloads those tables point into: string bytes (MUTF-8, length-prefixed), annotations, and the code_item structures that carry each method’s registers count, in/out argument counts, and its stream of bytecode instructions. A trailing map_list catalogues every section. Because each index type (string_ids, method_ids, etc.) is a 16-bit reference in many instructions, a single DEX can address only 65,536 methods; passing that limit is what forces multidex, described below.
ART, OAT and how DEX runs
A recurring misconception is that classes.dex holds Java bytecode for a JVM. It does not, and the DEX runtime is not a JVM. DEX bytecode is register-based: each method declares a fixed set of virtual registers and instructions operate on them by number (add-int v0, v1, v2), whereas the JVM is stack-based and pushes and pops operands. The register model needs fewer instructions per method and suits the load/store nature of ARM hardware.
The Android Runtime (ART) executes DEX. When an app installs, ART’s dex2oat compiler ahead-of-time (AOT) translates the DEX into native machine code for the device’s CPU and writes it into an OAT file, which is packaged as a normal ELF shared object (.odex/.oat) on the device. That precompiled code is why installation pauses for a moment and why the same APK runs unchanged on 32-bit ARM, 64-bit ARM or x86: the bytecode is portable and the device-specific step happens at install time. Android versions before 5.0 used the older Dalvik VM, which interpreted DEX and just-in-time (JIT) compiled hot paths at runtime instead of AOT-compiling at install. Modern ART actually blends both: it can install quickly with an interpreter plus JIT, then AOT-compile in the background using a profile of what the app really executes. Native code in lib/ is already machine code and skips this entirely; ART only touches the DEX.
Multidex is the workaround for the 65,536-method-reference limit of a single DEX. When an app’s method count exceeds that ceiling, the build splits its classes across classes.dex, classes2.dex, classes3.dex and so on, and ART loads all of them into one class loader at startup.
APK signing schemes v1–v4
Android refuses to install an unsigned APK, and it ties every future update to the same signing key: an update signed by a different key is rejected outright. That single rule is what stops an attacker from pushing a malicious “update” over an installed app. Four signature schemes have accumulated over the years, and a modern APK is usually signed with several at once so older devices can still verify it.
| Scheme | Since | What it covers | Key property |
|---|---|---|---|
| v1 (JAR signing) | Android 1.0 | Per-file SHA digests in META-INF/ (MANIFEST.MF, CERT.SF, CERT.RSA) | Anything outside the manifest list is unprotected; entries can be added |
| v2 (APK Signing Block) | Android 7.0 | The whole file, hashed in 1 MB chunks, via a block inserted before the ZIP central directory | Any byte change breaks verification; faster to check |
| v3 | Android 9 | Same block as v2, plus a signed key-rotation record | Lets a developer change signing keys without losing update continuity |
| v4 | Android 11 | A Merkle-tree (fs-verity) hash in a sidecar .apk.idsig file | Enables incremental install: the app streams and runs while still downloading |
v1 is the JAR signature you can see in the tree above: MANIFEST.MF lists a SHA digest for every other entry, CERT.SF signs those digests, and CERT.RSA carries the signer’s certificate. Its weakness is that it only protects the listed files, not the ZIP metadata around them. v2 (Android 7) fixed this by hashing the entire file and storing the signature in an APK Signing Block, a region wedged between the last ZIP entry and the central directory so it does not disturb the archive structure. v3 (Android 9) reused that block and added key rotation, letting an app migrate to a new key while proving the old and new keys belong to the same lineage. v4 (Android 11) added an incremental signature built on fs-verity, stored beside the APK in an .apk.idsig file, so a large app can begin running before every block has downloaded.
Two housekeeping steps sit next to signing. zipalign pads STORED entries so they start on 4-byte (or 16 KB page) boundaries for memory-mapping; it must run before v1 signing but the v2+ block is added after alignment. Because any edit to a signed APK invalidates its signature, a repackaged app has to be re-signed with a new key, and Android then treats it as an entirely separate application rather than an update.
Manifest declarations: package, SDK levels and permissions
Decoded from its binary form, AndroidManifest.xml carries the metadata Android reads before it will even install the app. The root <manifest> element declares the package name, the app’s unique identity on the device and in the store (for example com.example.app); reusing an installed package name with a different signing key is one reason an install fails. A <uses-sdk> element sets minSdkVersion (the lowest Android API level the app runs on) and targetSdkVersion (the API level it was tested against, which controls which behaviour-change opt-ins apply). Each <uses-permission> element names a capability the app wants, such as android.permission.CAMERA; dangerous permissions are additionally prompted at runtime on Android 6 and later. The manifest also enumerates the app’s components (activities, services, broadcast receivers, content providers) and their intent filters, so the system knows what the app can launch and respond to.
APK vs Android App Bundle
Since August 2021, new apps submitted to Google Play must be uploaded as an AAB (Android App Bundle), not an APK, and the two are not interchangeable. An AAB is a publishing format: it packs the code and resources for every device configuration into one upload and cannot be installed on a phone. Google Play’s servers run bundletool against the AAB to generate split APKs tailored to each device’s screen density, CPU ABI and language, so a phone downloads only the slices it needs (a base APK plus configuration splits). The APK is still the real on-device install unit; the bundle just moves the final packaging step from the developer to the store. The transform is one-way: an AAB produces APKs, but an installed APK cannot be turned back into the developer’s AAB. Because a split install is several APKs, mirror sites often wrap them as an XAPK or similar multi-APK bundle that a dedicated installer unpacks.
Technical safety notes
Sideloading is the leading Android malware vector for one concrete reason: an APK installed from a file bypasses the server-side scanning that Google Play Protect runs on store installs, so nothing vets the code before it reaches the device. The signature checks described above guarantee integrity and continuity — that the package was not altered after signing and that an update comes from the same key — but they say nothing about intent. A validly signed APK can still be malicious; a self-signed key is trivial for an attacker to generate, so a green signature only means “unchanged since whoever built this signed it”.
The practical risks are specific. Over-broad permissions are the main one: an app that requests SEND_SMS, accessibility service access or device-admin rights it has no functional need for can exfiltrate data or intercept two-factor codes, so the permissions listed in the manifest should match what the app plausibly does. Signature pinning cuts the other way as a defence: because Android refuses an update signed with a different key, an attacker cannot silently replace an installed app unless they hold the original key. Unsolicited APKs delivered by chat, SMS or email (“smishing”) are the common delivery route and should never be installed. Since an APK is a ZIP, renaming a copy to .zip and inspecting the manifest’s permission list, the DEX and the lib/ contents is a cheap way to sanity-check a file before committing to it. Reputable sources such as F-Droid and APKMirror verify each upload’s signature against the developer’s known key, which restores part of what Play Protect would otherwise provide.
References
- Android Developers — Application fundamentals
- AOSP — Application signing (APK Signature Scheme)
- Android Developers — About Android App Bundles
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.