对应的java属性与方法签名 在jni调用中,返回值和参数,以及静态字段和实例字段,有对应着相应的签名,如下表格: 这些签名的时候在接下的实例讲解中会用到; 简而言之,在jni中涉及到类型的使用(包括基本类和引用类型...: GetFieldID faild "); } } 说明: native调用java中的方法,java中的方法抛出异常,我们在native中检测异常,检测到后抛出native中的异常,并清理异常...函数介绍: 1> ExceptionCheck:检查是否发生了异常,若有异常返回JNI_TRUE,否则返回JNI_FALSE 2> ExceptionOccurred:检查是否发生了异常,若用异常返回该异常的引用...,否则返回NULL 3> ExceptionDescribe:打印异常的堆栈信息 4> ExceptionClear:清除异常堆栈信息 5> ThrowNew:在当前线程触发一个异常,并自定义输出异常信息...6> Throw:丢弃一个现有的异常对象,在当前线程触发一个新的异常 7> FatalError:致命异常,用于输出一个异常信息,并终止当前VM实例(即退出程序) jni的静态注册和动态注册 参考
第三个参数为该线程启动程序的函数指针,也就是线程启动时要执行的那个方法,类似于 Java Runnable 中的 run 方法,它的函数签名格式如下: void* start_routine(void*...,并且线程运行函数也不需要参数,就都直接设置为了 NULL,那么上面那段程序就可以执行了,并且 printThreadHello 函数是运行在新的线程中的。...将线程附着在 Java 虚拟机上 在上面的线程启动函数中,只是简单的执行了打印 log 的操作,如果想要执行和 Java 相关的操作,比如从 JNI 调用 Java 的函数等等,那就需要用到 Java...pthread_create 创建的线程是一个 C++ 中的线程,虚拟机并不能识别它们,为了和 Java 空间交互,需要先把 POSIX 线程附着到 Java 虚拟机上,然后就可以获得当前线程的 JNIEnv...具体使用如下: 首先在 Java 中定义在 C++ 线程中回调的方法,主要就是打印线程名字: private void printThreadName() { LogUtil.Companion.d
(2)ndk-build:可在Android.mk 和 Application.mk文件中配置编译选项,ndk-build的C++运行时默认值为none,ndk-build中默认停用C++异常,默认停用...Android中每个进程只允许有一个JavaVM。JNIEnv作用域为单个线程,可通过JavaVM的getEnv来获得当前线程的JNIEnv,JNIEnv可通过GetJavaVM来获得JavaVM。...= JNI_OK) return JNI_ERR; return result; } 4.2 线程与JavaVM 在java代码中,可以通过Thread.start()启动一个线程...; 对于在native代码中通过pthread_create() 或 std::thread 启动的线程,是没有JNIEnv的,也就无法调用JNI,可以使用 AttachCurrentThread()...或 AttachCurrentThreadAsDaemon() 函数将JavaVM附加到线程,附加后的线程可以调用JNI代码: // 保存JavaVM,方便在子线程中获取 static JavaVM *
jclass、jobject、jmethodID 和 jfieldID jni回调java是通过反射来实现的,这些反射的接口都定义在 JNIEnv中。...它会检查调用堆栈,如下所示: Foo.myfunc(Native Method) Foo.main(Foo.java:10) 最顶层的方法是 Foo.myfunc。...如果您自行创建线程(可能通过调用 pthread_create,然后使用 AttachCurrentThread 进行附加),可能会遇到麻烦。现在您的应用中没有堆栈帧。...如果从此线程调用 FindClass,JavaVM 会在“系统”类加载器(而不是与应用关联的类加载器)中启动,因此尝试查找特定于应用的类将失败。...您可以通过以下几种方法来解决此问题: 在 JNI_OnLoad 中执行一次 FindClass 查找,然后缓存类引用以供日后使用。
在JNI层进行性能优化和防止内存泄漏是Android NDK开发的核心挑战之一。以下是我在实践中总结的关键策略和最佳实践。...最佳实践:类加载时初始化缓存:在JNI_OnLoad或首次使用类的Native方法中查找并缓存为全局引用。...四、异常处理1、检查异常:在执行可能抛出异常的JNI调用后(尤其是调用Java方法CallMethod、创建对象NewObject、访问字段/方法ID),立即检查异常:env->CallVoidMethod...2、检查所有JNI函数返回值:特别是FindClass, GetMethodID, NewGlobalRef等,失败返回NULL或抛出异常。...核心原则:JNI层的内存泄漏往往源于对JVM引用生命周期管理的疏忽(尤其是全局引用和线程附着)以及Native内存与Java对象生命周期的不同步。
6.在模块的build.gradle中设置so文件路径 sourceSets { main { jni.srcDirs = [] jniLibs.srcDirs...支持C++的项目目录 (1)src/main/cpp下存放的我们编写供JNI调用的C++源码。...3、jni调用java非静态成员方法 1.使用 GetObjectClass、 FindClass获取调用对象的类 2.使用 GetMethodID获取方法的ID。这里需要传入方法的签名描述。...5、jni调用java构造方法 1.使用 FindClass获取需要构造的类 2.使用 GetMethodID获取构造方法的ID。方法名为 , 这里需要传入方法的签名描述。...7、jni异常处理 1.使用 ExceptionOccurred进行异常的检测。注意,这里只能检测java异常。 2.使用 ExceptionClear进行异常的清除。
在 Native 层访问 Java 对象的一般步骤 在 Native 层中通过 JNI 可以自由地访问 Java 对象,访问 Java 对象一般分为 3 步。 I....在 Native 层中 Java 对象的引用类型 在 JVM 中 Java 对象的引用类型分为 “强、软、弱、虚” ,我们常用的一般是弱引用,而在 Native 层中 Java 对象的引用类型一般分为...JNIEnv 和 JavaVM 类型的作用域 JNIEnv 这个结构体比较特殊,主要提供 JNI 调用环境(JNI Environment),JNIEnv 类型的变量是线程相关,即一个线程会对应一个 JNIEnv...变量,也就是说 JNIEnv 类型的指针不能在不同的线程中共用。...若要在一个子线程里访问 Java 对象,就需要获得对应 JNIEnv 类型变量的指针,一般通过一对 JNI 方法进行获取和释放: //获取 jvm->AttachCurrentThread(&env);
; //这种也是可以检测的 // exc = env->ExceptionOccurred(); // 返回一个指向当前异常对象的引用 // if (exc) { if...(env->ExceptionCheck()) { // 检查JNI调用是否有引发异常 env->ExceptionDescribe();//打印错误信息 env->ExceptionClear...(); // 清除引发的异常,在Java层不会打印异常的堆栈信息 env->ThrowNew(env->FindClass("java/lang/Exception"),..."JNI抛出的异常!")...} } c++调用java: 这里需要分为在主线程调用还是在子线程调用,在主线程调用就直接通过函数的env调用即可,在子线程调用则需要JavaVM ->AttachCurrentThread
接口指针 clazz:Java类 methodID:静态方法ID 返回: 返回静态的Java方法的调用方法 异常: 在Java方法中执行中抛出的异常 七、字符串操作 (一)、创建一个字符串 jstring...在使用这两个函数时,这两个函数中间的代码不能调用任何让线程阻塞或者等待JVM的其他线程的本地函数或者JNI函数。...否则,JVM有可能死活,想象一下这样的场景: 1、只有当前线程触发的GC完成阻塞并释放GC时,由其他线程出发的GC才可能由阻塞中释放出来继续执行。...2、在这个过程中,当前线程会一直阻塞,因为任何阻塞性调用都需要获取一个正在被其他线程持有的锁,而其他线程正等待GC。...而是应该通过同步方法来使用Java虚拟机指令来释放监视器 参数解释: env:JNI接口指针 obj:普通的Java对象或类对象 返回: 成功返回0,失败返回负数 异常: 如果当前线程不拥有该监视器
\src\main\java\ltd\dujiabao\jni_tests\HelloWorld.java-h .\jni:该选项指定生成的 JNI 头文件存放的目录。...= NULL)抛出异常ThrowNew: 这是一个JNI函数,用于抛出一个新的Java异常。illegalArgumentException: 这是要抛出的异常类的引用。"...在这个例子中,返回-1。可能这里有人会有疑问,为什么抛异常之后,还要return。...= (*env)->GetMethodID(env, pointClazz, "getX", "()D"); jmethodID getY_method_id = (*env)->GetMethodID...jmethodID getX_method_id = (*env)->GetMethodID(env, pointClazz, "getX", "()D");jmethodID getY_method_id
异常没有被捕获的原因是:因为在main方法中执行完了t1.start();方法后很快返回了,所以很快就执行到了try语句块外,甚至main线程直接就执行结束,在内存中先于线程t1被释放了。...我们使用多线程的初衷即是将一个复杂的工作简单化为若干个小任务,一个线程的执行错误不应影响其他线程,线程是相互独立的(不要想当然地任务写在Main方法中的代码都是属于Main线程去的~)。...所以我们可以采取在对应线程的run方法中进行异常捕获的处理,而不是委托给main线程: public class TempTest { public static void main(String...因为我们没有使用try-catch语句来包围异常,所以这类运行时异常都被称为uncaught exception。由于传入的线程对象为this,所以之前的方法中入口参数Thread都是当前线程对象。...0,5,main]的异常java.lang.RuntimeException: 自定义的运行时异常 这一来,我们可以通过定义一个UncaufhtExceptionHandler就做到了处理线程中可能遇到的所有异常
JNI代码使用错误 JNI的五大使用错误: 错用 JNIEnv 不检查异常 不检查返回值 错误地使用数组方法 错误地使用全局引用 错用JNIEnv 子线程执行本地代码,尝试通过JNIEnv调用JNI...JNI规范声明每个JNIEnv都必须是线程所拥有的。JVM可以根据此规范,在其中存储包含JNIEnv的其他线程的本地信息。...因此,通过缓存JavaVM对象的副本, 任何有权访问缓存对象的线程都可以在必要时访问自己的JNIEnv对象。 不检查异常 本地可以调用的许多JNI方法可能会在执行线程上引发异常。...常见的JNI编程错误是调用了JNI方法而不检查异常,并忽略异常继续执行。这可能导致严重的错误和崩溃。 ...例如,代码10中的代码,如果修改了Java类中的charField字段可能会导致崩溃,而不是抛出 NoSuchFieldError异常: //代码10.未检查异常 jclass objectClass;
native method()方法; -- 实现JNI的C/C++方法 : 在JNI层实现Java中声明的native方法, 这里使用javah工具生成带方法签名的头文件, 该JNI层的C/C++代码将被编译成动态库...调用JNI函数 可以访问java虚拟机, 操作java对象; JNI线程相关性 : JNIEnv只在当前的线程有效,JNIEnv不能跨线程传递, 相同的Java线程调用本地方法, 所使用的JNIEnv...是相同的, 一个Native方法不能被不同的Java线程调用; JNIEnv结构体系 : JNIEnv指针指向一个线程相关的结构,线程相关结构指向一个指针数组,指针数组中的每个元素最终指向一个JNI...; //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method jmethodID methodID = (*env)->GetMethodID...; //参数介绍 : 第二个参数是Class对象, 第三个参数是方法名,第四个参数是方法的签名, 获取到调用的method jmethodID methodID = (*env)->GetMethodID
加载共享库 static { System.loadLibrary("hello-jni");//libhello-jni.so } 之所以要在静态代码块中调用 System.loadLibrary ,...关于原生代码中声明的方法 Java代码中对原生方法的声明可以不带上参数,但对应的原生函数式带有参数的,例如: JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI...;//因为C中的JNI函数不清楚当前的JNI环境,所以要传入env。 而C++的写法则是: return env->NewStringUTF("Hello from JNI !")...捕获异常 public class JavaClass { //Throwing method. private void throwingMethod() throws NullPointerException...== (*env)->MonitorExit(env, obj)) { // Error handling. } 原生多线程 将当前线程同虚拟机绑定和解绑: JavaVM* cachedJvm; ..
("android/content/Intent"); jmethodID method = env->GetMethodID(clazz, "", "Ljava/lang/String;"...,method,action); 复制代码 调用 Java 类的方法 我们看下JNIEnv提供的调用Java类中的方法的函数: 对象方法 jobject CallObjectMethod(JNIEnv...JNIEnv对象是和线程绑定在一起的,那我们考虑几个问题: 当我们在Java线程中调用native层的JNI函数时,线程所属的JNIEnv对象已经生成了吗? 如果生成了,在什么时候生成的呢?...JNIEnv 对象的初始化总结 对于JNIEnv对象的初始化就两点: 主线程中的JNIEnv对象在创建虚拟机时就已经建好了 Java 中新建线程的JNIEnv对象是在该线程运行时建立的,并在线程结束时释放...JNI 中的异常处理 在 JNI 中检查Java层产生的异常 首先要明确的是,Android的C++层不支持try-catch机制 如果JNI调用Java层的方法时发生了异常 JNI调用会正常返回 但是
第二步,配置环境变量,在用户变量中添加NDK_ROOT = SDK所在目录/ndk-bundle 然后再在path变量中添加%NDK_ROOT% 第三步,选择工程文件的Project视图,在src/main...\\main\\jni\\Android.mk") } } 整个build.gradle文件如下: apply plugin: 'com.android.application...,所有准备工作都已经完事了,需要注意的是,c代码中的函数名相信很多人都已经发现了,和我们在java代码中声明的native不同,长了一大串,它的格式其实是 JNIEXPORT 返回值类型 JNICALL...首先,我们需要获取java中函数的methodID,例如我这里是获取设置进度条进度的方法,先获取方法所在jclass,需要注意的是,这里FindClass中第二个参数classname需要完整的类名,因此需要包名...中引用完java对象后及时调用env->DeleteLocalRef方法手动释放本地引用 如果native method返回java对象就不需要手动release,因为java会自动回收 好了,
正常情况下,如果不做特殊的处理,在主线程中是不能够捕获到子线程中的异常的。 例如下面的情况。...import java.util.concurrent.ThreadFactory; public class ThreadExceptionDemo { public static void main...如果想要在主线程中捕获子线程的异常,我们需要使用ExecutorService,同时做一些修改。...import java.util.concurrent.ThreadFactory; public class ThreadExceptionDemo { public static void main...上面的方式是设置每一个线程执行时候的异常处理。如果每一个线程的异常处理相同,我们可以用如下的方式进行处理,使用Thread的静态方法。
前面介绍了JNI的基本规范以及JNI的接口的生成过程。本文通过一个jni_test 应用实践操作JNI的接口各种典型应用。 ...一、UI设计 通过修改active_main.xml(为了简单起见采用相对布局),或者直接使用design工具拖动布局(先拖一个layout线下布局,再在该布局上排上控件,类似MFC的UI设计原理),UI...// 3、查找实例方法的ID jmethodID_Method_Instance = env->GetMethodID(mJclass,...3、查找实例方法的ID jmethodID_Method_Instance2 = env->GetMethodID(mJclass,..., ... // 删除局部引用 env->DeleteLocalRef(jobjectMyClass); return ret; } //回调java中的类中静态变量 extern
字节码 Class 对象 , 对应 C/C++ 中的 jclass 对象 ; 参数 : 传入 完整的 包名/类名 , 注意包名中使用 “/” 代替 “.” , 如 “kim/hsl/jni/Teacher...; 如 : 要获取 kim.hsl.jni.Student 类中的函数签名 , 使用 javap -s kim.hsl.jni.Student 命令 ; 5 ....获取 Student 的 public int getAge() 方法 jmethodID method_getAge = env->GetMethodID(student_class, "getAge...method_setTeacher = env->GetMethodID(student_class, "setTeacher" , "(Lkim/hsl/jni/Teacher;)V");...("kim/hsl/jni/Teacher"); // 5.3 查找构造方法 jmethodID method_init = env->GetMethodID(class_teacher
JavaVM 是 Java虚拟机在 JNI 层的代表,JNI 全局只有一个;而JNIEnv是 JavaVM 在线程中的代表,每个线程都有一个,JNI 中可能有很多个 JNIEnv。...局部引用只在创建它们的线程中有效,不能跨线程传递。...比如某些情况下,我们可能需要在native method中创建大量的局部引用,会导致native memory的内存泄漏,如果在native method返回之前native memory以及被用光,会导致...每当线程从Java环境切换到native code时,JVM都会分配一块内存,创建一个 局部引用表 ,这个表用来存放native method执行中创建的所有 局部引用。...JNI中的局部引用并不是nativde code中的局部变量,两者的区别可以总结如下: 局部变量存储在线程堆栈中,而 Local Reference 存储在 Local Ref 表中 局部变量在函数退栈后被删除