We have all seen .so files and ABI folders in an Android project, and for a long time I never looked beyond making them work. Here I will try to unpack the story behind ABI and .so files.
What is a .so file?
A .so file (shared object) is a pre-compiled native library — low-level machine code generated from C, C++, or Rust — that the CPU executes directly.
┌─ compile for ARM64 → arm64-v8a/sdk_name.so
│
native_cpp.cpp ─────┼─ compile for ARM32 → armeabi-v7a/sdk_name.so
native_rs.rs │
├─ compile for x86_64 → x86_64/sdk_name.so
│
└─ compile for x86 → x86/sdk_name.soPart 1: Why use C/C++/Rust in an Android codebase
There are a few common reasons:
- To do the heavy lifting. C, C++, and Rust are compiled, low-level languages, which makes them a good fit for image processing, map rendering, game engines, and video decoding.
- Better performance, with predictable memory use.
- Common codebase across platforms (Android, iOS, Web, Desktop).
So whenever we need C, C++, or Rust in the codebase, we compile it and package the output as .so files, one per ABI.
Part 2: Why do we need ABI and .so files?
Part 1 covered why we reach for native code. This part is about how that code reaches a device.
Phones use different processors, such as ARM and x86, and each understands a different instruction set. (Yes, the Microprocessors chapter we skipped in college is back. 😅)
Kotlin code is compiled to DEX, and ART turns that into machine code on the device, so one build runs everywhere. Native code has no such step: it must be compiled ahead of time for each processor type.
Each processor target has its own ABI — arm64-v8a, armeabi-v7a, x86, x86_64 — and compiling for each one produces a separate .so. At runtime, ART loads the matching .so, and Kotlin talks to it through JNI.
Why the ART point matters
Kotlin → DEX bytecode → ART compiles it on the device → CPU
C++ → .so (already machine code) → CPU directly
↑
ART only loads it and connects it via JNIKotlin gets compiled on the device by ART, so one build works everywhere. C++ has no compiler on the device, so you must compile it ahead of time for every CPU type you want to support.
Are ABI and .so files always present in an Android app?
Not exactly. If you are not using any native code, there may be no .so files of your own. But you still have to check your dependencies: an SDK can bring native libraries in without you writing a line of C++.
Use either of these to see whether your APK contains .so files:
a) From the terminal
zipinfo -1 app-test.apk | grep '\.so$'b) From Android Studio
Open Build → Analyze APK and look under lib/:
lib/
├── arm64-v8a/sdk_name.so
├── armeabi-v7a/sdk_name.so
├── x86/sdk_name.so
└── x86_64/sdk_name.soPart 3: Android ABI (Application Binary Interface)
An ABI — application binary interface — is the full agreement that compiled code follows for a specific CPU type: the instruction set, the calling convention, how data is laid out in memory, how symbols are named, and how the binary talks to the system.
On Android, there are four ABIs (official reference):
| ABI folder | CPU family | Where you meet it |
|---|---|---|
arm64-v8a | 64-bit ARM | Nearly every phone made in the last decade |
armeabi-v7a | 32-bit ARM | Older and budget devices |
x86_64 | 64-bit Intel / AMD | Emulators, some Chromebooks |
x86 | 32-bit Intel | Older emulators |
Asking an ARM chip to run x86 code is like reading a French menu to a dog. It is not being difficult. It genuinely cannot.
Part 4: Create your own C++ module
If you create a module from the Android Studio C++ template, you will see something like this in your Gradle file:
app/build.gradle.kts
android {
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.22.1"
}
}
}If you pin version, make sure that CMake version is installed in your SDK. Otherwise, drop the line and let Gradle use the default.
When you build the app, the compiled .so lands here:
Build output
app/build/intermediates/cxx/Debug/<hash>/obj/<abi>/myapplication.soBy default, only the ABI that matches the device or emulator is loaded at runtime.
The command adb shell getprop ro.product.cpu.abi retrieves the primary CPU Application Binary Interface (ABI) — the architecture of a connected Android device.
adb shell getprop ro.product.cpu.abiIf you want to generate .so files for every ABI, run:
./gradlew :app:assembleDebugYou will get one directory per ABI:
Build output
app/build/intermediates/cxx/Debug/<hash>/obj/
├── arm64-v8a/myapplication.so
├── armeabi-v7a/myapplication.so
├── x86/myapplication.so
└── x86_64/myapplication.soTo build for specific architectures only, use abiFilters:
app/build.gradle.kts
android {
defaultConfig {
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}
}externalNativeBuild is the required part: it tells Gradle to compile your C++ at all. abiFilters is optional: it narrows the list of ABIs that get built and packaged.