Android深入理解JNI(一)JNI 静态注册与动态注册

 

1.JNI概述

Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。那为什么要这么划分呢?Android系统由Java写不好吗?除了性能的之外,最主要的原因就是在Java诞生之前,就有很多程序和库都是由Native语言写的,因此,重复利用这些Native语言编写的库是十分必要的,况且Native语言编写的库具有更好的性能。
这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。
未命名文件(5).png
通过JNI,Java世界的代码就可以访问Native世界的代码,同样的,Native世界的代码也可以访问Java世界的代码。
 

静态注册

原理:根据函数名来建立 java 方法与 JNI 函数的一一对应关系;

实现流程:

    编写 java 代码;
    利用 javah 指令生成对应的 .h 文件;
    对 .h 中的声明进行实现;

弊端:

    编写不方便,JNI 方法名字必须遵循规则且名字很长;
    编写过程步骤多,不方便;
    程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时;

extern "C" JNIEXPORT jstring JNICALL
Java_cn_com_jni_dexshell_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject jobj/* this */,jstring id) {
 
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello .c_str());
}

动态注册

原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;

实现流程:

    利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
    实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;
    调用 FindClass 方法,获取 java 对象;
    调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;

优点:

    流程更加清晰可控;
    效率更高;

 

JNINativeMethod

在动态注册的过程中使用到了结构体 JNINativeMethod 用于记录 java 方法与 jni 函数的对应关系

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

结构体的第一个参数 name 是java 方法名;

第二个参数 signature 用于描述方法的参数与返回值;

第三个参数 fnPtr 是函数指针,指向 jni 函数;

其中,第二个参数 signature 使用字符串记录方法的参数与返回值,具体格式形如“()V”、“(II)V”,其中分为两部分,括号内表示的是参数,括号右侧表示的是返回值;
 

数据类型映射

    基本数据类型

java 类型 native 类型 域描述符 
boolean jboolean 
byte jbyte 
char jchar 
short jshort 
int jint 
long jlong 
float jfloat 
double jdouble 
void void 

2. 数组引用类型

如果是一维数组则遵循下表,如果是二维数组或更高维数组则对应的 native 类型为 jobjectArray,域描述符中使用 ‘[’ 的个数表示维数

java 类型 native 类型 域描述符 
int[] jintArray [I 
float[] jfloatArray [f 
byte[] jbyteArray [B 
char[] jcharArray [C 
short[] jshortArray [S 
double[] jdoubleArray [D 
long[] jlongArray [F 
boolean[] jbooleanArray [Z

3. 对象引用类型

对于其它引用类型,即 java 中的对象,其映射规则为

java 类型 native 类型 域描述符 
类名(如 Surface) 通常是 jobject,仅有一种例外,如果 java 类型是 String,则对应的native 类型是 jstring 以”L”开头,以”;”结尾中间是用”/” 隔开的包及类名(如 Landroid/view/Surface;)如果内部类则使用$连接内部类;

4. 对象数组引用类型

如果是一维数组则遵循下表,如果是二维数组或更高维数组则对应的 native 类型为 jobjectArray,域描述符中使用 ‘[’ 的个数表示维数

java 类型 native 类型 域描述符 
类名(如 Surface) 通常是 jobject,仅有一种例外,如果 java 类型是 String,则对应的native 类型是 jstring 在对象引用类型的域描述符的基础上在左边添加’[‘字符

 

jni 函数默认参数

在 jni 函数中有两个默认参数

JNIEnv *env, jobject thiz

 其中 JNIEnv 指代的是当前 java 环境,可以利用 JNIEnv 可以操作 java 层代码;jobject 指代的是 jni 函数对应的 java native 方法的类实例,如果 java 方法是 static,则代表的是 class 对象;

例:

java

package cn.com.jni.jniautoreg;

public class TestJNI {
    public static native void  native_init();
    public static native String    stringFromJNI(String name,String address);
}

 native

#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <memory.h>

#define LOG_TAG "my-jni"
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
using namespace std;
static const char *const kClassJniTest =
        "cn/com/jni/jniautoreg/TestJNI";

 
jstring JNI_stringFromJNI(JNIEnv *env, jobject obj, jstring name,jstring address) {
    LOGE("JAVA  call  JNI stringFromJNI");
    string hello = "Hello from JNI";
    return env->NewStringUTF(hello.c_str());
}



void JNI_native_init(JNIEnv *env, jobject thizz) {
    LOGE("  native_1init");

}

static const JNINativeMethod gMethods[] = {
        {
                "native_init",
                "()V",
                (void *) JNI_native_init
        },

       {
                "stringFromJNI",
                "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
                (void *) JNI_stringFromJNI
        },
};

static int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *gMethod,
                                 int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        LOGI(" JNI reg faild:%s",className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
        LOGI(" JNI reg method failed:%s",gMethod->name);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static int register_along_jni(JNIEnv *env) {

    return registerNativeMethods(env, kClassJniTest, gMethods,
                                 NELEM(gMethods));
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /* reserved */) {

    JNIEnv *env = NULL;
    jint result = -1;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("ERROR: JNI version error");

        return JNI_ERR;
    }
    if (register_along_jni(env) == -1) {
        LOGE("ERROR:  JNI_OnLoad failed");
        return JNI_ERR;
    }
    result = JNI_VERSION_1_6;
    return result;
}

 
注意:
在JNI_OnLoad函数的结尾处,我们一定要有返回值,而且必须是JNI_VERSION_1_4 或 JNI_VERSION_1_6,也就是JNI的版本号,我们一定要返回正确的版本号,否则系统也是无法加载的。
 

完整 demo

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恋恋西风

up up up

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值