Mainline and APEX
Treble made the vendor side replaceable. Mainline made the platform side replaceable too. The container is the APEX, and the delivery channel is the Play Store.
Treble's insight was that the vendor image and the system image should be separable. Mainline's insight is that the system image should not be one thing either: half of it changes for reasons that have nothing to do with the other half.
TL;DR#
- arrived with Android 10 (API 29Android 10): individual platform components ship as modules that can be updated without an OS release.
- Two formats: an for app-shaped modules, and an for components that are libraries, binaries or services.
- An APEX is a signed ZIP containing an ext4 image, mounted by
apexdat boot through a loopback device and dm-verity. - Updates are staged: the new version is recorded, activated on the next reboot, and can be rolled back.
- Delivery is , which reuses the Play Store infrastructure and installs an atomic bundle of modules.
Mental model#
Think of a shipping port where the ship used to be one welded hull.
Treble cut the hull into a cargo section and an engine section that could be swapped separately. But the cargo section was still one welded block: repainting a container meant dry-docking the whole thing.
Mainline replaces the cargo section with containers on a deck. Each container has its own manifest, its own contents and its own lock. You can lift one out and put a newer one in without touching the others — and if the new one is wrong, you can put the old one back.
Where the analogy breaks#
A container is lifted off and replaced while the ship is docked. An APEX is replaced while the ship is
sailing: the new version is installed to /data, then activated on the next reboot. The running
system never sees the file change underneath it, which is what makes the update safe and also what
makes it require a restart.
The big picture#
Text description of this diagram
A timeline of Mainline modules by the API level of the release in which they first shipped. API 29 shipped Conscrypt, DNS Resolver, Media, Media Codecs, Time Zone Data, Network Stack, DocumentsUI, PermissionController and ExtServices. API 30 added the ADB daemon, CellBroadcast, IPsec, MediaProvider, NNAPI, SDK Extensions, Statsd and Wi-Fi. API 31 added ART and Device Scheduling. API 33 added AdServices, AppSearch, Bluetooth, On-Device Personalization and UWB. API 34 added Health, Config Infrastructure and Remote Key Provisioning. The gaps show that modules were added as the tooling matured, not all at once.
Deep dive#
What a module is#
A is a piece of the platform with its own version, its own owner and its own update cadence. The definition is organisational as much as technical: a module exists because someone can own its release independently.
The first cohort, at API 29, was chosen for a specific reason: they were all things that parse untrusted input or change frequently.
| Module | Why it is a module |
|---|---|
| Conscrypt | TLS parsing, reachable from the network |
| DNS Resolver | Every hostname lookup, including DNS-over-TLS |
| Media / Media Codecs | Container and codec parsing; the Stagefright lesson |
| Time Zone Data | Rules change several times a year |
| Network Stack | DHCP and captive portal detection |
| PermissionController | Permission UI and policy, which changes with privacy rules |
| DocumentsUI | An app, so it ships as an APK |
That table is a good summary of the selection criteria: security-critical parsers, fast-changing data, and user-facing policy. A module that changes rarely and is deeply entangled with the framework is a poor candidate, because the boundary costs more than it saves.
APEX: the container#
An is "Android Pony EXpress", and it is a valid APK in the structural sense:
a signed ZIP with an AndroidManifest.xml. Inside are four things:
apex_manifest.json— the package name and version.AndroidManifest.xml— a minimal manifest so APK tooling (adb, PackageManager, aapt) can handle it.apex_payload.img— an ext4 image containing the actual files, with a dm-verity hash tree and vbmeta metadata created withlibavb.apex_pubkey— the public key used to verify the payload signature.
The payload is the point. An APEX is not a bag of files extracted into the system; it is a filesystem image mounted read-only. That is what lets a library live at a stable path with a versioned mount point.
The kernel requirements follow from that: the loopback driver and dm-verity must be enabled. AOSP
documents CONFIG_BLK_DEV_LOOP=Y and CONFIG_DM_VERITY=Y as required, with kernel 4.4+ supported
and 4.9+ required for devices launching with Android 10.
Why not just an APK?#
An APK is designed for app code: DEX, resources, a manifest, and a process model where the package is loaded by a runtime. An APEX exists for things that do not fit that model:
- A native library that other processes
dlopen. - A binary that
initstarts, such asadbd. - A service that runs as its own process.
- ART itself, which is the runtime every app depends on.
You cannot put libcrypto.so in an APK and have system_server link against it, because the APK's
files are not on a filesystem path the linker can see until the app is loaded. An APEX is mounted as
a filesystem, so its contents are just files at paths.
Staged updates and rollback#
The update flow is deliberately conservative:
- The APEX is downloaded through PackageManager, adb or another installer.
- PackageManager recognises the APEX and hands it to
apexd. apexdverifies it and records it in its internal database for activation at the next boot.- The system reboots.
- At boot,
apexdverifies and mounts the new version.
The consequence is that an APEX update always costs a reboot, unlike an app update. The exception is
supportsRebootlessUpdate, which allows an update without a reboot when the installed APEX is not in
use.
Updates live in /data. A built-in APEX in /system/apex is shadowed by a newer one in /data, but
the built-in copy remains as the fallback. That is the rollback story in its simplest form: the old
version is still on the read-only partition.
The module set, by release#
The module list grew in waves. The table below is a cut of this site's module dataset, showing the first release and the format.
| Module | Package | Format | First API | Why it is a module |
|---|---|---|---|---|
| Conscrypt | com.android.conscrypt | apex | 29 | TLS provider; a network-reachable parser |
| DNS Resolver | com.android.resolv | apex | 29 | Every hostname lookup, including DNS-over-TLS |
| Media | com.android.media | apex | 29 | Extractors and MediaSession APIs; hostile input |
| Media Codecs | com.android.media.swcodec | apex | 29 | Software Codec2 components |
| Time Zone Data | com.android.tzdata | apex | 29 | Rules change several times a year |
| Network Stack | com.android.tethering | apex | 29 | DHCP, captive portal, tethering |
| DocumentsUI | com.android.documentsui | apk | 29 | The file picker is an app |
| PermissionController | com.android.permissioncontroller | apk | 29 | Permission UI and policy |
| ExtServices | com.android.ext.services | apk-in-apex | 29 | Text classification; APK in 10, APEX in 11 |
| Module Metadata | com.android.modulemetadata | apk | 29 | Describes the installed module set |
| ADB daemon | com.android.adbd | apex | 30 | Device-side adbd, updated with host tooling |
| CellBroadcast | com.android.cellbroadcast | apex | 30 | Emergency alerts, regulatory changes |
| IPsec / IKEv2 | com.android.ipsec | apex | 30 | VPN tunnel library |
| MediaProvider | com.android.mediaprovider | apex | 30 | MediaStore and scoped storage |
| NNAPI Runtime | com.android.neuralnetworks | apex | 30 | ML accelerator mediation |
| SDK Extensions | com.android.sdkext | apex | 30 | New SDK levels on older platforms |
| Statsd | com.android.os.statsd | apex | 30 | Metrics collection; parses pushed config |
| Wi-Fi | com.android.wifi | apex | 30 | Wi-Fi framework and supplicant interaction |
| ART | com.android.art | apex | 31 | The runtime itself: native libs and binaries |
| Device Scheduling | com.android.scheduling | apex | 31 | Background work scheduling policy |
| Ad Services | com.android.adservices | apex | 33 | Privacy Sandbox APIs, evolving design |
| AppSearch | com.android.appsearch | apex | 33 | On-device search index |
| Bluetooth | com.android.btservices | apex | 33 | Over-the-air-reachable parser |
| UWB | com.android.uwb | apex | 33 | Radio stack, independent update cadence |
| Health | com.android.healthfitness | apex | 34 | Health Connect; sensitive-data privacy rules |
| Config Infrastructure | com.android.configinfrastructure | apex | 34 | aconfig flag delivery |
| Remote Key Provisioning | com.android.rkpd | apex | 34 | Attestation key provisioning |
Delivery and atomicity#
Modules ship through Play System Update, which reuses Play Store infrastructure. The user sees a monthly update alongside app updates; the device installs a bundle of modules and activates them on reboot.
The atomicity is the important property: a bundle either applies entirely or not at all. That avoids the failure mode where three of five modules update and the combination is broken in a way none of the three teams tested.
What cannot be modularised#
Some things are still not modules, and the reasons are instructive:
- The kernel — that is GKI's job, and it is a different mechanism.
system_serveritself — too entangled with the boot sequence and too large to containerise.- The HAL implementations — those belong to the vendor and move with the vendor image.
- Parts of the framework with deep native dependencies — Bionic, for example, cannot be swapped under a running system.
The boundary of Mainline is roughly "code that can be loaded or started after init and does not
define the ABI everything else links against".
How it evolved#
Four releases tell the story: Android 9 laid the groundwork, Android 10 shipped the first cohort and the APEX format, Android 11 added Wi-Fi and adbd, and Android 12 added ART — the point at which the runtime itself became updatable. Everything after that is expansion.
Under the hood#
- apexd and the APEX tooling
- the source tree for Mainline modules; each subdirectory is one module
- the ART module packaging
- apexd.rcwhen apexd starts and what it depends on
Curiosity corner#
- An APEX is a valid APK. It has a manifest and is signed as one, so
adb installcan hand it to PackageManager, which then hands it toapexd. /apexcontains versioned mount points. Looking at it on a device shows the versioned directory and the bind mount, which is the clearest way to see how shadowing works.- ART as a module means the runtime can change without a platform release. That is the single biggest performance-relevant update channel Android has.
Try it#
adb shell ls /apex
adb shell dumpsys package com.android.art | head -20
adb shell cmd apex list 2>/dev/null | head -20
/apex lists the mounted modules by name. The versioned directories are the actual mount points;
the unversioned ones are bind mounts to the active version.
Check yourself#
Check yourself
What was the core technical barrier that kept Android phones on old software before 2017?
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.
- docsModular system componentssource.android.comaccessed 2026-10-27↩
- docsAPEX file formatsource.android.comaccessed 2026-10-27↩
- docsART mainline modulesource.android.comaccessed 2026-10-27↩
- docsMedia mainline modulesource.android.comaccessed 2026-10-27↩
- docsConscrypt mainline modulesource.android.comaccessed 2026-10-27↩
- docsADB mainline modulesource.android.comaccessed 2026-10-27↩
- docsPermission controller mainline modulesource.android.comaccessed 2026-10-27↩
- docsGeneric Kernel Image (GKI)source.android.comaccessed 2026-10-27↩
- docsAndroid Verified Boot (AVB)source.android.comaccessed 2026-10-27↩