• Android JNI调用流程


    前言

    对基本的JNI开发流程予以记录

    参考书籍:《JNI_NDK开发指南》
    参考博客:
    JNI开发流程——东邪丶
    JNI开发流程——Android百晓生

    一、JNI是什么

    JNI全称为Java Native Interface,主要用于实现Java和C/C++的通信。

    在这里插入图片描述

    二、JNI的优劣

    优势:

    • 能够访问一些底层/系统级接口(一般这类接口都是C/C++编写的)
    • 能够直接调用C/C++,一定程度上能够提升执行效率
    • 能够避开一些java层的限制,比如JVM的内存开销过大等等
    • 确保代码在不同的平台上方便移植

    劣势:

    • 程序可靠性会降低
    • 在C/C++中通过JNI访问Java的对象方法、对象属性时,相比于Java中自行调用,效率不高

    三、JNI的开发流程

    Java调用C++函数

    1、创建声明native方法的Java工程,加载native函数的动态库,生成.h文件

    • Java中native方法的声明方式:public static native xxx(xxx);
    • Java中加载native动态库的方式:System.LoadLibrary(xxx)或System.load(xxx);
    public class JniTest {
    
        // 获取字符串
        public static native String getStringFromC();
        // 获取相加值
        public static native int addFromC(int a, int b);
    
        static {
            // 需要在System.getProperty("java.library.path")路径下放入对应的jni_test.dll包
            // System.loadLibrary("jni_test");
            // 需要使用绝对路径,且需要添加后缀
            System.load("C:/Users/kqli/IdeaProjects/shanguigu_interview_java/src/com/kqli/jni/jni_test.dll");
        }
    
        public static void main(String[] args) {
            // 输出java library路径
            // System.out.println(System.getProperty("java.library.path"));
            String stringFromC = getStringFromC();
            System.out.println();
            System.out.println(stringFromC);
            int a = 1, b = 1;
            System.out.println(String.format("%d + %d = %d", a, b, addFromC(a, b)));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    Java加载native动态库,有两种API可实现:

    ①System.loadLibrary(“LibraryName”);
    该API只需要指定动态库名字即可,不需要加前后缀。
    且java会到java.library.path系统属性指定的目录下查找动态库文件,如果没有找到会抛出java.lang.UnsatisfiedLinkError异常。
    若.dll文件有多个工程使用,可放到一个统一目录,在Path环境变量中配置存放路径,系统也能正确加载

    ②System.load(“/Users/Desktop/LibraryName.so”);
    该API需要指定动态库的绝对路径名,且要加上前缀和后缀。

    Java静态代码块中加载动态库,防止在未加载动态库之前就调用native方法

    Java在创建类实例时,类会先被ClassLoader先加载到Java VM中,紧接着调用类的static静态代码块,所以在此时加载动态库可有效避免native方法调用比加载动态库时更早。

    使用命令生成java工程.h文件

    javac -h [目标文件路径] [文件名.java]

    如出现编码格式异常则使用

    javac -encoding utf-8 -h [目标文件路径] [文件名.java]

    执行完成,会在src目录下生成.h头文件,如下图:

    在这里插入图片描述在这里插入图片描述

    2、创建实现C函数的C工程,将本地代码编译成动态库

    1)将.h头文件拷贝到c工程,并添加关联到项目(此时.h中#include头文件引入报错)
    2)JDK中搜索jni.h,将jni.h和jni_md.h文件复制到C工程中,并修改引用为#include “jni.h”(解决jni.h头文件报错)

    注:#include指令使用区别,系统头文件引入使用<>;第三方头文件引入使用" ",所以此处需修改引用为#include “jni.h”

    3)创建c01.c、c02.c文件,实现C函数

    Java调用C函数需要做C函数和Java本地方法的映射,建立该映射有两种方式: 显式映射和隐式映射。

    C函数和Java本地方法的隐式映射(相对简单)

    在c01.c中实现Java与C函数隐式映射

    #include "com_kqli_jni_JniTest.h"
    #include "jni.h"
    
    //函数实现
    JNIEXPORT jstring JNICALL Java_com_kqli_jni_JniTest_getStringFromC
    (JNIEnv *env, jclass jclass) {
        return (*env)->NewStringUTF(env, "Hello, kqli!");
    }
    
    JNIEXPORT jint JNICALL Java_com_kqli_jni_JniTest_addFromC(JNIEnv *env, jclass jclass, jint a, jint b)
    {
        return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    C函数和Java本地方法的显式映射

    在c02.c中实现Java与C函数显式映射

    #include "jni.h"
    
    #define ARRAY_SIZE(arr)   (sizeof(arr) / sizeof((arr)[0]))
    
    // C函数需要比Java本地方法多出两个参数,这两个参数之后的参数列表与Java本地方法保持一致
    // 第一个参数表示JNI环境,该环境封装了所有JNI的操作函数
    // 第二个参数为Java代码中调用该C函数的对象
    // jint表示JNI的int类型,在本文后面会给出所有JNI类型
    jint add(JNIEnv *env, jobject thiz, jint a, jint b)
    {
        return a + b;
    }
    
    jstring getString(JNIEnv *env, jobject thiz)
    {
        return (*env)->NewStringUTF(env, "hello, kqli!");
    }
    
    static const JNINativeMethod methods[] = {
        // 第一个参数为Java本地方法名
        // 第二个参数为函数签名:(参数签名)返回值签名, 在本文后面会给出所有签名符号
        // 第三个参数为C函数
        {"addFromC", "(II)I", (void *)add},   // 建立Java本地方法和C函数的映射
        {"getStringFromC", "()Ljava/lang/String;", (void *)getString},
    };
    
    // 在Java中调用System.loadLibrary方法时会调用到该函数
    JNIEXPORT jint JNICALL
    JNI_OnLoad(JavaVM *jvm, void *reserved)
    {
        JNIEnv *env;
        jclass cls;
    
        // 获取JNI环境
        if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_8)) {
            return JNI_ERR;
        }
    
        // 获取Java类
        // JNI_OnLoad函数写法基本固定, 唯一需要修改的是FindClass的第二个参数,即类名
        cls = (*env)->FindClass(env, "com/kqli/jni/JniTest");
        if (cls == NULL) {
            return JNI_ERR;
        }
    
        // 注册本地方法
        if ((*env)->RegisterNatives(env, cls, methods, ARRAY_SIZE(methods)) < 0)
            return JNI_ERR;
    
        return JNI_VERSION_10;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    点击查看语法解释

    动态库共有以下几种:

    • Windows:.dll库
    • Linux/Unix:.so库
    • Mac os x:.jnilib库

    Windows编译.dll库

    输出.o文件:gcc -c [.c文件名] -o [输出的.o文件]
    输出.dll库:gcc [.o文件] -o [输出的.dll文件] -shared

    在这里插入图片描述

    3、加载.dll文件,运行java工程

    将vs中生成的.dll动态库拷贝到java工程目录下,运行java工程

    使用System.loadLibrary(“jni_test”)时,.dll文件须放到工程根目录下系统动态加载时才能找到,否则会报路径错误;
    使用System.load(“C:/Users/kqli/IdeaProjects/shanguigu_interview_java/src/com/kqli/jni/jni_test.dll”)时,.dll文件必须与填写路径一致

    运行成功!
    在这里插入图片描述

    C++调用Java方法

    待更新

  • 相关阅读:
    第十七届智能车竞赛 - 磁力计角度数据处理
    termux安装以及基本配置
    Open vSwitch: 深入解析现代网络虚拟化的核心
    spring源码之下载及构建
    docker网络
    【WebService笔记02】使用CXF框架实现WebService接口的发布和调用
    【Java 基础篇】Java网络编程实战:P2P文件共享详解
    计算机毕业设计(附源码)python在线阅读系统
    vue3 webviewAPP
    【Git】深入了解Git及其常用命令
  • 原文地址:https://blog.csdn.net/qq_43357394/article/details/133783269