The resources system
Resources are the reason one APK can serve a hundred languages, five densities and two themes. The mechanism is a compiled table plus a fixed precedence order — and you can learn it by playing with it.
Every Android app ships one resource system and uses it for everything: strings, images, layouts, colours, dimensions, styles. It is the mechanism that lets a single APK serve a hundred languages and five screen densities without a hundred APKs. Understanding it explains both a whole class of bugs and why App Bundles are possible.
TL;DR#
- A resource is identified by an integer ID, not a file path.
R.string.titleis a constant. - The ID encodes package (
0x7f), type, and entry index. That is why IDs are stable per build and why resource lookups are fast. resources.arscis the compiled table mapping IDs to values per configuration.res/values-fr/,res/drawable-xxhdpi/and friends are configuration variants, and the system chooses among them with a fixed precedence order.- RROs (runtime resource overlays) let a device replace another package's resources without rebuilding it — the mechanism OEMs use to theme Android without forking it.
Mental model#
Think of a library with one catalogue number per book, and several editions on the shelf.
The catalogue number is the resource ID. The editions are the configuration variants: the
French edition, the large-print edition, the night-mode edition. A reader asks for catalogue number
0x7f0b0012 and the librarian picks the edition that fits them.
The library does not search the shelves by title. It looks up the number in a card index
(resources.arsc) and takes the matching edition.
Where the analogy breaks#
A library has one reader at a time. Android has one process with one configuration at a time, but the
configuration can change while the app is running — a rotation, a locale change, a night-mode switch —
and the app is expected to re-read its resources. That is why Activity recreation exists.
The big picture#
Text description of this diagram
An interactive resource resolver. Choose a device locale, smallest width, night mode and density. Five steps show how Android narrows the candidate folders: first it eliminates any folder that names a value the device does not have, then it compares locale, then smallest width, then night mode, then density. The winning folder is shown for each configuration, together with the drawable that would be selected at that density.
Play with the configuration and watch which folder wins. The interesting cases are the ones where a
folder is eliminated rather than merely outranked — values-sw720dp/ cannot serve a 600dp device at
all, while values-fr/ merely loses to values-fr-rCA/.
Deep dive#
Resource IDs and why they start with 0x7f#
A resource ID is a 32-bit integer with three parts:
- the package byte (for an app's own resources,
0x7f), - a type byte (string, drawable, layout, and so on),
- an entry index.
The package byte is why 0x7f appears everywhere: it identifies the app's own resource package, while
the framework's resources use 0x01. When you see 0x7f0b0012 in a crash log, you can decode it: the
0x7f package, a type, and an index into that type's table.1
The important consequence is that resource IDs are assigned at build time, not looked up by name at
runtime. R.string.app_name is a compile-time constant. That is why resource lookups are cheap, and
why a missing resource is a build error rather than a runtime one.
resources.arsc: the table#
resources.arsc is the compiled resource table. For each resource ID it holds a set of
configurations and their values. A value can be:
- a string (in a shared string pool),
- a file path (for a drawable or layout),
- a primitive (dimension, colour, integer),
- a reference to another resource.
When the app calls getString(R.string.title), the framework:
- takes the ID,
- looks up the entry in the table,
- selects the best-matching configuration for the current device,
- reads the value.
No file system search, no XML parsing. That is why the design exists: the alternative — scanning
res/values-fr/strings.xml at runtime — would be far too slow.
Configuration qualifiers and precedence#
A qualifier is the suffix on a folder: -fr, -xxhdpi, -night, -sw600dp. You can combine them:
values-fr-sw600dp-night.
The matching algorithm has two phases:
Phase 1 — eliminate contradictions. If a folder names a configuration the device does not have,
it is discarded. values-fr/ cannot serve an English device. values-sw720dp/ cannot serve a 600dp
device, because the device's smallest width is smaller than the qualifier demands.
Phase 2 — compare survivors, in a fixed order. The order is defined by the platform and is roughly:
- MCC/MNC (carrier),
- locale (language, then region),
- layout direction,
- smallest width, then available width/height,
- screen size and aspect,
- round vs not-round,
- wide colour gamut, HDR,
- orientation,
- UI mode (night, car, desk, TV),
- density,
- touchscreen type,
- keyboard and navigation,
- platform version.
A folder that does not specify a qualifier is not eliminated at that step — it is simply deferred to a
later one. That is why values/ is always a valid fallback: it names nothing, so it can never
contradict, and it loses only when something more specific matches.
Density, and the scaling rule#
Density is special because it is the one qualifier that never eliminates a candidate. Every bitmap can be scaled. The platform prefers an exact density match, then the nearest density, and on a tie prefers the higher one — because downscaling looks better than upscaling.
nodpi is the exception: it means "do not scale this", so it is usable by any device and preferred
when present for that resource.
AAPT and AAPT2#
The resource compiler is AAPT2. Its job:
- compile each resource file into a binary form (
.flat), - link all of them, plus the manifest, into
resources.arscand the final APK, - assign IDs,
- generate the
Rclass.
AAPT2 replaced AAPT largely for incremental builds: compiling each resource separately means changing one string does not recompile the world.1
Overlays: how OEMs change Android without forking it#
A Runtime Resource Overlay (RRO) replaces resources in a target package at runtime. Instead of modifying the target APK, the overlay is a separate package that declares "for package X, resource Y should have value Z".
RROs are how:
- OEMs apply a theme or branding to AOSP apps without forking them,
- a device can ship a regional variant of a resource,
- an accessibility overlay can increase contrast or change sizing.
The overlay is enabled at runtime by the overlay manager, and the resource system resolves the overlay before the target. This is a Mainline-adjacent idea: it separates what the app is from how it looks on this device.2
Resources and App Bundles#
The configuration system is what makes App Bundles work. Because a resource is already tagged with the configurations it serves, Play can generate a split that contains only the density, ABI and language resources a device needs. Without qualifiers, every APK would have to contain everything.
That is the deep connection between this page and the next: the resource system is the reason per-device delivery is possible at all.3
How it evolved#
| API | Version | What changed |
|---|---|---|
| 1 | 1.0 |
|
| 4 | 1.6 Donut |
|
| 5 | 2.0 Eclair |
|
| 8 | 2.2 Froyo |
|
| 9 | 2.3 Gingerbread |
|
| 11 | 3.0 Honeycomb |
|
| 13 | 3.2 Honeycomb |
|
| 14 | 4.0 Ice Cream Sandwich |
|
| 16 | 4.1 Jelly Bean |
|
| 17 | 4.2 Jelly Bean |
|
| 18 | 4.3 Jelly Bean |
|
| 19 | 4.4 KitKat |
|
| 20 | 4.4W KitKat (Wear) |
|
| 21 | 5.0 Lollipop |
|
| 22 | 5.1 Lollipop |
|
| 23 | 6.0 Marshmallow |
|
| 24 | 7.0 Nougat |
|
| 26 | 8.0 Oreo |
|
| 27 | 8.1 Oreo |
|
| 28 | 9 Pie |
|
| 29 | 10 |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 32 | 12L |
|
| 33 | 13 Tiramisu |
|
| 34 | 14 Upside Down Cake |
|
| 35 | 15 Vanilla Ice Cream |
|
| 36 | 16 Baklava |
|
| 37 | 17 Cinnamon Bun |
|
Under the hood#
- ResourceTypes.cppthe resource table implementation
- Resources.javathe app-facing lookup API
- the overlay manager
- the resource compiler and linker
Curiosity corner#
- Changing a resource ID is an ABI break. An app that caches IDs, or a split that references them, can break if IDs shift between builds.
0x7fis not magic, it is a package index. The framework's own resources use0x01.- A missing translation is not an error. If
values-fr/has no entry, the lookup falls back tovalues/— which is why partially translated apps show a mix of languages.
Try it#
aapt2 dump resources app-release.apk | head -60
aapt2 dump configurations app-release.apk
adb shell cmd overlay list
Example output
Package name: com.example.app
type 0 configCount=4 entryCount=120
config fr (values-fr)
config fr-rCA (values-fr-rCA)
config night (values-night)
config (default)aapt2 dump resources prints the table the platform reads. cmd overlay list shows which RROs are
installed and enabled on the device — often a longer list than people expect.
Build it#
Add a values-fr/strings.xml with one translated string, then switch the device language and watch
the app fall back to English for every other string. That mixed state is the resource system working
as designed.
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 resources overviewdeveloper.android.comaccessed 2026-10-27↩
- docsRuntime resource overlays (RROs)source.android.comaccessed 2026-10-27↩
- docsModular system componentssource.android.comaccessed 2026-10-27↩
- docsAPK signature schemesource.android.comaccessed 2026-10-27↩
- docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