Android NDK 系列(4) — SO 中调用 Java 方法,我在博客上发表一些我的 NDK 学习心得,希望对大家能有帮助。
这一篇我们讲述如何在 so 中调用 java 层的函数
首先,之前写的文章中通过一个简单的例子来使用了一下 NDK,编写了调用 so 中方法,返回一个字符串的功能,该方法是从 Java 层调用 Native 方法。
下面,我们要介绍的是如何从 Native 中调用 Java 方法。
问题废话不多说,直接开始。
首先,Java 方法简单可以分为两种,静态方法和非静态方法。
先给一个 Java 类,其中包含上述两种方法
- public class MyJni { ... private static void getvalue1(int value) { Log.d("123", "" + value); } private void getvalue2(int value) { Log.d("123", "" + value); }}
需要实现从 静态 & 非静态 Native 方法中调用上述两个方法。
实践在 jni.h 中定义了 Call***Method() 函数,通过这些函数,我们可以从 Native 中调用 Java 函数。
- // 静态函数jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list);jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, jvalue*);jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, // 普通函数jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...) __NDK_FPABI__;jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list) __NDK_FPABI__;jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*) __NDK_FPABI__;void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
在 jni.h 中定义了很多,大家可以去看一下。我这边放上的是截取其中一部分。
看到上面的函数,主要区别在与返回值和参数不同,所以定义了很多不同的函数。在我们的例子中,使用的是如下两个:
- void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
在使用上述函数前,我们要对了解其中的参数:
JNIEnv * 是指向 vm 的一个指针,通过该指针,我们可以调用到 vm 提供的很多功能。
jobjec 是表示调用函数的对象实例,即上面的 new MyJni() 实例
jclass 是表示函数所在类的类信息,即上面的 MyJni 类
jmethodID 是表示需要调用的 Java 方法 ID
其他就还有方法参数信息
为了获取上述调用的所有的参数,首先调用初始化函数
- myJni = new MyJni();// MyJni.java:public MyJni() { Log.d("123", "Load MyJni nativeSetup"); nativeSetup(); // 生成对象的时候调用初始化函数}public native void nativeSetup();
- // 定义全局变量jclass m_class;jobject m_object;jmethodID m_getValue, m_getV;// 初始化函数JNIEXPORT void JNICALL Java_com_example_qiuyu_testhellojni_MyJni_nativeSetup (JNIEnv *env, jobject thiz) { // 初始化中存储相应变量 jclass clazz = (*env)->GetObjectClass(env, thiz); //获取当前对象的类信息 m_class = (jclass)(*env)->NewGlobalRef(env,clazz); //将类型信息存储到m_class中 m_object = (jobject)(*env)->NewGlobalRef(env,thiz); // 将对象信息存储到m_object中 m_getV = (*env)->GetMethodID(env,m_class,"getvalue2","(I)V"); // 根据类信息、方法名、参数返回值找到方法ID m_getValue = (*env)->GetStaticMethodID(env,m_class,"getvalue1", "(I)V"); // 根据类信息、方法名、参数返回值找到方法ID return;}
使用两种方法调用 Java 方法,一种静态 native:nativeStaticExec, 另一种是非静态 native:nativeExec
- JNIEXPORT void JNICALL Java_com_example_qiuyu_testhellojni_MyJni_nativeExc (JNIEnv *env, jobject thiz, jint value) { (*env)->CallStaticVoidMethod(env,m_class,m_getValue,value); (*env)->CallVoidMethod(env,m_object,m_getV,value); return;}/* * Class: com_example_qiuyu_testhellojni_MyJni * Method: nativeStaitcExec * Signature: (I)V */JNIEXPORT void JNICALL Java_com_example_qiuyu_testhellojni_MyJni_nativeStaitcExec (JNIEnv *env, jclass clasz, jint value) { (*env)->CallStaticVoidMethod(env,m_class,m_getValue,value); (*env)->CallVoidMethod(env,m_object,m_getV,value); return;}
在 Java 层调用上面两个 native 方法
源码
- public void onClick(View v) { switch (v.getId()) { case R.id.button: Log.d("123", "nativeStaitcExec"); MyJni.nativeStaitcExec(11); // Static break; case R.id.button1: Log.d("123", "nativeExec"); myJni.nativeExec(10); // normal break; }}
Github : https://github.com/QyMars/AndroidNativeCode
总结这样,实现了简单 C 调用 Java 方法,这种还是很好用的,比如在 C 中调用获取应用签名函数,并在 Native 层中创建线程来进行签名校验,关于这个可以在下一篇中讲述。 由于手机不在身边,也没有实际的截图,代码我会放到我的 Github 上,给大家共享一下。
最近感触很多,还是得把心沉下来,好好学习技术。争取学的深一些,能多发一些干货给大家。
来源: