[Android JNI] --- JNI基础(下)

简介: [Android JNI] --- JNI基础(下)

引用类型描述符

一般引用类型描述符的规则如下,注意不要丢掉“;”

L + 类描述符 + ;

如,String 类型的域描述符为:

Ljava/lang/String;

数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号

[ + 其类型的域描述符

例如:

int[]    描述符为 [I
double[] 描述符为 [D
String[] 描述符为 [Ljava/lang/String;
Object[] 描述符为 [Ljava/lang/Object;
int[][]  描述符为 [[I
double[][] 描述符为 [[D

对应在 jni.h 获取 Java 的字段的 native 函数如下,name为 Java 的字段名字,sig 为域描述符

//C
jfieldID    (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
jobject     (*GetObjectField)(JNIEnv*, jobject, jfieldID);
//C++
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
    { return functions->GetFieldID(this, clazz, name, sig); }
jobject GetObjectField(jobject obj, jfieldID fieldID)
    { return functions->GetObjectField(this, obj, fieldID); }

3.2 类描述符

类描述符是类的完整名称:包名+类名,java 中包名用 . 分割,jni 中改为用 / 分割

如,Java 中 java.lang.String 类的描述符为 java/lang/String

native 层获取 Java 的类对象,需要通过 FindClass() 函数获取, jni.h 的函数定义如下:

//C
jclass  (*FindClass)(JNIEnv*, const char*);
//C++
jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

字符串参数就是类的引用类型描述符,如 Java 对象 cn.cfanr.jni.JniTest,对应字符串为Lcn/cfanr/jni/JniTest; 如下:

jclass jclazz = env->FindClass("Lcn/cfanr/jni/JniTest;");

3.3 方法描述符

方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:

(参数……)返回类型

例如:

Java 层方法   ——>  JNI 函数签名
String getString()  ——>  Ljava/lang/String;
int sum(int a, int b)  ——>  (II)Ivoid main(String[] args) ——> ([Ljava/lang/String;)V

另外,对应在 jni.h 获取 Java 方法的 native 函数如下,其中 jclass 是获取到的类对象,name 是 Java 对应的方法名字,sig 就是上面说的方法描述符

//C
jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//C++
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
    { return functions->GetMethodID(this, clazz, name, sig); }

不需要记住,可以使用下面方法查看签名

java -s -p xxx.class

4 静态注册和动态注册

当执行一个 Java 的 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。

注册分为 静态注册 和 动态注册两种。

4.1 静态注册

定义

通过 JNIEXPORT 和 JNICALL 两个宏定义声明,在虚拟机加载 so 时发现上面两个宏定义的函数时就会链接到对应的 native 方法。

规则

java_完整包名_类名_方法名

特殊规则:

  • 包名或类名或方法名中含下划线 _ 要用 _1 连接;
  • 重载的本地方法命名要用双下划线 __ 连接;
  • 参数签名的斜杠 “/” 改为下划线 “_” 连接,分号 “;” 改为 “_2” 连接,左方括号 “[” 改为 “_3” 连接;
    另外,对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。

示例

包名:com.android.javac,类名:NativeAccessJava
// java method
public native void setJavaString();
// jni method
JNIEXPORT void JNICALL
Java_com_android_javac_NativeAccessJava_setJavaString(JNIEnv *env, jobject thiz) 

4.2 动态注册

动态注册原理

动态注册 JNI 的原理:直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系,步骤:

1> 先编写 Java 的 native 方法;

2> 编写 JNI 函数的实现(函数名可以随便命名);

3> 利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;

4> 利用registerNatives(JNIEnv* env)注册类的所有本地方法;

5> 在 JNI_OnLoad 方法中调用注册方法;

6> 在Java中通过System.loadLibrary加载完JNI动态库之后,会自动调用JNI_OnLoad函数,完成动态注册;

不再使用 JNIEXPORT 和 JNICALL 两个宏定义声明指定的方法,也不用依照固定的命名规则命名方法(不过通常 jni 里的方法名还是保持和 native 方法的方法名一致,见名思义),而是通过了一个 RegisterNatives 方法完成了动态注册。

函数映射表

JNINativeMethod这其实是一个结构体,在jni.h头文件中定义,通过这个结构体从而使Java与jni建立联系

typedef struct {
const char* name; //Java中函数的名字
const char* signature;//符号签名,描述了函数的参数和返回值
void* fnPtr;//函数指针,指向一个被调用的函数
} JNINativeMethod;

示例

// java method
    public native String stringFromJNI();
    public static native int add(int a, int b);
// jni register
jint DyReg::RegisterNatives(JNIEnv *env) {
    LOGE("RegisterNatives");
    jclass clazz = env->FindClass("com/android/javac/DynamicReg");
    if (clazz == NULL) {
        LOGE("could't find class: com/android/javac/DynamicReg");
        return JNI_ERR;
    }
    JNINativeMethod methods_MainActivity[] = {
            {"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
            {"add",           "(II)I",                (void *) add}
    };
    // int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
    return env->RegisterNatives(clazz, methods_MainActivity,
                                sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}

相关文章
|
7月前
|
Android开发
Android JNI与CAN通信遇到的问题总结
Android JNI与CAN通信遇到的问题总结
273 1
|
7月前
|
Android开发
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
Android JNI 报错(signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr )
1052 1
|
4月前
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
322 1
|
7月前
|
Java 开发工具 Android开发
OpenCV(一):Android studio jni配置OpenCV(亲测有效,保姆级)
OpenCV(一):Android studio jni配置OpenCV(亲测有效,保姆级)
837 0
|
7月前
|
Java Android开发
Android JNI 调用
Android JNI 调用
40 1
|
Java Android开发
Android JNI开发从0到1,java调C,C调Java,保姆级教程详解
Android JNI开发从0到1,java调C,C调Java,保姆级教程详解
91 1
|
Java Android开发 C++
Android中的JNI开发,你了解多少?
Android中的JNI开发,你了解多少?
99 0
|
7月前
|
传感器 Java 开发工具
[NDK/JNI系列03] Android Studio集成NDK开发环境
[NDK/JNI系列03] Android Studio集成NDK开发环境
72 0
|
7月前
|
Android开发
[Android jni] Bitmap与Mat对象的相互转换
[Android jni] Bitmap与Mat对象的相互转换
229 0
|
7月前
|
Java 开发工具 Android开发
[Android]JNI的基础知识
[Android]JNI的基础知识
118 0
[Android]JNI的基础知识