JNI学习

Java Native Interface 应用: 允许 Java 代码与用其他编程语言(如 C、C++)编写的应用程序和库进行交互
调用本地库 , 与操作系统特定功能交互 , 性能优化 (关键部分用系统本地语言实现,JNI调用实现)。
某些算法库等中间件是由c/c++实现

加载时机

  1. 在类的静态初始化块中加载本地库 (少数地方调用库时)
  2. 在 Application 初始化时加载本地库 (程序启动时加载)

java端

package JNITest;

public class JNITestDemo {
    //native 用来声明本地方法(java之外实现)
    public native void helloWord();
    //在main调用之前加载动态库
    static {
        System.loadLibrary("hello"); // 加载本地库,库名为 libhello.so 或 hello.dll
    }
    public static void main(String[] args) {
        //创建实例调用
        new JNITestDemo().helloWord();
    }
}

生成JNITest_JNITestDemo.h文件

用utf-8编码格式编译
javac -encoding UTF-8 -h . JNITest\JNITestDemo.java

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest_JNITestDemo */

#ifndef _Included_JNITest_JNITestDemo
#define _Included_JNITest_JNITestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JNITest_JNITestDemo
 * Method:    helloWord
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

编译选项

启动参数添加https://blog.csdn.net/Saintmm/article/details/124603343
image.png
java -Djava.library.path=F:\workProject\clionProject\StudyProject\cmake-build-debug-visual-studio\JNITest 告诉 JVM 在哪里查找本地库文件

image.png

c端库编译

https://cloud.tencent.com/developer/article/1847548

拷贝头文件到项目

image.png

#include "stdio.h"
#include "jni_md.h"
#include "JNITest_JNITestDemo.h"

JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord(JNIEnv *env, jobject obj)
{
    printf("hello word");
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class JNITest_JNITestDemo */

#ifndef _Included_JNITest_JNITestDemo
#define _Included_JNITest_JNITestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JNITest_JNITestDemo
 * Method:    helloWord
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord(JNIEnv *env, jobject obj);

#ifdef __cplusplus
}
#endif
#endif

  • *JNIEnv env:指向 JNI 环境的指针。
  • jobject obj:指向调用该本地方法的 Java 对象的引用
  1. JNIEnv 概念和作用
    线程相关性:JNIEnv 是一个与线程相关的结构体。每个 Java 线程都有自己的 JNIEnv 实例,它反映了该线程在 Java 虚拟机中的运行环境。
    代表 Java 运行环境:通过 JNIEnv,JNI 本地方法可以与 Java 虚拟机进行交互,包括调用 Java 方法、访问字段、创建对象等操作。因此,JNIEnv 可以看作是本地方法与 Java 虚拟机通信的桥梁。
  2. 使用 JNIEnv
    调用 Java 方法:JNI 本地方法可以使用 JNIEnv 调用 Java 中的方法,包括静态方法和实例方法。
    操作 Java 对象:Java 对象在 JNI 层以 jobject 类型传递,JNI 方法可以使用 JNIEnv 操作这些 Java 对象,例如获取字段值、设置字段值、调用方法等操作。

方法遵循一定命名规范,让jvm正确找到调用。

cmake_minimum_required(VERSION 3.27.8)
project(StudyProject C)

set(CMAKE_C_STANDARD 99)

add_executable(StudyProject main.c)
add_subdirectory(./JNITest ${CMAKE_CURRENT_BINARY_DIR}/JNITest)
cmake_minimum_required(VERSION 3.27.8)

aux_source_directory(./ DIR_SRCS)
add_library(hello SHARED ${DIR_SRCS})

产物

image.png

参数添加

package JNITest;

public class JNITestDemo {
    public native void helloWord(String message); // 声明 native 方法

    // 加载本地库
    static {
        System.loadLibrary("hello");
    }

    public static void main(String[] args) {
        // 创建实例并调用 native 方法
        JNITestDemo demo = new JNITestDemo();
        demo.helloWord("Hello from Java!"); // 传递字符串参数给 native 方法
    }
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest_JNITestDemo */

#ifndef _Included_JNITest_JNITestDemo
#define _Included_JNITest_JNITestDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JNITest_JNITestDemo
 * Method:    helloWord
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

GetStringUTFChars

将jstring对象转换为C/C++的UTF-8编码的字符数组
ReleaseStringUTFChars释放

#include "stdio.h"
#include "jni_md.h"
#include "JNITest_JNITestDemo.h"

JNIEXPORT void JNICALL Java_JNITest_JNITestDemo_helloWord(JNIEnv *env, jobject obj, jstring s)
{
    const char *c_message = (*env)->GetStringUTFChars(env, s, NULL);
    if (c_message == NULL) {
        return; // 内存分配失败
    }
    printf("JNI test hello word %s", c_message);
    // 释放字符串
    (*env)->ReleaseStringUTFChars(env, s, c_message);
}

image.png

注意事项

内存管理

