前言
这几天在研究android的源码时,顺便看了它的jni部分,发现它的注册本地方法的方式和自己记录的笔记不一样,而且还用了一个叫JNINativeMethod结构体。不懂。通过各种百度和Google,找到了关于这方面知识的介绍。之前的两篇文章介绍的都是通过静态方式注册本地方法,而android源码中采用地是动态注册,那么问题来了,采用动态注册有什么优势?或者说采用静态注册有什么弊端?
在邓凡平著的《深入理解Android(卷1)》一书中有对采用静态注册本地方法的弊端详细地分析:
1、需要编译所有声明了native方法的Java类,每个所生成的class文件 都得用javah生成一个头文件。
2、javah生成的JNI层函数名特别长,书写起来很不方便。
3、初次调用native函数时要根据函数名字搜索对应用JNI层函数来建立关联关系,这样会影响运行效率。
动态注册原理
上文中有提到一个构念:JNINativeMethod。它是一个结构体,用来记录Java native方法和JNI函数一一对应的关系,在jni.h中可以看到这个结构体定义的方式:
typedef struct {
//Java中native方法的名称,不用携带包的路径
const char* name;
//Java方法的签名信息,用字符串表示,是参数类型和返回值类型的组合
const char* signature;
//JNI层对应函数的函数指针,注意它是void*类型
void* fnPtr;
} JNINativeMethod;
JNI层代码
根据android源码中注册方式,依葫芦画瓢,用动态注册的方式来比较在java中和c中对数组进行插入排序效率的比较
-
写好本地方法
public class JNIUtil { public native void insertSortArray(int[] data); } -
JNI层对应的排序方法
void native_insertSortArray(JNIEnv *env, jobject obj, jintArray array) { jint* data = (*env)->GetIntArrayElements(env,array,NULL); jsize len = (*env)->GetArrayLength(env,array); insertSort(data, len); (*env)->ReleaseIntArrayElements(env,array,data,0);} -
c中数组插入排序函数
void insertSort(int *data, int len) { int temp = 0; int i = 1; int j = 0; for (; i < len; i++) { temp = data[i]; j = (i - 1); for (; j >= 0; j--) { if (data[j] > temp) { data[j + 1] = data[j]; } else { break; } } data[j + 1] = temp; } } -
定义一个JNINativeMethod结构体数组,其成员就是JNIUtil中所有native方法的一一对应关系
static JNINativeMethod gMethods[] = { { "insertSortArray", "([I)V", //方法的签名 (void*) native_insertSortArray } };
注意:在获取自己定义的本地方法的签名信息时,需先找到这个本地方法所在类的.class文件,位置在app/build/intermediates/classes,然后用javap命令就能获取到方法的签名信息
-
注册本地方法
static int registerNativeMethod(JNIEnv *env) { //由于JNINativeMethod结构体中使用的函数名并非全路径,所以要指明是哪个类 jclass clazz = (*env)->FindClass(env, "com/pbl/jni/dynamic/enroll/JNIUtil"); //调用JNIEnv的RegisterNatives函数,注册关联关系 jint result = (*env)->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])); return result; } -
当Java层通过System.loadLibrary加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad的函数。
jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv *env = NULL; // if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { // return -1; // } if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } assert(env != NULL); if (registerNativeMethod(env)<0) { return -1; } return JNI_VERSION_1_4;//必须返回这个值,否则报错 }
注意:由于android源码是用c++写的,用的是被注释了的代码,而我这里用的是c写的,所以需要改动一下,不然会报错。
Android层代码
-
在onCreate内定义长度为5000的int数组,并随机赋值
mData = new int[5000]; mData1 = new int[5000]; initArray(mData); initArray(mData1); private void initArray(int[] data) { for (int i = 0; i < data.length; i++) { data[i] = (int) (Math.random() * 500000 + 1); } } -
java排序
public void insertSortOne(View view) { long start = System.currentTimeMillis(); javaInsertSort(mData);//排序方法与c中一致 long end = System.currentTimeMillis(); mJavaResult.setText("用时:"+(end - start)+"ms"); } -
C排序
public void insertSortTwo(View view) { long start = System.currentTimeMillis(); mJNIUtil.insertSortArray(mData1); long end = System.currentTimeMillis(); mCResult.setText("用时:"+(end - start)+"ms"); }
最终效果:

很显然,c的效率非常快,并且,我们动态注册native方法也成功完成了。
参考资料:邓凡平 《深入理解android卷I》