JNI on Android: How callbacks work

Clint Paul
3 min readJan 15, 2022

--

https://unsplash.com/photos/QckxruozjRg?utm_source=unsplash&utm_medium=referral&utm_content=creditShareLink

The article was originally posted at clintpauldev.com

Hi, I hope everyone is doing great. I joined Sharechat last year, and the journey is super exciting so far. I’m able to learn tons of new things every day at work. So, sharing a unique topic that is interesting and challenging at the same time. That is JNI ( Java Native Interface ).

In simple words, what JNI does is that it will help us to communicate between two parties. The two parties are the code written in Kotlin/Java and the native code (C/C++). In most cases, interaction with the native code is not necessary. Why? Because the framework APIs and Java/Kotlin code can do the job. The need to reach out to native code is required when dealing with physical device components such as sensors and touch input, graphic related, or resuing our own or other C or C++ libraries. I had a similar encounter with the JNI. I needed to initiate a callback to the Kotlin/Java code when an operation is succeeded/failed at the native side.

Let’s look at the code

Imagine that we need to get a string as a callback from the native side. Let’s see how we can implement it.

Kotlin side code

fun registerErrorCallback(error: String) {
Log.e(TAG, “error description $error”)
}

As you can see, we are trying to get an error description as a callback. And it is a String data type.

CPP code

I think it’s better we gradually build this part and explain.

A typical Cpp class name:

JNIEXPORT void JNICALL Java_com_org_moduleA_moduleAsubModule_className_initSomething
(JNIEnv *env)

A small explanation of JNIEXPORT and JNICALL is that it helps the RegisterNatives find the functions at run time with the correct calling conventions. Finally, the java package name will be followed. Also, you can see that there is one parameter called JNIEnv. It helps us to access Java elements.

JNIEXPORT void JNICALL Java_com_org_moduleA_moduleAsubModule_className_initSomething
(JNIEnv *env)
{
jclass cls =
env->FindClass(“com/org/moduleA/moduleAsubModule/className”);
}

First, we need to find the class in which the callback method is defined at the Kotlin/Java side. As mentioned earlier, we use env to access a Java element called FindClass. It will load a locally defined class if you provide the correct path of the class.

JNIEXPORT void JNICALL Java_com_org_moduleA_moduleA_subModule_className_initSomething
(JNIEnv *env)
{
jclass cls =
env->FindClass(“com/org/moduleA/moduleA_subModule/className”);

jmethodID methodid =
env->GetMethodID(cls,"registerErrorCallback",
(Ljava/lang/String;)V");
}

Next, we need to find the method id. Here we need to provide a few things like the class name ( Which we obtained earlier ), the name of the callback method which we defined on the Kotlin side, and the signature. You might be confused seeing how the signature is defined here. Let’s break it down.

There are two parts to the signature. One which enclosed in the parenthesis is the method’s arguments. The second portion following the parenthesis is the return type. If you look into our callback method defined at the Kotlin side, you can see that it has one argument, which is a string type and no return type.

fun registerErrorCallback(error: String) {
Log.e(TAG, “error description $error”)
}

What’s inside the parenthesis is a string type ( method argument )

(Ljava/lang/String;)

What’s outside the parenthesis is a void type ( method return type )

(Ljava/lang/String;)V");

If you need more info about method signature, please check this link.

JNIEXPORT void JNICALL Java_com_org_moduleA_moduleA_subModule_className_initSomething
(JNIEnv *env)
{
jclass cls =
env->FindClass(“com/org/moduleA/moduleA_subModule/className”);

jmethodID methodid =
env->GetMethodID(cls,"registerErrorCallback",
(Ljava/lang/String;)V");
jstring jstr = env->NewStringUTF("Some callback"); env->CallVoidMethod(cls, methodid, jstr);
}

Finally, you call the CallVoidMethod that invokes an instance method with a void return type. We need to pass the class name, method id, and the actual arguments.

Test it out and see if everything is working as expected. If you have any doubts, please comment here. Also, if anyone knows how to pass a Kotlin delegate/lambda as an argument using the JNI, please answer here. I’m having doubts about how to implement it.

Thank you, all. Have a great day.

Follow me on LinkedIn.

--

--

Clint Paul

Software Engineer @ShareChat. I love to read and write.