← Back to all posts

02 Xpense

How Android Apps Actually Work

Xpense Dev Retrospective


Why This Matters

Before getting into Xpense's actual code, it's worth knowing what's happening underneath it. Not to sound smart, but because half the weird bugs and design choices later in this series only make sense once you know what Android is doing with your app behind the scenes.


What Android Actually Is

Your phone is a full computer. CPU, RAM, storage, an operating system running all of it. Android is that operating system, and every app, including Xpense, runs on top of it under Android's rules.

Think of Android as a building manager and Xpense as a tenant. The tenant can't break through walls or wander into other tenants' rooms. It can't touch the camera, contacts, or storage on its own, it has to ask the building manager (Android) for permission first. That's the entire reason permission dialogs exist.


The Layers Xpense Sits On

Android is stacked in layers, each one depending on the one below it.

Layer 4 - The App Xpense. The Kotlin code I wrote, the XML layouts, the logic that makes it Xpense and not some other app.
Layer 3 - Android Framework The tools Android hands you for free. TextView, RecyclerView, SharedPreferences, Intent, all the classes used without writing them from scratch.
Layer 2 - Android Runtime (ART) The engine that actually executes the code on the device, turning it into instructions the CPU understands.
Layer 1 - Linux Kernel The real operating system underneath everything. Memory, hardware drivers, processes, security. Never touched directly.

The point of this stack: I wrote code at Layer 4 using tools from Layer 3. Layers 1 and 2 handled memory management and pixel rendering automatically. That's what a framework buys you, none of that had to be written by hand.


From Kotlin to Something the Phone Can Run

Kotlin doesn't run directly on a phone. It has to go through a pipeline first, and Xpense's code goes through the exact same one every other Android app does.

StepWhat Happens
1. Source codePlain .kt files, the code actually written and read
2. Kotlin compilerConverts it to JVM-compatible bytecode
3. Android toolchainConverts that bytecode into DEX format, built for devices with limited memory
4. PackagingDEX files, compiled resources, and the manifest get bundled into an APK
5. ARTRuns the DEX on the actual device. The CPU executes it. The app appears on screen

Worth knowing: Xpense started out written in Java, not Kotlin. Java goes through this exact same pipeline, just with javac instead of the Kotlin compiler at step 2. Both land on the same DEX format. Android genuinely doesn't care which language produced it, which is part of why the later rewrite to Kotlin didn't require touching how the app fundamentally works.


What's Actually Inside an APK

An APK isn't some special mystery format. It's a ZIP file with a different extension. Rename Xpense's APK to xpense.zip, open it, and there's a normal folder structure inside.

Inside the APKWhat it holds
classes.dexAll the compiled code, in DEX format
AndroidManifest.xmlThe app's identity and permissions, compiled to binary
res/Compiled layouts, images, colors, strings
resources.arscA table mapping resource IDs to actual resources
META-INF/The digital signature proving the APK came from me

Every App Lives in Its Own Sandbox

When Xpense is tapped open, Android doesn't just "start" it. It spins up a new isolated process, hands it its own slice of memory, and runs it there, completely separate from every other app on the phone.

Each app is a locked room in an office building. Xpense can't read another app's data, and another app can't read Xpense's. If Xpense genuinely needs to talk to something outside its room, like opening a link in a browser, it has to go through a formal request, which on Android is called an Intent.

This is also why closing the app doesn't always mean Android kills it right away. It gets parked in memory in case it's reopened, and only gets killed off if the phone actually needs the memory back.


Debug Builds vs Release Builds

Two kinds of Xpense APK exist. A debug build, made with assembleDebug, signed automatically with a temporary key Android generates, used only for testing on my own device. And a release build, made with assembleRelease, signed with my own personal keystore, which is the actual version that goes out to real users on the Play Store.

Important detail: a debug build and a release build of the same app are treated as two completely different apps if their signatures don't match. You can't install a release APK as an "update" over a debug install. Signing gets its own full post later in this series, since losing a release keystore is one of the few genuinely unrecoverable mistakes in Android development.


Closing

That's the foundation. Next up, actually opening Xpense's project folder and going through what every file and directory in it is for, and why it's organized the way it is.

← Previous 01 - What Xpense Actually Is Next → 03 - Xpense's Project Structure