导读
为了在Android OS系统上开发应用程序,Google提供了两种开发包:SDK和NDK。你可以从Google官方查阅到有许多关于SDK的优秀的书籍、文章作为参考,但Google没有提供足够的NDK资料。在现有的书籍中,我认为Cinar O.写于2012年的”Pro Android C++ with the NDK”值得一读。
本文旨在帮助那些缺乏Android NDK经验但又想扩充这方面知识的人们。我所关注的是JNI(本地编程接口,简称JNI)。本文分上下两篇,在上篇中,会从JNI为接口开始讲起;下篇会进行回顾,并给出带两个文件读写功能的实例。
ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Android开发 小组。参与方式请查看小组简介。
什么是 Android NDK?
Android NDK(Native Development Kit )是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。
何时使用NDK?
Google仅在极少数情况下建议使用NDK,有如下使用场景:
- 必须提高性能(例如,对大量数据进行排序)。
- 使用第三方库。举例说明:许多第三方库由C/C++语言编写,而Android应用程序需要使用现有的第三方库,如Ffmpeg、OpenCV这样的库。
- 底层程序设计(例如,应用程序不依赖Dalvik Java虚拟机)。
什么是JNI?
JNI是一种在Java虚拟机控制下执行代码的标准机制。代码被编写成汇编程序或者C/C++程序,并组装为动态库。也就允许了非静态绑定用法。这提供了一个在Java平台上调用C/C++的一种途径,反之亦然。
JNI的优势
与其他类似接口(Netscape Java运行接口、Microsoft的原始本地接口、COM/Java接口)相比,JNI主要的竞争优势在于:它在设计之初就确保了二进制的兼容性,JNI编写的应用程序兼容性以及在某些具体平台上的Java虚拟机兼容性(当谈及JNI,这里并不特别针对Dalvik;JNI由Oracle开发,适用于所有Java虚拟机)。这就是为什么C/C++编译后的代码无论在任何平台上都能执行。不过,一些早期版本并不支持二进制兼容。
二进制兼容性是一种程序兼容性类型,允许一个程序在不改变其可执行文件的条件下在不同的编译环境中工作。
JNI组织结构
![图1 — JNI接口指针](https://i-blog.csdnimg.cn/blog_migrate/597477cb810a4358309badd7c9263df9.png)
这张JNI函数表的组成就像C++的虚函数表。虚拟机可以运行多张函数表,举例来说,一张调试函数表,另一张是调用函数表。JNI接口指针仅在当前线程中起作用。这意味着指针不能从一个线程进入另一个线程。然而,可以在不同的线程中调用本地方法。
示例代码:
1
2
3
4
5
6
|
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
{
const
char
*str = (*env)->GetStringUTFChars(env, s, 0);
(*env)->ReleaseStringUTFChars(env, s, str);
return
10;
}
|
- *env — 一个接口指针。
- obj — 在本地方法中声明的对象引用。
- i和s — 用于传递的参数。
原始类型(Primitive Type)在虚拟机和本机代码进行拷贝,对象之间使用引用进行传递。VM(虚拟机)要追踪所有传递给本地代码的对象引用。GC无法释放所有传递给本地代码的对象引用。与此同时,本机代码应该通知VM不需要的对象引用。
局部引用和全局引用
JNI定义了三种引用类型:局部引用、全局引用和全局弱引用。局部引用在方法完成之前是有效的。所有通过JNI函数返回的Java对象都是本地引用。程序员希望VM会清空所有的局部引用,然而局部引用仅在其创建的线程里可用。如果有必要,局部引用可以通过接口中的DeleteLocalRef JNI方法立即释放:
1
2
3
4
|
jclass clazz;
clazz = (*env)->FindClass(env,
"java/lang/String"
);
...
(*env)->DeleteLocalRef(env, clazz)
|
全局引用在完全释放之前都是有效的。要创建一个全局引用,需要调用NewGlobalRef方法。如果全局引用并不是必须的,可以通过DeleteGlobalRef方法删除:
1
2
3
4
5
6
7
|
jclass localClazz;
jclass globalClazz;
...
localClazz = (*env)->FindClass(env,
"java/lang/String"
);
globalClazz = (*env)->NewGlobalRef(env, localClazz);
...
(*env)->DeleteLocalRef(env, localClazz);
|
错误
JNI不会检查NullPointerException、IllegalArgumentException这样的错误,原因是:
- 导致性能下降。
- 在绝大多数C的库函数中,很难避免错误发生。
JNI允许用户使用Java异常处理。大部分JNI方法会返回错误代码但本身并不会报出异常。因此,很有必要在代码本身进行处理,将异常抛给Java。在JNI内部,首先会检查调用函数返回的错误代码,之后会调用ExpectOccurred()返回一个错误对象。
1
|
jthrowable ExceptionOccurred(JNIEnv *env);
|
例如:一些操作数组的JNI函数不会报错,因此可以调用ArrayIndexOutofBoundsException或ArrayStoreExpection方法报告异常。
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引用类型](https://i-blog.csdnimg.cn/blog_migrate/b244f0df136ab4d27c82b69aada02d40.png)
改进的UTF-8编码
JNI使用改进的UTF-8字符串来表示不同的字符类型。Java使用UTF-16编码。UTF-8编码主要使用于C语言,因为它的编码用\u000表示为0xc0,而不是通常的0×00。非空ASCII字符改进后的字符串编码中可以用一个字节表示。
原文链接: elekslabs 翻译: ImportNew.com - 陈强
译文链接: http://www.importnew.com/8038.html