JNI与NDK编程知识基础详解
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
在平台之间移植其应用;
重复使用现在库,或者提供其自己的库重复使用;
在某些情况下提性能,特别是像游戏这种计算密集型应用;
使用第三方库,现在许多第三方库都是由C/C++库编写的,比如Ffmpeg这样库;
不依赖于Dalvik Java虚拟机的设计;
代码的保护。由于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);
总结:
以上是学习jni的基础知识点,必须理解的,不懂的就问就学;
jni还有很多知识点,比如动态注册,静态注册等等;
要学习一点c语言和c++的基础知识点,后面会讲到总结。
【推荐阅读】
Android高级开发中ANR/Native Crash问题的解决方法