JNI开发笔记三之动态注册

前言

这几天在研究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");
    }
    

最终效果:

JNI开发笔记三之动态注册_第1张图片
test.gif

很显然,c的效率非常快,并且,我们动态注册native方法也成功完成了。

参考资料:邓凡平 《深入理解android卷I》

你可能感兴趣的:(JNI开发笔记三之动态注册)