Is it possible to write mobile applications for Android on classic C languageif official documentation recommended Kotlin or Java? The answer is yes, and this opens up unique opportunities for creating high-performance native applications. Unlike a virtual machine Dalvik/ART, C code is compiled directly into machine instructions, which is critical for game engines, multimedia processing or computer vision algorithms.
However, development in C for Android requires a special approach: this is not possible without Native Development Kit (NDK) โa toolkit from Googlethat connects native code with the Java/Kotlin part of the application via JNI (Java Native Interface). In this article we will analyze the entire process: from setting up the environment to optimizing and publishing the application in Google Play. You will learn when to choose C instead Kotlin, how to avoid common mistakes when working with JNI, and why some popular applications (for example, WhatsApp or games on Unity) actively use native code.
Why C for Android: pros and cons cons
The main advantage of C is performance. Native code runs 2โ10 times faster than bytecode Java/Kotlin, which is critical for:
- ๐ฎ Game engines (Unreal Engine, Cocos2d-x)
- ๐น Processing video/audio real-time (filters, codecs)
- ๐ค Machine learning (inference models TensorFlow Lite)
- ๐ Cryptographic operations (encryption, hashing)
But there are also pitfalls:
- โ ๏ธ Difficulty of debugging: Errors in native code often lead to the crash of the entire application (
SIGSEGV). - ๐ Double support: You will have to write both the native part in C and the โwrapperโ in Java/Kotlin.
- ๐ฆ Increasing the size of the APK: Libraries
.sofor different architectures (armeabi-v7a,arm64-v8a) take up a lot of space.
โ ๏ธ Attention: Google does not recommend using NDK for regular applications. If your task is a simple UI or working with an API, it is better to stay with Kotlin. for resource-intensive tasks.
Required tools: what to install
To start development, prepare the following:
| Tool | Version (2026) | Purpose |
|---|---|---|
| Android Studio | Giraffe or later | Main IDE with NDK support |
| Android NDK | r26+ | Compiler clang, header files, utilities |
| CMake | 3.22.1+ | Build system for native libraries |
| LLDB | Built into NDK | Debugger for native code |
Install them via Android Studio:
- Open
Settings โ Appearance & Behavior โ System Settings โ Android SDK. - Go to the tab
SDK Toolsand markNDKandCMake. - Click
Applyand wait for the download (~1.5 GB).
โ ๏ธ Attention: If you use Windows, make sure that the paths to NDK and the project do not contain spaces or Cyrillic characters. This may break the build.
โ๏ธ Preparing the environment
Creating the first project with C support
Let's start with a basic project that outputs a message from native code to Androidapplication.
- In Android Studio create a new project with a template
Native C++(File โ New โ New Project โ Native C++). - In file
MainActivity.kt(or.java) find the methodstringFromJNI()- this is a bridge between Java and C. - Open
native-lib.cppin the foldercpp. There is already a code example here:
#include <jni.h>#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(
JNIEnv* env,
jobject / this /) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
The assembled application will display the text "Hello from C++" on the screen. This example demonstrates:
- ๐ Java โ C binding via
JNI. - ๐ Function name format:
Java___. - ๐ Conversion types:
std::string โ jstring.
โ ๏ธ Attention: Function names in JNI are case sensitive and must exactly match the method signature in Java/Kotlin. An error in one character will lead to java.lang.UnsatisfiedLinkError.
Use the utility javah (or -Xjni v Kotlin) to automatically generate header files .h with the correct JNI function signatures.
Working with JNI: data transfer between Java and C
JNI is a bridge between managed code (Java/Kotlin) and native (C/C++). Let's consider the main data exchange scenarios:
1. Passing primitive types
Primitives (int, float) are passed directly:
// Javapublic native int sum(int a, int b);
// C
JNIEXPORT jint JNICALL
Java_com_example_myapp_MainActivity_sum(JNIEnv* env, jobject obj, jint a, jint b) {
return a + b;
}
2. Working with strings
Strings (jstring) require conversion:
// Javapublic native String concat(String s1, String s2);
// C
JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_concat(JNIEnv* env, jobject obj, jstring s1, jstring s2) {
const char *str1 = env->GetStringUTFChars(s1, NULL);
const char *str2 = env->GetStringUTFChars(s2, NULL);
std::string result = std::string(str1) + std::string(str2);
env->ReleaseStringUTFChars(s1, str1);
env->ReleaseStringUTFChars(s2, str2);
return env->NewStringUTF(result.c_str());
}
3. Arrays and buffers
For arrays, use Get<Type>ArrayElements:
// Javapublic native int[] sortArray(int[] arr);
// C
JNIEXPORT jintArray JNICALL
Java_com_example_myapp_MainActivity_sortArray(JNIEnv* env, jobject obj, jintArray arr) {
jint *body = env->GetIntArrayElements(arr, NULL);
jsize length = env->GetArrayLength(arr);
// Sorting (for example, std::sort)
env->ReleaseIntArrayElements(arr, body, 0);
return arr;
}
โ ๏ธ Attention: Always free resources after working with JNIobjects (call Release... methods). Memory leaks in native code lead to unstable operation of the application.
What is JNIEnv?
JNIEnv is a pointer to a structure with methods for interacting with the JVM. Through it you call functions to work with Java objects (creating strings, arrays, etc.). Each thread has its own JNIEnv, and it cannot be transferred between threads!
Optimization and debugging of native code
Native code requires a special approach to optimization and error diagnosis. Here are the key tools:
1. Profiling with Android Profiler
In Android Studio open View โ Tool Windows โ Profiler and select:
- ๐ CPU Profiler โ to analyze the execution time of native functions.
- ๐๏ธ Memory Profiler โ to search for leaks in native memory.
2. Debugging with LLDB
To set a breakpoint in native code:
- Build the project in mode
Debug. - Launch the application on the device/emulator.
- Open Android Studio open
Run โ Attach Debugger to Android Processand select your application.
3. Logging
Use __android_log_print to display logs:
#include <android/log.h>#define LOG_TAG "MyNativeApp"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
void myFunction() {
LOGD("This message will appear in Logcat!");
}
View logs through adb logcat with a filter by tag:
adb logcat MyNativeApp:D *:S
Critical: Native crashes (SIGSEGV, SIGABRT) do not fall into Google Play Console like normal exceptions. To catch them, integrate with NDK support or use Crashlytics with NDK support or use signal() to intercept signals. Debugging native code is more difficult than Java: always test on real devices (the emulator can hide problems with the architecture). APK to Google Play
Debugging native code is more difficult than Java: always test on real devices (the emulator may hide architectural problems).
Build and publish: from APK to Google Play
When building a project with native code Android Studio automatically:
- Compiles C code into
.solibraries for each architecture (armeabi-v7a,arm64-v8a,x86). - Packages libraries in
lib/insideAPK. - Connects libraries to the application via
System.loadLibrary().
To reduce the size APK, configure build.gradle:
android {defaultConfig {
ndk {
// Build only for 64-bit architectures
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
Before publishing in Google Play:
- ๐ Check compatibility with
64-bitarchitectures (mandatory from 2019). - ๐ฆ Make sure that
APKdoes not exceed 150 MB (download limit). - ๐ก๏ธ Add to
AndroidManifest.xmlresolutionandroid:extractNativeLibs="true"(if you need to extract libraries when installation).
โ ๏ธ Attention: Google Play can block the application if it is built only for x86 (this is suspicious, since most devices are on ARM). Always turn on at least arm64-v8a.
Examples of real applications in C
Many popular applications use native code for critical tasks:
| Application | Task in C/C++ | Reason |
|---|---|---|
| Message encryption (Signal Protocol) | High speed of processing cryptographic operations | |
| VLC for Android | Video/audio decoding | Using libraries FFmpeg and libVLC |
| PUBG Mobile | Game engine (Unreal Engine 4) | Maximum rendering performance |
| Google Chrome | Engine Blink and JavaScript (V8) | Cross-platform and optimization |
If you are developing such an application, study the sources of open projects:
adb logcat | ndk-stack -sym ./obj/local/arm64-v8a-->
FAQ: Frequently asked questions about development in C for Android
Is it possible to write the entire application in C, without Java/Kotlin?
Technically yes, but it is extremely inconvenient. Android required Java/Kotlin for:
- Creation
Activity/Service(main components of the application). - Works with UI (XML-markup, Jetpack Compose).
- Interactions with Android API (camera, sensors, notifications).
Native code is used only for heavy calculations, and the application logic is written in Java/Kotlin.
Which compiler is used in NDK: GCC or Clang?
Since 2022 Google has completely switched to Clang as the main compiler in NDK. GCC is no longer supported. Clang provides:
- Stronger type control.
- Better compatibility with C++17/20.
- Optimizations for
ARM64.
How to reduce the size of .so libraries?
Several ways:
- Use
-Osor-Ozinstead of-O2inCMakeLists.txtto optimize for size. - Remove unnecessary symbols using
strip --strip-unneeded libnative-lib.so. - Build only for the architectures you need (for example, only
arm64-v8a). - Use
-ffunction-sections -fdata-sections -Wl,--gc-sectionsto remove unused code.
Can I use C++ instead of C?
Yes, and this is even preferable! NDK fully supports C++including:
- Exceptions (
try/catch). - STL (
std::string,std::vector). - RTTI (
dynamic_cast,typeid).
Example CMakeLists.txt for C++:
cmake_minimum_required(VERSION 3.22.1)project("my-app")
add_library(native-lib SHARED native-lib.cpp)
find_package(log-log LOG REQUIRED)
target_link_libraries(native-lib log)
How to update NDK to the latest version?
Via Android Studio:
- Open
Settings โ Appearance & Behavior โ System Settings โ Android SDK. - Go to the tab
SDK Tools. - Uncheck the current version NDK, check the new one and click
Apply.
Or via the command line:
sdkmanager "ndk;26.1.10909125"
โ ๏ธ After the update, clear the build cache (Build โ Clean Project) and rebuild the project.