关闭

android NDK、JNI技术详解及NDK在Android系统架构中的位置关系

标签: androidNDKJNI介绍详解基础必看
664人阅读 评论(0) 收藏 举报
分类:

在上一篇文章中,我们介绍了NDK的下载地址。

本篇文章,我们主要阐明两个问题

1、什么是Android NDK,及其在Android系统架构中的位置?

2、什么事Android JNI技术以及使用方法?


一、什么是Android NDK?

Android NDK(Android Native Development Kit)是一系列的开发工具,允许程序开发人员在Android应用程序中嵌入C/C++语言编写的非托管代码。

Android NDK的最新版本是11b,集成了交叉编译器,支持ARMv5TE处理器指令集、JNI接口和一些稳定的库文件。

Android NDK提供一系列的说明文档、示例代码和开发工具,指导程序开发人员使用C/C++语言进行库文件开发,并提供便捷工具将库文件打包到apk文件中。

Android虚拟机允许应用程序源代码通过JNI技术调用在本地实现的源代码。

而且,Android NDK还提供了

● 一组交叉编译链(编译器、链接器等)来生成可以在Linux,OS X和Windows(用Cygwin)运行的二进制文件;

● 一组与由Android平台提供的稳定的本地API列表的头文件,它们在docs/STABLE-APIS.html中有说明;

● 一个编译系统(build system)可以允许开发者写一个非常短的编译文件(build files)去描述哪个源代码需要编译,并且怎样编译。编译系统可以解决所有的toolchain/platform/CPU/ABI细节的问题。并且,较晚的NDK版本中还添加了更多的可以不用改变开发者的编译文件的情况下的toolchains、platforms、系统接口。

通过以上的叙述,我们知道Android NDK解决了核心模块使用托管语言开发执行效率低下的问题;允许程序开发人员直接使用C/C++源代码,极大的提高了Android应用程序开发的灵活性。

但同时Android NDK也存在着一些不足。

NDK并不是一个可以编写通用的源代码并且可以在Android设备上运行的方法,你的应用程序还是需要使用JAVA程序,适当的处理系统事件来避免“应用程序没有反应”的对话框或者处理Android应用程序的生命周期。注意:可以适当的在源代码中写一个复杂的应用程序,用于启动/停止一个小型的“应用程序包”。

NDK在Android平台仅仅提供了有限的本地API和库文件的支持的系统头文件,然而一个标准的Android系统镜像包括许多本地共享库,这些都应该被考虑在更新和发行版本的可以彻底改变的实现细节。如果Android系统库没有明确的被NDK明确的支持,然后应用程序不应该依赖于它提供的,或者打破了将来在各种设备上的无线系统更新,选定的系统库将逐渐被添加到稳定的NDK API中。



二、什么时候用到NDK?

必须提高性能(例如,对大量数据进行排序);

跨平台应用移植、使用第三方库。举例说明:许多第三方库由C/C++语言编写,而Android应用程序需要使用现有的第三方库,如Ffmpeg、OpenCV、MP4V2这样的库;

底层程序设计(例如,应用程序不依赖Dalvik Java虚拟机);

驱动程序设计、调用系统的底层驱动、调用硬件;

对运行效率敏感的算法实现。



三、Android NDK与Android SDK的关系

Android  Sdk (Android Software Development Kit, 即Android软件开发工具包)。Android SDK 包含了SDK Manager 和 AVD Manage。对于android系统的一些开发版本的管理以及模拟器管理。

Android  Ndk (Native Development Kit)跟sdk差不多的,他也是一个开发工具包。用他开发c/c++是很方便,他有一个强大的编译集合。用于编译符合JNI语法规则的程序,并压缩到Android APK中供其他部分使用。

Android NDK对于Android SDK只是个组件,它可以帮我们生成的JNI兼容的共享库可以在大于Android1.5平台的ARM CPU上运行,将生成的共享库拷贝到合适的程序工程路径的位置上,以保证它们自动的添加到你的apk包中(并且签名的)。


