JNI 方法注册与签名+BufferedReader使用readLine问题

Posted 我是修电脑的

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JNI 方法注册与签名+BufferedReader使用readLine问题相关的知识,希望对你有一定的参考价值。

最近了解了关于Java JNI接口的一些关于方法注册与签名相关的知识,在此进行一下总结。

使用JNI接口时,我们首先需要把Java方法声明为native:

 

[java] view plain copy
 
  1. public native void f();  


然后编写对应的C/C++代码,并编译成为动态链接库(.dll或.so),在调用Java方法前载入动态链接库即可调用:

 

 

[java] view plain copy
 
  1. static {  
  2.     System.loadLibrary("native-lib");  
  3. }  


那么,Java文件中的native方法是如何与native文件中的方法一一对应的呢?

 

在此有两种方法:静态注册与动态注册,下面将一一介绍:

静态注册

采用静态注册时,Java层的native方法与native层的方法在名称上具有一一对应的关系,具体要求如下:
native层的方法名为:Java_<包名>_<类名>_<方法名>(__<参数>)
其中,包名使用下划线代替点号进行分割
只有当native方法出现需要重载的时候,native层的方法名后才需要跟上参数(括号里的内容),参数的编写形式与JNI签名相关(后面会介绍)
通常而言,我们可以把native方法集中在一个类中,然后调用:
[plain] view plain copy
 
  1. javah -jni 包名.类名  

自动生成对应的c层头文件
下面是静态注册的例子:
Java层:
[java] view plain copy
 
  1. package com.app.superxlcr.jnitest;  
  2.   
  3. /** 
  4.  * Created by superxlcr on 2017/5/25. 
  5.  */  
  6.   
  7. public class NativeTest {  
  8.   
  9.     public native void f();  
  10.   
  11.     public native int f(int a, double b);  
  12.   
  13.     public native void f(Object a, String b);  
  14.   
  15.     public native void g();  
  16.   
  17. }  

native层:
[cpp] view plain copy
 
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include <jni.h>  
  3. /* Header for class com_app_superxlcr_jnitest_NativeTest */  
  4.   
  5. #ifndef _Included_com_app_superxlcr_jnitest_NativeTest  
  6. #define _Included_com_app_superxlcr_jnitest_NativeTest  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     com_app_superxlcr_jnitest_NativeTest 
  12.  * Method:    f 
  13.  * Signature: ()V 
  14.  */  
  15. JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__  
  16.   (JNIEnv *, jobject);  
  17.   
  18. /* 
  19.  * Class:     com_app_superxlcr_jnitest_NativeTest 
  20.  * Method:    f 
  21.  * Signature: (ID)I 
  22.  */  
  23. JNIEXPORT jint JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__ID  
  24.   (JNIEnv *, jobject, jint, jdouble);  
  25.   
  26. /* 
  27.  * Class:     com_app_superxlcr_jnitest_NativeTest 
  28.  * Method:    f 
  29.  * Signature: (Ljava/lang/Object;Ljava/lang/String;)V 
  30.  */  
  31. JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_f__Ljava_lang_Object_2Ljava_lang_String_2  
  32.   (JNIEnv *, jobject, jobject, jstring);  
  33.   
  34. /* 
  35.  * Class:     com_app_superxlcr_jnitest_NativeTest 
  36.  * Method:    g 
  37.  * Signature: ()V 
  38.  */  
  39. JNIEXPORT void JNICALL Java_com_app_superxlcr_jnitest_NativeTest_g  
  40.   (JNIEnv *, jobject);  
  41.   
  42. #ifdef __cplusplus  
  43. }  
  44. #endif  
  45. #endif  

我们可以看到,对于拥有重载的f 方法,其native方法名称后都带有参数,而没有重载的g 方法则没带有
静态注册JNI方法的弊端非常明显,就是方法名会变得很长,因此下面我们介绍另外一种动态注册的方法
 

动态注册

使用动态注册时,我们需要准备好需要自己想要对应的native方法,然后构造JNINativeMethod数组,JNINativeMethod是一种结构体,源码如下:
[cpp] view plain copy
 
  1. typedef struct {  
  2.     // Java层native方法名称  
  3.     const char* name;  
  4.     // 方法签名  
  5.     const char* signature;  
  6.     // native层方法指针  
  7.     void*       fnPtr;  
  8. } JNINativeMethod;  

然后重写JNI_OnLoad方法(该方法会在Java层通过System.loadLibrary加载完动态链接库后被调用),我们在其中进行动态注册工作:
[cpp] view plain copy
 
  1. JNIEXPORT jint JNICALL  
  2. JNI_OnLoad(JavaVM* vm, void* reserved) {  
  3.     JNIEnv *env = NULL;  
  4.     jint result = -1;  
  5.   
  6.     // 获取JNI env变量  
  7.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  8.         // 失败返回-1  
  9.         return result;  
  10.     }  
  11.   
  12.     // 获取native方法所在类  
  13.     const char* className = "com/app/superxlcr/jnitest/MainActivity";  
  14.     jclass clazz = env->FindClass(className);  
  15.     if (clazz == NULL) {  
  16.         return result;  
  17.     }  
  18.   
  19.     // 动态注册native方法  
  20.     if (env->RegisterNatives(clazz, methods, 1) < 0) {  
  21.         return result;  
  22.     }  
  23.   
  24.     // 返回成功  
  25.     result = JNI_VERSION_1_4;  
  26.     return result;  
  27. }  

动态注册的大致步骤如下:
  1. 通过vm(Java虚拟机)参数获取JNIEnv变量
  2. 通过FindClass方法找到对应的Java类
  3. 通过RegisterNatives方法,传入JNINativeMethod数组,注册native函数
对于JNINativeMethod结构而言,签名是其非常重要的一项元素,它用于区分Java中native方法的各种重载形式,下面将介绍方法的签名
 

方法签名

方法签名对于区分Java层native重载方法有重大意义
总的来说,方法签名的组成规则为:
[plain] view plain copy
 
  1. (参数类型标识1参数类型标识2...参数类型标识n)返回值类型标识  
 
类型标识对应关系如下:
类型标识 Java类型
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L包名/类名; 各种引用类型
V void

另外,当Java类型为数组时,在标识前会有“[”符号,例如:String[] 类型标识为 [Ljava/lang/String;
 
下面举几个例子:
[java] view plain copy
 
  1. // Signature: ()V  
  2. public native void f();  
  3.   
  4. // Signature: (ID)I  
  5. public native int f(int a, double b);  
  6.   
  7. // Signature: (Ljava/lang/Object;Ljava/lang/String;)V  
  8. public native void f(Object a, String b);  
  9.   
  10. // Signature: ()V  
  11. public native void g();  
      1. BufferedReader使用readLine问题
      2. 有时我们在使用BufferedReader时候会发现使用readLine函数迟迟没有任何返回,这是因为BufferedReader和BufferedWriter是基于行进行操作的,因此我们使用BufferedWriter的时候使用newLine函数即可,具体代码如下:

         

        [java] view plain copy
         
        1. BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));  
        2. writer.write(str);  
        3. writer.newLine();  
        4. writer.flush();  
        5.   
        6. BufferedReader reader = new BufferedReader(new InputStreamReader(in));  
        7. str = reader.readLine();  

以上是关于JNI 方法注册与签名+BufferedReader使用readLine问题的主要内容,如果未能解决你的问题,请参考以下文章

Android深入理解JNI类型转换方法签名和JNIEnv

NDK:JNI 的数据结构

jni使用javap查看java类方法签名

JNI签名与数据匹配

Android JNI之调用JAVA方法的返回值签名

JNI