The install flow
Installing is a transaction with a signature check, a data directory, a dexopt pass and a broadcast. It is also one of the platform's most security-sensitive paths — and the rules for who may install what keep tightening.
An install is a small transaction with large consequences. The package manager must decide, before anything runs, whether this artefact is allowed to exist on the device: is the signature valid, does it match the existing app, does the manifest claim something the user has not granted, and is there enough space to compile it?
TL;DR#
- Installs go through PackageInstaller sessions, not direct file copies.
- Commit is a transaction: parse, verify the signature, validate the split set, stage files, create the app's data directory, dexopt, then commit.
- Signature continuity is the central check: an update must be signed by the same key as the installed app (or by a key the old one authorised, via rotation).
- The dexopt step is usually the slow part of a cold install.
- Sideloading has been progressively tightened: per-app "unknown sources" since Android 8.0, and restricted settings for sideloaded apps since Android 13.
Mental model#
Think of moving into a flat.
You do not carry your furniture through the wall. You sign a lease (the session), the landlord checks
your identity and references (the signature), you get keys and an address (/data/app, /data/data),
and only then can you move in. The building's register is updated, and the neighbours are told.
Where the analogy breaks#
A lease is granted once and lasts. An install can be staged and committed later, can be incremental (the app starts before the whole APK has arrived), and can be rolled back. The transaction has more states than "done".
The big picture#
Text description of this diagram
A sequence diagram of installing an APK. The installer creates a PackageInstaller session and writes the APK bytes. Commit hands the session to the package manager, which parses the manifest, verifies the signature against the expected signer, validates the split set and the SDK range, stages files into the app's install directory, creates the data directory with the app's UID, runs dexopt, and commits the package database. A broadcast then tells launchers and other apps, and the installer receives a committed callback.
The sequence is deliberately ordered so that nothing visible happens until everything is verified. That ordering is the security property.
Deep dive#
Sessions, not file copies#
The modern install path is PackageInstaller:
createSession()with a mode and parameters.openWrite()and stream the APK bytes.commit().
The session exists so the installer can prepare a complete, verified set before anything is installed. For split installs, every split must arrive before commit. For staged installs, the commit can be deferred to a later moment, which is how background updates and reboot-time installs work.1
What commit actually does#
Inside PackageManagerService, the commit path is roughly:
- Parse the manifest into a
PackageInfo-shaped object. - Verify the signature against the existing package (or the expected signer for a new install).
- Validate the split set: base present, no duplicate split names, signature consistent across splits.
- Check the SDK range and the declared features.
- Stage files into
/data/app/.... - Create
/data/data/<package>with the app's UID and the right SELinux label. - dexopt the DEX files with the device's compiler filter.
- Commit the package database and send
PACKAGE_ADDED.
Steps 7 and 8 are the ones users notice: dexopt is CPU-bound, and the broadcast is what makes the launcher show an icon.
Signature continuity#
The most important check is that an update is signed by the same key as the installed app. Without it, any app could replace any other app's data by claiming its package name.
The schemes:
- v1 signed each file; still used for very old devices.
- v2 signs the whole archive.
- v3 adds key rotation: the old key can authorise a new key, so an app can change signing keys without breaking updates.
- v4 adds a separate signature file used for incremental installation, so a device can verify parts of an APK as they arrive.
apksigner is the tool that produces these signatures and can verify them.2
dexopt and compiler filters#
DEX is not machine code. At install (and later, in the background), ART compiles or verifies it. The compiler filter decides how much work happens at install time:
verify— check the bytecode, do not compile.speed-profile— compile the methods in the profile, interpret the rest.speed— compile everything.
A device chooses a filter based on its class and the install context. The practical effect: an install that feels slow is often doing AOT compilation, and an install that feels instant will pay the cost at first launch instead.
Incremental install#
Android 11 (API 30) added incremental installation: the app can start running while its APK is still being streamed to the device. The v4 signature scheme is what makes this safe, because the device can verify chunks as they arrive rather than waiting for the whole file.3
The trade-off is that an incremental install is more fragile: if the stream is interrupted, the app may need to be re-fetched.
Sideloading and the tightening rules#
Installing from outside a store has become progressively more controlled:
- Android 8.0 (API 26) replaced the global "unknown sources" toggle with per-app permission:
each app that wants to install packages must be granted
REQUEST_INSTALL_PACKAGESand the user must allow it individually. - Android 13 (API 33) added restricted settings for sideloaded apps: apps installed from outside a store may not be able to enable accessibility services or notification listeners without an extra confirmation step.
The intent is not to prevent sideloading; it is to make the risky path visible. A user who installs an APK from a browser has made a choice, and the platform makes sure they know it.
adb install versus Play versus a file manager#
All three end up in the same commit path, but they differ in the session they create:
adb installuses a shell session with elevated privileges; it can install to the system partition on a debuggable build.- Play uses a session with Play App Signing and split delivery.
- A file manager uses a session as an ordinary app with
REQUEST_INSTALL_PACKAGES.
The differences are in who is allowed to commit and what signature is expected, not in the commit itself.
What can go wrong, and what it means#
| Error | Likely cause |
|---|---|
INSTALL_FAILED_UPDATE_INCOMPATIBLE | signature mismatch with the installed app |
INSTALL_FAILED_INSUFFICIENT_STORAGE | not enough space, including for dexopt |
INSTALL_FAILED_VERSION_DOWNGRADE | lower versionCode without -d |
INSTALL_FAILED_MISSING_SPLIT | a required split was not included |
INSTALL_PARSE_FAILED_MANIFEST_MALFORMED | binary manifest is invalid or corrupted |
Reading the error code is usually faster than reading the log.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 18 | 4.3 Jelly Bean |
|
| 23 | 6.0 Marshmallow |
|
| 28 | 9 Pie |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 37 | 17 Cinnamon Bun |
|
Under the hood#
- PackageManagerService.javathe install transaction
- PackageInstaller.javathe session API
- PackageParser.javamanifest parsing and verification
- dexopt orchestration
Curiosity corner#
- Installing does not start the app. Commit sends a broadcast; the launcher may update, but nothing runs until something starts a component.
- The data directory outlives the APK update. An update replaces code and resources, not
/data/data/<package>— which is why "update broke my login" is usually a migration bug, not an install bug. adb install -ris not a reinstall. It is an update: signature continuity still applies.
Try it#
adb install -r app-release.apk
adb shell pm list packages -f | grep example
adb shell dumpsys package com.example.app | head -40
adb shell pm path com.example.app
Example output
Performing Streamed Install
Success
package: com.example.app
versionCode=1 minSdk=26 targetSdk=34
pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ]
dataDir=/data/user/0/com.example.apppm path lists every installed split for the package, which is the quickest way to confirm that a
split install really produced several APKs.
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.
- docsAPK signature schemesource.android.comaccessed 2026-10-27↩
- docsapksignerdeveloper.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v4source.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v2source.android.comaccessed 2026-10-27↩
- docsAbout Android App Bundlesdeveloper.android.comaccessed 2026-10-27↩
- docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩
- docsPerubahan Perilaku Android 8.0developer.android.comaccessed 2026-10-27↩
- docsAndroid 10 features and APIsdeveloper.android.comaccessed 2026-10-27↩