Native call Java
- NativeCallJavaTest.kt
package com.practice
class NativeCallJavaTest {
external fun test()
init {
System.loadLibrary("native-call-java-lib")
}
}
Call the member methods
- Counter.kt
package com.practice
class Counter {
private var count = 0
fun add() {
println("Counter#add(): old count is $count")
count++
}
fun count(): Int {
println("Counter#count(): $count")
return count
}
}
- native-call-java-lib.cpp
#include <jni.h>
#include <android/log.h>
#include <iostream>
#define TAG "counter"
jint getCount(JNIEnv *env, jclass cls, jobject obj) {
jmethodID methodId = env->GetMethodID(cls, "count", "()I");
return env->CallIntMethod(obj, methodId);
}
void addCount(JNIEnv *env, jclass cls, jobject obj) {
jmethodID methodId = env->GetMethodID(cls, "add", "()V");
env->CallVoidMethod(obj, methodId);
}
extern "C" JNIEXPORT void JNICALL
Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject thiz) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "test()");
// new object
jclass cls = env->FindClass("com/practice/Counter");
jmethodID constructId = env->GetMethodID(cls, "<init>", "()V");
jobject obj = env->NewObject(cls, constructId);
// get count
__android_log_print(ANDROID_LOG_DEBUG, TAG, "count is %d", getCount(env, cls, obj));
// add count
addCount(env, cls, obj);
__android_log_print(ANDROID_LOG_DEBUG, TAG, "count is %d", getCount(env, cls, obj));
}
Call the static method of companion object
- Counter.kt
class Counter {
companion object {
@JvmStatic
fun getName(): String {
return "Kotlin static method is called."
}
}
}
- native-call-java-lib.cpp
jstring getName(JNIEnv *env) {
jclass cls = env->FindClass("com/practice/Counter");
if (cls == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "Counter/$Companion is not found");
}
jmethodID methodId = env->GetStaticMethodID(cls, "getName", "()Ljava/lang/String;");
if (methodId == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "Counter.getName is not found");
}
return (jstring) env->CallStaticObjectMethod(cls, methodId);
}
extern "C" JNIEXPORT void JNICALL
Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
jstring jName = getName(env);
const char *name = env->GetStringUTFChars(jName, 0);
__android_log_print(ANDROID_LOG_DEBUG, TAG, "name is %s", name);
env->ReleaseStringUTFChars(jName, name);
}
Call the static method from singleton
- Singleton.kt
package com.practice
object Singleton {
@JvmStatic
fun getValue(input: Int): Int = input * 2
}
- native-call-java-lib.cpp
jint getValue(JNIEnv *env, jint input) {
jclass cls = env->FindClass("com/practice/Singleton");
if (cls == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "Singleton class not found");
}
jmethodID methodId = env->GetStaticMethodID(cls, "getValue", "(I)I");
if (methodId == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "Singleton.getValue(int) not found");
}
return env->CallStaticIntMethod(cls, methodId, input);
}
extern "C" JNIEXPORT void JNICALL Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "value from Singleton is %d", getValue(env, 3));
}
No such method error
When I remove the @JvmStatic
annotation from Counter.getName()
, I will get below error.
D/counter: Counter.getName is not found
A/art: art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: JNI CallStaticObjectMethodV called with pending exception java.lang.NoSuchMethodError: no static method "Lcom/practice/Counter;.getName()Ljava/lang/String;"
Call the method of nested class (also named static nested class in Java)
- Counter.kt
class Counter {
class Comparator(val num: Int) {
fun isSameAs(num: Int): Boolean {
return num == this.num
}
}
}
- native-call-java-lib.cpp
jboolean isSameAs(JNIEnv *env, jclass cls, jobject obj, jint num) {
jmethodID methodId = env->GetMethodID(cls, "isSameAs", "(I)Z");
return env->CallBooleanMethod(obj, methodId, num);
}
extern "C" JNIEXPORT void JNICALL Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) {
// new inner class
jclass cls = env->FindClass("com/practice/Counter$Comparator");
if (cls == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "can not find inner class.");
}
jmethodID constructId = env->GetMethodID(cls, "<init>", "(I)V");
if (constructId == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of inner class.");
}
jobject obj = env->NewObject(cls, constructId, 3);
if (obj == nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, TAG, "can not create instance of inner class.");
}
// isSameAs 3
__android_log_print(ANDROID_LOG_DEBUG, TAG, "Is 3 same as the number in inner class? %d", isSameAs(env, cls, obj, 3));
}
Call the method of inner class (non-static nested class in Java)
Inner class can access the member of outer class.
Outer.kt
package com.practice class Outer { private val bar: Int = 1 inner class Inner { fun getBar() = bar } }
native-call-java-lib.cpp
jobject createOuterInstance(JNIEnv *env) { jclass cls = env->FindClass("com/practice/Outer"); if (cls == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not find outer class."); } jmethodID constructId = env->GetMethodID(cls, "<init>", "()V"); if (constructId == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of outer class."); } return env->NewObject(cls, constructId); } jobject createInnerInstance(JNIEnv *env, jclass innerCls, jobject outerObj) { jmethodID innerConstructId = env->GetMethodID(innerCls, "<init>", "(Lcom/practice/Outer;)V"); if (innerConstructId == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of inner class."); } return env->NewObject(innerCls, innerConstructId, outerObj); } jint getBar(JNIEnv *env, jclass cls, jobject obj) { jmethodID methodId = env->GetMethodID(cls, "getBar", "()I"); return env->CallIntMethod(obj, methodId); } extern "C" JNIEXPORT void JNICALL Java_com_practice_NativeCallJavaTest_test(JNIEnv *env, jobject) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "test()"); // New Outer class jobject obj = createOuterInstance(env); if (obj == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not create instance of outer class."); } // New Inner class jclass innerCls = env->FindClass("com/practice/Outer$Inner"); if (innerCls == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not find inner class."); } jobject innerObj = createInnerInstance(env, innerCls, obj); if (innerObj == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not create instance of inner class."); } __android_log_print(ANDROID_LOG_DEBUG, TAG, "value in inner class is %d", getBar(env, innerCls, innerObj)); }
No such method error
D/native: can not get construct of inner class.
A/zygote: java_vm_ext.cc:504] JNI DETECTED ERROR IN APPLICATION: JNI NewObjectV called with pending exception java.lang.NoSuchMethodError: no non-static method "Lcom/practice/Outer$Inner;.<init>()V"
native-call-java-lib.cpp
jobject createInnerInstance(JNIEnv *env, jclass innerCls, jobject outerObj) { jmethodID innerConstructId = env->GetMethodID(innerCls, "<init>", "()V"); // wrong here! if (innerConstructId == nullptr) { __android_log_print(ANDROID_LOG_DEBUG, TAG, "can not get construct of inner class."); } return env->NewObject(innerCls, innerConstructId); // also wrong here! }
I can use javap
to check the signature of inner class constructor.
$ javap -s -p com/practice/Outer\$Inner.class
Compiled from "Outer.kt"
public final class com.practice.Outer$Inner {
final com.practice.Outer this$0;
descriptor: Lcom/practice/Outer;
public final int getBar();
descriptor: ()I
public com.practice.Outer$Inner();
descriptor: (Lcom/practice/Outer;)V // That's it!
}
It still has the second wrong.
E/zygote: JNI ERROR (app bug): accessed stale Global 0xd67b6e72 (index 224900839 in a table of size 507)
A/zygote: java_vm_ext.cc:504] JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0xd67b6e72
Inner object will hold the reference of outer object, so I have to pass the outer instance to the constructor of inner class even it has no parameter.
Nested Classes of The Java™ Tutorials @Oracle