我在博客上发表一些我的NDK学习心得,希望对大家能有帮助。 这一篇我们讲述如何动态注册native方法
首先,之前写的文章中通过一个简单的例子来使用了一下NDK,实现了从Native中调用Java方法。
下面,我们要介绍的是实现动态绑定native方法来破除命名限制。
在静态注册的情况,所有的方法都是有固定的方法名:Java_<包名> <类名> <方法名>,这种情况下,调用一个方法比较繁琐,同时也有命名限制,所以使用动态绑定来解决这个问题。
首先,还是老样子,加载so,定义native方法
public class MyJni {
static {
System.loadLibrary("myjni");
}
public native void nativePrint();
public native int nativeCount(int a, int b);
public MyJni() {
Log.d("123", "Load MyJni nativePrint & nativeCount");
nativePrint();
int a = nativeCount(1,2);
Log.d("123", a+"");
}
}
为了在程序初始化的时候进行方法的动态注册,当so被加载时就会调用JNI_OnLoad函数,需要重写JNI_OnLoad方法,在该方法中注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return -1;
}
//===========================================
assert(env != NULL);
if (!registerNatives(env)) // 注册本地方法
{
return -1;
}
//===========================================
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
}
调用registerNatives进行注册,其中需要指定注册的类信息,这样才能够获取其jclass对象
/*
* 为所有类注册本地方法
*/
static int registerNatives(JNIEnv* env) {
const char* kClassName = "com/example/qiuyu/testhellojni/MainActivity";//指定要注册的类
return registerNativeMethods(env, kClassName, gMethods,
sizeof(gMethods) / sizeof(gMethods[0]));
}
调用registerNativeMethods,使用RegisterNatives进行注册
/*
* 为某一个类注册本地方法
*/
static int registerNativeMethods(JNIEnv* env
, const char* className
, JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
需要定义gMethods JNINativeMethod数组,实际就是修改指针指向执行的函数,从而实现方法注册
// 该结构表示一个Java本地方法与native层的c方法的映射
typedef struct {
char *name; // Java本地方法名称
char *signature; // Java本地方法签名
void *fnPtr; // c函数指针
} JNINativeMethod;
/**
* Table of methods associated with a single class.
*/
static JNINativeMethod gMethods[] =
{
{"nativePrint", "()V", (void*)native_Print },
{"nativeCount", "(II)I", (void*)native_Count },
};
// 实际执行函数
void native_Print(JNIEnv * env, jobject thiz) {
LOGE("native_Print");
}
jint native_Count(JNIEnv * env, jobject thiz, jint a, jint b) {
return a+b;
}
最终执行结果如下:
07-05 04:52:17.739 23202-23202/com.example.qiuyu.testhellojni D/123: Load MyJni nativeSetup
07-05 04:52:17.739 23202-23202/com.example.qiuyu.testhellojni E/jni_thread: native_Print
07-05 04:52:17.739 23202-23202/com.example.qiuyu.testhellojni D/123: 3
注:classname千万不能写错,写错之后会出现java.lang.NoClassDefFoundError错误
注:如果使用ProGuard进行混淆,很可能会找不到native方法,这个要注意
Github : https://github.com/QyMars/AndroidNativeCode
通过动态注册,可以避免硬编码,更加灵活,下面一章学习Native线程使用,之后要完成如何Native中启动一个线程来进行签名校验判断。