查看原文
其他

JNI与NDK编程知识基础详解

哆啦安全 2022-05-24

The following article is from Android开发编程 Author Android开发编程

前沿小记

1、Android架构师要学习的知识点有很多,后面我会总结下关于Android开发中jni和ndk开发的知识点,当前基础是越牢固越好,后期学习起来就不会太累,一点就懂;

2、今天我们就来总结下jni和ndk的基础知识点;

3、Android 平台从一开就已经支持了C/C++了。我们知道Android的SDK主要是基于Java的,所以导致了在用Android SDK进行开发的工程师们都必须使用Java语言。不过,Google从一开始就说明Android也支持JNI编程方式,也就是第三方应用完成可以通过JNI调用自己的C动态度;

一、什么是ndk

  • NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的;

  • NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so;

  • NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作;

  • NDK提供了一份稳定、功能有限的API头文件声明;Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog);

  • NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式;

  • 使用NDK,我们可以将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率;

  • 使用NDK,我们可以将需要保密的应用逻辑使用C开发。毕竟,Java包都是可以反编译的;

  • NDK促使专业so组件商的出现:比如视频库的编译、音频库、图片库滤镜等等;

  • NDK将使Android平台支持C开发的开端;NDK提供了的开发工具集合,使开发人员可以便捷地开发、发布C组件。同时,Google承诺在NDK后续版本中提高“可调式”能力,即提供远程的gdb工具,使我们可以便捷地调试C源码;

二、为什么使用NDK

  1. 在平台之间移植其应用;

  2. 重复使用现在库,或者提供其自己的库重复使用;

  3. 在某些情况下提性能,特别是像游戏这种计算密集型应用;

  4. 使用第三方库,现在许多第三方库都是由C/C++库编写的,比如Ffmpeg这样库;

  5. 不依赖于Dalvik Java虚拟机的设计;

  6. 代码的保护。由于APK的Java层代码很容易被反编译,而C/C++库反编译难度大;

三、jni详解

1、jni是什么?以及和ndk关系

  • JNI,全称为Java Native Interface,即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行;

  • 在Android Framework中,需要提供一种媒介或 桥梁,将Java层(上层)与C/C++层(下层)有机的联系起来,使得他们互相协调完成某些任务。而充当这种媒介的就是Java本地接口(JNI,Java Native Interface);

  • JNI提供一些列的接口,允许Java类与C/C++等本地编辑语言(在JNI中,这些语言被称为 本地语言)编写的应用 程序、模块 、库进行交互操作。比如,在Java类中使用C语言库中的函数或在C语言中使用 Java类库,都需要借助JNI;

  • Android NDK是一个开发工具集,提供一系列工具快速开发C/C++的动态库,并能自动将 .so/.dll 和 Java 应用一起打包到Apk;NDK提供工具可以方便JNI调用C/C++,而且提供了交叉编译器可以修改.mk文件生成特定CPU平台的动态库,并能将so和java应用一起打包到apk中;简单说就是JNI负责Java与C/C++进行互相操作,NDK提供工具方便在Android平台使用JNI;

2、JNI开发流程的步骤

  • 在Java中先声明一个native方法;

  • 编译Java源文件javac得到.class文件;

  • 通过javah -jni命令导出JNI的.h头文件;

  • 使用Java需要交互的本地代码,实现在Java中声明的Native方法;

  • 将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib);

  • 通过Java命令执行Java程序,最终实现Java调用本地代码;

3、 JNI数据结构

JNI函数表的组成就像C++的虚函数表。虚拟机可以运行多张函数表,举例来说,一张调试函数表,另一张是调用函数表。JNI接口指针仅在当前线程中起作用。这意味着指针不能从一个线程进入另一个线程,然而,可以在不同的咸亨中调用本地方法;

jdouble test (JNIEnv *env, jobject obj, jint i, jstring s){ const char *str = (*env)->GetStringUTFChars(env, s, 0); (*env)->ReleaseStringUTFChars(env, s, str); return 10;}

JNI有自己的原始数据类型和数据引用类型如下

四、jni交互原理详解

1、JavaVM

JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个JavaVM结构中封装了一些函数指针(或叫函数表结构),JavaVM中封装的这些函数指针主要是对JVM操作接口。另外,在C和C++中的JavaVM的定义有所不同,在C中JavaVM是JNIInvokeInterface_类型指针,而在C++中有对JNIInvokeInterface_进行了一次封装,比C中少了一个参数,这也是为什么JNI代码更推荐使用C++来编写的原因;

2、JNIEnv

JNIEnv是一个线程相关的结构体,该结构体代表了Java在本线程的执行环境

JNIEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构,而一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储TLS中。因此,不同的线程的JNIEnv是不同,也不能相互共享使用。JNIEnv结构也是一个函数表,在本地代码中通过JNIEnv的函数表来操作Java数据或者调用Java方法。也就是说,只要在本地代码中拿到了JNIEnv结构,就可以在本地代码中调用Java代码;

调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码;

操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象;

3、JNIEnv和JavaVM的区别

  • JavaVM:JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个;

  • JNIEnv:JavaVM 在线程中的代码,每个线程都有一个,JNI可能有非常多个JNIEnv;

4、JNIEnv与线程

  • JNIEnv是线程相关的,即在每一个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv,即线程A不能调用线程B的JNIEnv,所以JNIEnv不能跨线程;

  • JNIEnv只在当前线程有效:JNIEnv仅仅在当前线程有效;

  • JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方便,传入的JNIEnv是同样的;

  • 本地方法匹配多个JNIEnv:在Java层定义的本地方法,能够在不同的线程调用,因此能够接受不同的JNIEnv;

5、JNIEnv结构

6、JNIEnv相关的常用函数

创建Java中的对象 jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list); jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);字符串相关 jstring (*NewString)(JNIEnv*, const jchar*, jsize); jsize (*GetStringLength)(JNIEnv*, jstring); const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*); void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*); jstring (*NewStringUTF)(JNIEnv*, const char*); jsize (*GetStringUTFLength)(JNIEnv*, jstring); /* JNI spec says this returns const jbyte*, but that's inconsistent */ const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);获取数组相关 jsize GetArrayLength(jarray array){ return functions->GetArrayLength(this, array); }jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index);

 总结:

  1.  以上是学习jni的基础知识点,必须理解的,不懂的就问就学;

  2.  jni还有很多知识点,比如动态注册,静态注册等等;

  3.  要学习一点c语言和c++的基础知识点,后面会讲到总结。


【推荐阅读】

Android Native内存泄漏检测

Android NDK开发之JNI基础篇

用Asan提前解决NDK疑难crash

Android NDK开发基础之C语言的内存管理

Android NDK开发中快速定位Crash问题

NDK开发中Native方法的静态注册与动态注册

Native(C++)开发中如何使用ASan检测内存错误

Android高级开发中ANR/Native Crash问题的解决方法


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存