Use this file to discover all available pages before exploring further.
The Java Native Interface (JNI) is the bridge that allows Java and Kotlin code to call and be called by native C/C++ code. It’s the fundamental mechanism that enables NDK development on Android.
// Java String to C stringjstring jstr = /* from Java */;const char* cstr = (*env)->GetStringUTFChars(env, jstr, NULL);// Use cstr...(*env)->ReleaseStringUTFChars(env, jstr, cstr);// C string to Java Stringconst char* message = "Hello from native";jstring result = (*env)->NewStringUTF(env, message);return result;// For large strings, use direct buffer accessconst jchar* chars = (*env)->GetStringCritical(env, jstr, NULL);jsize len = (*env)->GetStringLength(env, jstr);// Process chars...(*env)->ReleaseStringCritical(env, jstr, chars);
Array operations
// Access primitive arrayjintArray arr = /* from Java */;jsize len = (*env)->GetArrayLength(env, arr);jint* elements = (*env)->GetIntArrayElements(env, arr, NULL);// Modify elementsfor (int i = 0; i < len; i++) { elements[i] *= 2;}// Commit changes back to Java array (0 = copy back and free)(*env)->ReleaseIntArrayElements(env, arr, elements, 0);// Create new arrayjintArray newArr = (*env)->NewIntArray(env, 10);jint buffer[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};(*env)->SetIntArrayRegion(env, newArr, 0, 10, buffer);
Object operations
// Find classjclass stringClass = (*env)->FindClass(env, "java/lang/String");// Get field ID and access fieldjfieldID fid = (*env)->GetFieldID(env, clazz, "count", "I");jint value = (*env)->GetIntField(env, obj, fid);(*env)->SetIntField(env, obj, fid, value + 1);// Call methodjmethodID mid = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;");jstring result = (*env)->CallObjectMethod(env, obj, mid);// Create objectjmethodID constructor = (*env)->GetMethodID(env, clazz, "<init>", "()V");jobject newObj = (*env)->NewObject(env, clazz, constructor);
Exception handling
// Check for exceptionsjstring str = (*env)->NewStringUTF(env, "text");if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionDescribe(env); // Print to logcat (*env)->ExceptionClear(env); return NULL;}// Throw exceptionjclass exceptionClass = (*env)->FindClass(env, "java/lang/IllegalArgumentException");(*env)->ThrowNew(env, exceptionClass, "Invalid argument");return NULL; // Must return from native method
JNIEXPORT jobject JNICALLJava_Example_createObject(JNIEnv* env, jobject thiz) { jclass clazz = (*env)->FindClass(env, "java/lang/String"); // clazz is a local reference, automatically freed when method returns jstring str = (*env)->NewStringUTF(env, "hello"); // Can explicitly free to save memory in long methods // (*env)->DeleteLocalRef(env, clazz); return str; // Return value is caller's responsibility}
Local references are freed when the native method returns. Do not store them for later use. The JVM has a limit on local references (typically 512).
// Anti-pattern: JNI overhead dominatesexternal fun add(a: Int, b: Int): Intfun sumArray(arr: IntArray): Int { var sum = 0 for (value in arr) { sum = add(sum, value) // JNI call per element! } return sum}
// Better: Single JNI callexternal fun sumArray(arr: IntArray): Intfun processData(arr: IntArray): Int { return sumArray(arr) // One JNI call, bulk processing}
jbyte* data = (*env)->GetPrimitiveArrayCritical(env, jarray, NULL);if (data != NULL) { // CRITICAL: No JNI calls allowed here! // GC may be disabled, must be very fast process_data(data, length); (*env)->ReleasePrimitiveArrayCritical(env, jarray, data, 0);}
Between GetPrimitiveArrayCritical and ReleasePrimitiveArrayCritical, do not: