i'm the developer of rkmovies, a 4k media player that ships as the tiny android apk page (1.17 mb) and the windows setup page (29.5 mb), with the web app on my little project site. people keep asking how an apk that plays 4k stays under 2 mb, so i ran apkanalyzer on my own release build and read the breakdown. this is what's inside.
the honest headline: there's almost nothing in it. a universal apk has four big buckets: classes.dex (your code), res/ + resources.arsc (layouts, strings, images), lib/ (native .so per abi), and META-INF/ (signatures). mine has no lib/ at all — zero native libraries — because the app is a webview shell that hands decoding to the device's mediacodec. that single decision is the whole size story.
dex was the first fight. r8 full mode + shrinkResources on the release build strips unreachable code and unused resources. the rule i live by now: features don't cost size, dependencies do. every library i add is measured with apkanalyzer -h apk file-size before and after. one transitive dependency once added 300 kb quietly — the compare view in apk analyzer caught it.
resources were the second fight. vector drawables instead of pngs, webp where a raster is unavoidable, and localeFilters so i don't ship string tables for languages the app doesn't have. resources.arsc is sneaky — it holds every string of every shipped locale, and libraries drag their own in.
abi splits are the classic trap. each native lib ships one copy per abi (arm64, armeabi-v7a, x86, x86_64) — 4x the bytes for one feature. play store solves this with app bundles; outside the store you either split per abi or filter with ndk { abiFilters }. i ship no .so, so i skipped the whole problem, and i keep useLegacyPackaging = false habits in mind for when i do add native code: uncompressed, page-aligned .so loads straight from the apk instead of double-copying to disk.
what's left is overhead you can't remove: the manifest, the dex header, the v2 signature block. at 1.17 mb those are a rounding error.
the general pattern: measure with apkanalyzer first, cut the biggest bucket, repeat. size budgets work like money budgets — you don't save by skipping coffee, you save by knowing where the rent goes.
if you're building small: start with what the os gives you, add a dependency only when you've measured the wall, and keep the analyzer in your release routine.