再次分析上图我们可以知道    NDK是Google为Android进行本地开发而放出的一个本地开发工具,包括Android的Native API公共库以及编译工具,注意,NDK需要Android 1.5版本以上的支持。由上图可知NDK处在开发流程的编译环节,NDK是JNI开发的一个扩展工具包!针对Android平台,其支持的设备型号繁多,单单就设备的核心CPU而言,都有三大类:ARM、x86和MIPS,况且ARM又分为ARMv5和ARMv7等等,为何Android又能适配如此之多的设备?接着JNI开发流程的话,利用NDK,我们可以针对不同的手机设备,编译出对应可运行的本地共享库了,至于如何使用NDK进行编译、开发,我们留作下次再进行探讨。而SDK是Google提供的Android标准开发工具包,里面包含了完整的API文档,各Android版本的开发库,Android的虚拟机以及Android的打包工具等。众所周知,Android的应用开发语言是Java,App的运行时是Delvik Runtime,属于JVM的改良版本,官方说Delvik VM更适用于移动设备。一般而言,由于Google的SDK提供了强大又完善的API,开发一般需求的应用,SDK足矣。然而前面已经说过,Java的运行效率引发了不少问题,因而才有了JNI技术的存在,那SDK和NDK的关系是怎样的呢?见下图解说,可以说,NDK是SDK的一个补充。  总之NDK与SDK是并列关系,DNK是SDK的有效补充。



四、JAVA  JNI (Java Native Interface),Java的本地接口化技术

1、我们在上文中提到NDK仅仅是一个开发工具包,就相当于Android SDK仅仅是开发编译集成环境,真正的APP程序是用java语言编写的一样,NDK是用来编译JAVA语言的只不过这里的JAVA语言要符合某种编写机制,这种机制就是JNI(JAVA Native Interface)

JNI是一种在Java虚拟机控制下执行代码的标准机制,  JNI是Java众多开发技术中的一门,意在利用本地代码,为Java程序提供更高效,更灵活的拓展。JNI是一门技术,是Java Code和C/C++ Code联系的桥梁。


2、为何要用JNI技术

与其他类似接口(Netscape Java运行接口、Microsoft的原始本地接口、COM/Java接口)相比,JNI主要的竞争优势在于:它在设计之初就确保了二进制的兼容 性,JNI编写的应用程序兼容性以及在某些具体平台上的Java虚拟机兼容性(当谈及JNI,这里并不特别针对Dalvik;JNI由Oracle开发, 适用于所有Java虚拟机)。这就是为什么C/C++编译后的代码无论在任何平台上都能执行。不过,一些早期版本并不支持二进制兼容


3、JNI组织结构

 

图1 — JNI接口指针图1 — JNI接口指针

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

示例代码:

  1. jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s) 
  2.      const char *str = (*env)->GetStringUTFChars(env, s, 0); 
  3.      (*env)->ReleaseStringUTFChars(env, s, str); 
  4.      return 10
  • *env — 一个接口指针。
  • obj — 在本地方法中声明的对象引用。
  • i和s — 用于传递的参数。

原始类型(Primitive Type)在虚拟机和本机代码进行拷贝,对象之间使用引用进行传递。VM(虚拟机)要追踪所有传递给本地代码的对象引用。GC无法释放所有传递给本地代码的对象引用。与此同时,本机代码应该通知VM不需要的对象引用。

4、JNI 局部引用和全局引用

JNI定义了三种引用类型:局部引用、全局引用和全局弱引用。局部引用在方法完成之前是有效的。所有通过JNI函数返回的Java对象都是本地引 用。程序员希望VM会清空所有的局部引用,然而局部引用仅在其创建的线程里可用。如果有必要,局部引用可以通过接口中的DeleteLocalRef JNI方法立即释放:

  1. jclass clazz; 
  2. clazz = (*env)->FindClass(env, "java/lang/String"); 
  3. ... 
  4. (*env)->DeleteLocalRef(env, clazz) 