  1. 释放本地资源
    • 使用 NewGlobalRef 创建的全局引用,在使用完毕后需要通过 DeleteGlobalRef 明确释放,避免内存泄漏。
    • 在使用 GetObjectClass、GetFieldID、GetMethodID 等获取的局部引用,也需要及时释放。
  2. 处理字符串和数组
    • 使用 GetStringUTFChars、GetStringChars 获取字符串内容后,需要通过 ReleaseStringUTFChars、ReleaseStringChars 释放对应资源。
    • 对于数组操作,使用 GetXXXArrayElements 获取数组元素后,必须使用对应的 ReleaseXXXArrayElements 进行释放。
  3. 异常处理
    • JNI方法中的异常不会自动传播到Java层,需要手动处理和清除异常状态,通常通过 ExceptionCheck、ExceptionDescribe 等方法进行异常处理和调试。

线程安全性

  1. JNIEnv对象的线程限制
    • JNIEnv 对象只能在创建它的线程中使用,不能跨线程传递。因此,JNI方法中的JNIEnv对象只能用于当前线程中,不能保存或者跨线程使用。
  2. 多线程操作
    • 如果JNI方法需要在多线程环境下使用,确保正确处理同步和竞态条件。可以使用Java层的同步机制,如synchronized关键字,来保护共享资源。

性能考虑

  1. 避免频繁的JNI调用
    • JNI调用涉及到Java和本地代码的上下文切换,会带来一定的性能开销。因此,尽量避免在循环中频繁调用JNI方法,考虑批量处理或者优化算法以减少JNI调用次数。
  2. 使用本地缓存
    • 对于需要频繁访问的Java对象或数据,可以考虑在本地缓存中保留引用,避免每次调用都重新获取引用,提高访问效率

常用数据转换函数

字符串操作

  1. GetStringUTFChars / ReleaseStringUTFChars
    • const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
    • void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
    • 将Java字符串(jstring)转换为C字符串(UTF-8编码),并在使用后释放。这对于在本地代码中操作Java字符串非常有用。
  2. GetStringChars / ReleaseStringChars
    • const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
    • void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
    • 将Java字符串(jstring)转换为C字符串(Unicode编码),并在使用后释放。适用于需要直接访问Java字符串字符数组的情况。
  3. NewStringUTF
    • jstring NewStringUTF(JNIEnv *env, const char *utf);
    • 根据UTF-8编码的C字符串创建一个新的Java字符串(jstring)对象。常用于将C字符串转换为Java字符串后传递给Java方法。

数组操作

  1. GetXXXArrayElements / ReleaseXXXArrayElements
    • jbooleanArray GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy);
    • void ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode);
    • 获取和释放Java数组的元素,支持boolean、byte、char、short、int、long、float、double等类型的数组操作。
  2. NewXXXArray / GetXXXArrayRegion / SetXXXArrayRegion
    • jintArray NewIntArray(JNIEnv *env, jsize length);
    • void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf);
    • void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, const jint *buf);
    • 创建新的Java数组(int数组),以及获取和设置数组的指定区域。

基本数据类型操作

  1. GetXXXField / SetXXXField
    • jobject GetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID);
    • void SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value);
    • 获取和设置Java对象的字段值,支持各种基本数据类型以及对象类型的字段操作。
  2. CallXXXMethod
    • jobject CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID, …);
    • 调用Java对象的实例方法,支持返回对象、基本数据类型等不同类型的方法调用。

全局引用管理

  1. NewGlobalRef / DeleteGlobalRef
    • jobject NewGlobalRef(JNIEnv *env, jobject obj);
    • void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
    • 创建和删除全局引用,全局引用可以在JNI方法之间传递,并且可以长期保存Java对象的引用。
  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Android JNI学习路线可以按照以下步骤进行: 1. 了解JNI的基本概念和作用:JNI(Java Native Interface)是Java提供的一种机制,用于实现Java与其他编程语言(如C、C++)之间的交互。它允许在Java代码中调用本地代码(Native Code),并且可以在本地代码中调用Java代码。 2. 学习JNI的基本语法和规则:JNI使用一组特定的函数和数据类型来实现Java与本地代码之间的交互。你需要学习如何声明本地方法、如何在Java代码中调用本地方法、如何在本地代码中调用Java方法等。 3. 学习JNI的数据类型映射:JNI提供了一套数据类型映射规则,用于将Java数据类型映射到本地代码中的数据类型。你需要学习如何处理基本数据类型、对象类型、数组类型等。 4. 学习JNI的异常处理:在JNI中,Java代码和本地代码之间的异常处理是非常重要的。你需要学习如何在本地代码中抛出异常、如何在Java代码中捕获异常等。 5. 学习JNI的线程处理:JNI允许在本地代码中创建和操作线程。你需要学习如何创建和销毁线程、如何在线程之间进行通信等。 6. 学习JNI的性能优化:JNI涉及到Java代码和本地代码之间的频繁切换,因此性能优化是非常重要的。你需要学习如何减少JNI调用的次数、如何避免不必要的数据拷贝等。 7. 学习JNI的调试和测试:在开发JNI程序时,调试和测试是非常重要的。你需要学习如何使用调试器调试本地代码、如何进行单元测试等。 8. 学习JNI的进阶主题:一旦掌握了基本的JNI知识,你可以进一步学习JNI的进阶主题,如JNI与Java虚拟机的交互、JNI与动态链接库的交互、JNI与多线程的交互等。 总结起来,Android JNI学习路线包括了基本概念、基本语法、数据类型映射、异常处理、线程处理、性能优化、调试和测试以及进阶主题等内容。通过系统地学习这些知识,你将能够更好地理解和应用JNI技术。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值