Overview
Android is a layered software stack. Each layer depends only on the layer below it, which lets hardware vendors and OEMs customize lower layers without breaking app compatibility. From bottom to top: Linux Kernel → HAL → Native Libraries + ART → Java/Kotlin Framework → Applications.
Understanding this stack matters when you optimize performance, debug native crashes, or reason about IPC costs and security boundaries.
Layer 1: Linux Kernel
Android runs on a modified Linux kernel (currently based on LTS kernels such as 5.10 / 6.1). Key customizations:
- Binder IPC driver —
/dev/binder. The single most important Android-specific addition. All system service calls pass through it. - Ashmem / memfd — anonymous shared memory for zero-copy data sharing.
- Low Memory Killer (LMK) — an in-kernel driver that kills processes based on
oom_adjscore when free memory is low (now moved to userspace LMKD on modern kernels). - Wakelocks — prevent CPU or display from sleeping during critical operations.
- ION memory allocator — contiguous memory allocation for GPU and camera.
The kernel provides process isolation: every Android app runs in its own Linux process with a unique UID, giving security at the OS level.
Layer 2: Hardware Abstraction Layer (HAL)
HAL lives between the kernel drivers and the Android framework. It exposes stable C/C++ interfaces so the framework can work with hardware without knowing driver specifics.
Android 8 (Treble project) introduced HIDL HALs (run in separate processes), and Android 11+ introduced AIDL HALs. This separation means:
- HAL processes (
vendorpartition) can be updated independently of the framework (systempartition). - A camera HAL crash does not crash the system server.
Common HALs: Camera, Audio, Bluetooth, GPS, Sensors, Graphics (Gralloc, EGL), Fingerprint.
System Server (Java) ──Binder──► Camera HAL process ──ioctl──► kernel driver
Layer 3: Native Libraries & ART Runtime
Bionic libc
Android's C library. Smaller than glibc, Apache-licensed. Provides malloc, pthreads, math. No execinfo.h; stack unwinding uses libunwind.
Key native libraries
| Library | Purpose |
|---|---|
| OpenGL ES / Vulkan | GPU rendering |
| SQLite | Embedded relational database |
| WebKit / Chromium | WebView rendering |
| OpenSSL / Conscrypt | TLS |
| Skia | 2D graphics used by Canvas |
| libc++ | C++ standard library |
Android Runtime (ART)
ART is the managed runtime for Java/Kotlin code. It replaced Dalvik in Android 5.
- AOT compilation (
dex2oat) —.dexbytecode → native.oatfile at install time (or device idle time on Android 7+). - JIT + Profile-Guided Optimization (PGO) — hot methods identified at runtime, background-compiled to native code. See the ART vs Dalvik article for depth.
- GC — concurrent, generational. GC pauses are short but not zero; see the Memory Management article.
ART runs in the app's process. Each app has its own ART instance forked from Zygote.
Layer 4: Java / Kotlin Framework
This is what most Android developers interact with. The framework is implemented as Java/Kotlin classes compiled to DEX, running on ART in the System Server process and in each app process.
Key framework services (all running in System Server)
| Service | Responsibility |
|---|---|
ActivityManagerService (AMS) | Activity/task lifecycle, process management |
WindowManagerService (WMS) | Window layout, input dispatch, rotation |
PackageManagerService (PMS) | APK install/query, intent resolution |
InputManagerService | Touch, key events |
PowerManagerService | Wakelocks, screen on/off |
ConnectivityService | Network state |
NotificationManagerService | Notifications, channels |
Apps talk to these services exclusively through Binder. The Java stub you call (e.g., getSystemService(Context.ACTIVITY_SERVICE)) marshals arguments into a Parcel, sends it over Binder to the server process, and blocks waiting for the reply.
Context
Context is the gateway to framework services and resources. Application context lives as long as the process; Activity context ties to the Activity lifecycle. Using Activity context for long-lived objects (singletons, static fields) is the classic memory leak.
Layer 5: Application Layer
Apps ship as APKs (zip archives containing classes.dex, resources, AndroidManifest.xml, native .so files). The package manager installs them under /data/app/.
Each app is assigned a unique Linux UID (app_NNNN). The app's data directory (/data/data/com.example.app/) is owned by that UID.
How an App Launch Crosses All Layers
User taps icon
│
▼
Launcher calls startActivity(intent)
│ (Binder call to AMS)
▼
ActivityManagerService (System Server)
│ decides process doesn't exist
│ sends socket message to Zygote
▼
Zygote forks new process
│ (Linux fork() + CoW pages)
▼
New process: ActivityThread.main()
│ attaches to AMS via Binder
▼
AMS sends bindApplication() transaction
│
▼
App: Application.onCreate()
│
▼
AMS sends launchActivity() transaction
│
▼
App: Activity.onCreate() → onStart() → onResume()
│
▼
SurfaceFlinger composites first frame
(crosses into kernel via Binder/ioctl to GPU driver)
Every arrow that crosses a process boundary is a Binder transaction with serialization cost.
IPC Overview
Android uses Binder for nearly all IPC. Key properties:
- 1 MB transaction limit per process pair (actually 1 MB – 8 bytes shared across all threads). Sending large Bitmaps over Binder throws
TransactionTooLargeException. - Synchronous by default — calling thread blocks until the remote returns.
- Thread pool — each process has a Binder thread pool (default 15 + 1 threads) to serve incoming calls.
- Security — kernel stamps each transaction with the calling UID/PID; the server can call
Binder.getCallingUid()to check permissions.
For large data, use SharedMemory (ashmem) or FileDescriptor passed over Binder instead of raw bytes.
Practical Gotchas
- Never do Binder calls on the main thread in release code. They can block if the server is busy, causing ANRs. Enable
StrictMode.ThreadPolicyto catch this in debug builds. - Context leaks — storing Activity context in a static field or singleton keeps the entire Activity (and its View tree) alive after it is destroyed.
- Native library ABI — if your app ships
.sofiles, list all supported ABIs inbuild.gradle(abiFilters). A mismatched ABI causesUnsatisfiedLinkErrorat runtime. - HAL version mismatch — on older devices, a HAL your code depends on may not be present or may behave differently. Always check
PackageManager.hasSystemFeature()before using hardware features. - System Server is single-process — a crash there reboots the phone. Avoid contributing to it; keep AIDL interfaces minimal and robust.
Summary
| Layer | Process | Language | Examples |
|---|---|---|---|
| Kernel | kernel space | C | Binder driver, LMK, wakelocks |
| HAL | vendor processes | C/C++ | Camera HAL, Audio HAL |
| Native libraries | app process | C/C++ | Bionic, Skia, SQLite |
| ART | app process | — | GC, AOT/JIT, DEX execution |
| Framework | System Server + app | Java/Kotlin | AMS, WMS, PMS |
| Application | app process | Java/Kotlin | Your code |
Knowing which layer a problem lives in — a GC pause vs. a Binder delay vs. a HAL bug — determines where to look in the profiler and how to fix it.