全局引用在完全释放之前都是有效的。要创建一个全局引用,需要调用NewGlobalRef方法。如果全局引用并不是必须的,可以通过DeleteGlobalRef方法删除:

  1. jclass localClazz; 
  2. jclass globalClazz; 
  3. ... 
  4. localClazz = (*env)->FindClass(env, "java/lang/String"); 
  5. globalClazz = (*env)->NewGlobalRef(env, localClazz); 
  6. ... 
  7. (*env)->DeleteLocalRef(env, localClazz); 

错误

JNI不会检查NullPointerException、IllegalArgumentException这样的错误,原因是:

  • 导致性能下降。
  • 在绝大多数C的库函数中,很难避免错误发生。

JNI允许用户使用Java异常处理。大部分JNI方法会返回错误代码但本身并不会报出异常。因此,很有必要在代码本身进行处理,将异常抛给Java。在JNI内部,首先会检查调用函数返回的错误代码,之后会调用ExpectOccurred()返回一个错误对象。

  1. jthrowable ExceptionOccurred(JNIEnv *env); 

例如:一些操作数组的JNI函数不会报错,因此可以调用ArrayIndexOutofBoundsException或ArrayStoreExpection方法报告异常。

5、JNI原始类型

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

Java类型

本地类型(JNI

描述

boolean(布尔型) jboolean 无符号8个比特
byte(字节型) jbyte 有符号8个比特
char(字符型) jchar 无符号16个比特
short(短整型) jshort 有符号16个比特
int(整型) jint 有符号32个比特
long(长整型) jlong 有符号64个比特
float(浮点型) jfloat 32个比特
double(双精度浮点型) jdouble 64个比特
void(空型) void N/A

JNI引用类型

图2 — JNI引用类型图2 — JNI引用类型

6、改进的UTF-8编码

JNI使用改进的UTF-8字符串来表示不同的字符类型。Java使用UTF-16编码。UTF-8编码主要使用于C语言,因为它的编码用\u000表示为0xc0,而不是通常的0×00。非空ASCII字符改进后的字符串编码中可以用一个字节表示。


7、JNI函数:

JNI接口不仅有自己的数据集(dataset)也有自己的函数。回顾这些数据集和函数需要花费我们很多时间。可以从官方文档中找到更多信息:

http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/functions.html

JNI函数使用示例

下面会通过一个简短的示例确保你对这些资料所讲的内容有了正确的理解:

  1. #include <jni.h> 
  2.     ... 
  3. JavaVM *jvm; 
  4. JNIEnv *env; 
  5. JavaVMInitArgs vm_args; 
  6. JavaVMOption* options = new JavaVMOption[1]; 
  7. options[0].optionString = "-Djava.class.path=/usr/lib/java"
  8. vm_args.version = JNI_VERSION_1_6; 
  9. vm_args.nOptions = 1; 
  10. vm_args.options = options; 
  11. vm_args.ignoreUnrecognized = false
  12. JNI_CreateJavaVM(&jvm, &env, &vm_args); 
  13. delete options; 
  14. jclass cls = env->FindClass("Main"); 
  15. jmethodID mid = env->GetStaticMethodID(cls, "test""(I)V"); 
  16. env->CallStaticVoidMethod(cls, mid, 100); 
  17. jvm->DestroyJavaVM(); 

让我们来逐个分析字符串:

  • JavaVM — 提供了一个接口,可以调用函数创建、删除Java虚拟机。
  • JNIEnv — 确保了大多数的JNI函数。
  • JavaVMlnitArgs —  Java虚拟机参数。
  • JavaVMOption — Java虚拟机选项。

JNI的_CreateJavaVM()方法初始化Java虚拟机并向JNI接口返回一个指针。

JNI_DestroyJavaVM()方法可以载入创建好的Java虚拟机。










0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:169277次
    • 积分:3250
    • 等级:
    • 排名:第10480名
    • 原创:115篇
    • 转载:275篇
    • 译文:2篇
    • 评论:25条
    最新评论