JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
memset(&g_ctx, 0, sizeof(g_ctx));
g_ctx.javaVM = vm;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
jclass clz =
(*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);
jmethodID jniHelperCtor =
(*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
queryRuntimeInfo(env, g_ctx.jniHelperObj);
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
上面是hello-jniCallback项目中的原始文件hello-jnicallback.c中相关代码,为了慢慢学习知识点,我自己没有使用hello-jnicallback.c文件,而是自己创建了自己的学习文件,hello_jni.h 跟hello_jni.c.
根据原始sample的中的文件,大概代码可以简化为如下所示,返回0
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
LOGI("enter JNI_OnLoad");
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
{
LOGE("Failed to get GetEnv");
return JNI_ERR;
}
LOGI("leave JNI_OnLoad");
return 0;
}
但是运行程序之后,程序发生了错误,通过LOG信息可以发现,错误提示是错误的Jni 版本
估计是这里返回值是要返回一个JNI的对应版本,尝试了下返回JNI_VERSIOn_1_2,JNI_VERSIOn_1_4,?JNI_VERSIOn_1_6都没有问题
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
LOGI("enter JNI_OnLoad");
JNIEnv* env;
......
LOGI("leave JNI_OnLoad");
return JNI_VERSION_1_2;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
memset(&g_ctx, 0, sizeof(g_ctx));
g_ctx.javaVM = vm;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR; // JNI version not supported.
}
jclass clz =
(*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);
jmethodID jniHelperCtor =
(*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
queryRuntimeInfo(env, g_ctx.jniHelperObj);
g_ctx.done = 0;
g_ctx.mainActivityObj = NULL;
return JNI_VERSION_1_6;
}
void queryRuntimeInfo(JNIEnv *env, jobject instance) {
// Find out which OS we are running on. It does not matter for this app
// just to demo how to call static functions.
// Our java JniHelper class id and instance are initialized when this
// shared lib got loaded, we just directly use them
// static function does not need instance, so we just need to feed
// class and method id to JNI
jmethodID versionFunc = (*env)->GetStaticMethodID(
env, g_ctx.jniHelperClz, "getBuildVersion", "()Ljava/lang/String;");
if (!versionFunc) {
LOGE("Failed to retrieve getBuildVersion() methodID @ line %d", __LINE__);
return;
}
jstring buildVersion =
(*env)->CallStaticObjectMethod(env, g_ctx.jniHelperClz, versionFunc);
const char *version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
if (!version) {
LOGE("Unable to get version string @ line %d", __LINE__);
return;
}
LOGI("Android Version - %s", version);
(*env)->ReleaseStringUTFChars(env, buildVersion, version);
// we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
(*env)->DeleteLocalRef(env, buildVersion);
// Query available memory size from a non-static public function
// we need use an instance of JniHelper class to call JNI
jmethodID memFunc = (*env)->GetMethodID(env, g_ctx.jniHelperClz,
"getRuntimeMemorySize", "()J");
if (!memFunc) {
LOGE("Failed to retrieve getRuntimeMemorySize() methodID @ line %d",
__LINE__);
return;
}
jlong result = (*env)->CallLongMethod(env, instance, memFunc);
LOGI("Runtime free memory size: %" PRId64, result);
(void)result; // silence the compiler warning
}
?java中的JniHandler的相关方法如下
public class JniHandler {
private static final String TAG = "hello-jniCallback";
/*
* Print out status to logcat
*/
@Keep
private void updateStatus(String msg) {
if (msg.toLowerCase().contains("error")) {
Log.e("JniHandler", "Native Err: " + msg);
} else {
Log.i("JniHandler", "Native Msg: " + msg);
}
}
/*
* Return OS build version: a static function
*/
@Keep
static public String getBuildVersion() {
return Build.VERSION.RELEASE;
}
/*
* Return Java memory info
*/
@Keep
public long getRuntimeMemorySize() {
return Runtime.getRuntime().freeMemory();
}
}
Log信息如下:
通过上面的代码分析:这里有两种方法的调用,一种是CallStaicObjectMethod方法,另外一种是CalllongMethod方法,以为我之前接触过一点JNI的知识,了解JNI跟java的交互分为对象的调用跟类的调用
经过代码分析,大概的调用流程如下所示
jclass clz =
(*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
g_ctx.jniHelperClz = (*env)->NewGlobalRef(env, clz);
jmethodID jniHelperCtor =
(*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
g_ctx.jniHelperObj = (*env)->NewGlobalRef(env, handler);
jstring buildVersion =
(*env)->CallStaticObjectMethod(env, g_ctx.jniHelperClz, versionFunc);
const char *version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
if (!version) {
LOGE("Unable to get version string @ line %d", __LINE__);
return;
}
LOGI("Android Version - %s", version);
(*env)->ReleaseStringUTFChars(env, buildVersion, version);
// we are called from JNI_OnLoad, so got to release LocalRef to avoid leaking
(*env)->DeleteLocalRef(env, buildVersion);
代码修改为如下
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
LOGI("enter JNI_OnLoad");
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
{
LOGE("Failed to get GetEnv");
return JNI_ERR;
}
LOGI("leave JNI_OnLoad");
jclass clz = (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
jobject objRef = (*env)->NewGlobalRef(env, clz);
jmethodID versionFun = (*env)->GetStaticMethodID(env, objRef, "getBuildVersion", "()Ljava/lang/String;");
jstring buildVersion = (*env)->CallStaticObjectMethod(env, objRef, versionFun);
const char* version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
if (version == NULL)
{
LOGE("Failed to get version");
return JNI_ERR;
}
LOGI("java build version - %s", version);
return JNI_VERSION_1_6;
}
经过测试,这样是没有问题的,
但是上面有一个NewGlobalRef的调用通过jni.h的查询发现,jclass跟jobject其实是同一个类型void*
准备测试下,如果不调用这个这个NewGlobalRef能不能正常运行,通过这个方法描述,这个好像只是一个全局的引用,修改为如下所示:
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
LOGI("enter JNI_OnLoad");
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
{
LOGE("Failed to get GetEnv");
return JNI_ERR;
}
LOGI("leave JNI_OnLoad");
jclass clz = (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
jmethodID versionFun = (*env)->GetStaticMethodID(env, clz, "getBuildVersion", "()Ljava/lang/String;");
jstring buildVersion = (*env)->CallStaticObjectMethod(env, clz, versionFun);
const char* version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
if (version == NULL)
{
LOGE("Failed to get version");
return JNI_ERR;
}
LOGI("java build version - %s", version);
return JNI_VERSION_1_6;
}
经过测试发现,没有任何问题
总结:JNI调用java中的静态方法的流程是
通过跟上面调用JNI调用静态方法的对比,猜测调用对象方法,应该是一样的
在?java中的JniHandler中添加一个新方法,如下所示
@Keep
public String getBuildVersion2() {
return "BuildVersion2";
}
新的代码如下所示
但是发现报错了,错误提示信息如下
JVM object referenced by 'clz' is of type 'Class<JniHandler>' and it does not have access to method 'getBuildVersion2()' declared in class 'JniHandler'.?
猜测是类型不匹配,这里应该是一个jobject类型,而不是jclass类型?,在上面的步骤中,不是可以通过调用NewGlobalR来获取jobject类型吗,修改如下,
信心满满,这下应该没有问题吧,现实是残酷的,啪啪打脸.c代码发生crash了
?再对比原始的方法,发现有一段这样的代码调用
jmethodID jniHelperCtor =
(*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V");
jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
应该需要通过先调用java的非静态方法,然后再根据生成的jmethodID去调用NewObject.开始编写代码.大概是如下所示
在编写过程中, 在传入参数,versionFun的时候, Android Studio报错了,提示
Not a constructor.??
不是一个构造期,难道需要跟java反射一样吗.需要搞一个构造器,再次去看了下原来的代码,发现有如下几行代码
jmethodID jniHelperCtor = (*env)->GetMethodID(env, g_ctx.jniHelperClz, "<init>", "()V"); jobject handler = (*env)->NewObject(env, g_ctx.jniHelperClz, jniHelperCtor);
根据名字猜测JnihelperCtor,这个难道是需要调用构造器吗,立马行动起来,修改如下
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
LOGI("enter JNI_OnLoad");
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK)
{
LOGE("Failed to get GetEnv");
return JNI_ERR;
}
LOGI("leave JNI_OnLoad");
jclass clz = (*env)->FindClass(env, "com/example/hellojnicallback/JniHandler");
jmethodID ctor = (*env)->GetMethodID(env, clz, "<init>", "()V");
jobject obj = (*env)->NewObject(env, clz, ctor);
jmethodID versionFun = (*env)->GetMethodID(env, clz, "getBuildVersion2", "()Ljava/lang/String;");
jstring buildVersion = (*env)->CallObjectMethod(env, obj, versionFun);
const char* version = (*env)->GetStringUTFChars(env, buildVersion, NULL);
if (version == NULL)
{
LOGE("Failed to get version");
return JNI_ERR;
}
LOGI("java build version - %s", version);
return JNI_VERSION_1_6;
}
?运行测试,发现可以了
考虑了下,调用流程应该跟java的反射一样,需要调用构造器函数,调用流程如下