Android NDK开发总结
1,搭建本地NDK环境
Build path中设置C/C++ build Build command ndk-build NDK_DEBUG=1
C/C++ General 中设置 path and symbols为
在AndroidManifest的Application中设置Debuggable的值为true,此时可能有错误提示不能设置true,打开Problem 右键Quick Fix–>Disable check in this file only就可以了
打开Cygwin,用cd命令定位到工程目录下,我的是 cd /cygdrive/f/练习/androidTest
然后执行ndk-gdb命令,如果提示有冲突,则先关闭eclipse再执行
然后设置相应断点 ,Debug as Android Native Application就可以进入C/C++高度的模式了
2.NDK总结,需要引用第三方库时
D:\android NDK开发、编译、调试环境搭建与操作入门 - qiang106 - ITeye技术网站.mht—比较全面的总结
当进入JNI调试状态 ,又需要引用第三方so库时,此时编译会自动清掉第三方库,解决方法如下
1. 在jni目录下添加需要导入的.so文件,这里以ibtpnsSecurity.so为例
- 在jni目录下的Android.mk文件中下添加脚本
[html] view plaincopy
include $(CLEAR_VARS)
LOCAL_MODULE := libtpnsSecurity
LOCAL_SRC_FILES := libtpnsSecurity.so
include $(PREBUILT_SHARED_LIBRARY)
问题解决!
将第三方so库导入到jni目录下,然后配置编译文件,将so文件还原编译到libs下面
NDK JNI开发中内存管理和释放
1、什么需要释放?
什么需要什么呢 ? JNI 基本数据类型是不需要释放的 , 如 jint , jlong , jchar 等等 。 我们需要释放是引用数据类型,当然也包括数组家族。如:jstring,jobject ,jobjectArray,jintArray 等等。
当然,大家可能经常忽略掉的是 jclass ,jmethodID , 这些也是需要释放的哦
2、如何去释放?
1) 释放
jstring jstr = NULL;
char* cstr = NULL;
//调用方法
jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, getName);
cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
//释放资源
(*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
(*jniEnv)->DeleteLocalRef(jniEnv, jstr); 释放 类 、对象、方法
3) 释放 数组家族
jobjectArray arrays = NULL;
jclass jclsStr = NULL;
jclsStr = (*jniEnv)->FindClass(jniEnv, "java/lang/String");
arrays = (*jniEnv)->NewObjectArray(jniEnv, len, jclsStr, 0);
(*jniEnv)->DeleteLocalRef(jniEnv, jclsStr); //释放String
(*jniEnv)->DeleteLocalRef(jniEnv, arrays); //释放jobjectArray数组
native method 调用 DeleteLocalRef() 释放某个 JNI Local Reference 时,首先通过指针 p 定位相应的 Local Reference 在 Local Ref 表中的位置,然后从Local Ref 表中删除该 Local Reference,也就取消了对相应 Java 对象的引用(Ref count 减 1)
5.2.1 释放局部引用
大部分情况下,你在实现一个本地方法时不必担心局部引用的释放问题,因为本地方法被调用完成后,JVM会自动回收这些局部引用。尽管如此,以下几种情况下,为了避免内存溢出,JNI程序员应该手动释放局部引用:
1、 在实现一个本地方法调用时,你需要创建大量的局部引用。这种情况可能会导致JNI局部引用表的溢出,所以,最好是在局部引用不需要时立即手动删除。比如,在下面的代码中,本地代码遍历一个大的字符串数组,每遍历一个元素,都会创建一个局部引用,当对这个元素的遍历完成时,这个局部引用就不再需要了,你应该手动释放它:
for (i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* process jstr */
(*env)->DeleteLocalRef(env, jstr);
}
2、 你想写一个工具函数,这个函数被谁调用你是不知道的。4.3节中的MyNewString演示了怎么样在工具函数中使用引用后,使用DeleteLocalRef删除。不这样做的话,每次MyNewString被调用完成后,就会有两个引用仍然占用空间。
3、 你的本地方法不会返回任何东西。例如,一个本地方法可能会在一个事件接收循环里面被调用,这种情况下,为了不让局部引用累积造成内存溢出,手动释放也是必须的。
4、 你的本地方法访问一个大对象,因此创建了一个对这个大对象的引用。然后本地方法在返回前会有一个做大量的计算过程,而在这个过程中是不需要前面创建的对大对象的引用的。但是,计算过程,对大对象的引用会阻止GC回收大对象。
在下面的程序中,因为预先有一个明显的DeleteLocalRef操作,在函数lengthyComputation的执行过程中,GC可能会释放由引用lref指向的对象。
JNI回调类型对应表
一旦你有了这个头文件,你就需要写头文件对应的本地方法,就像我在清单C做的那样。注意:所有的本地方法的第一个参数都是指向JNIEnv结构的。 这个结构是用来调用JNI函数的,(我会在另一个章节中讨论)。第二个参数jclass的意义,要看方法是不是静态的(static)或者实例 (Instance)的。前者,jclass代表一个类对象的引用,而后者是被调用的方法所属对象的引用。最后的两个jint参数表示了Java方法的 int参数。
返回值和参数类型根据等价约定映射到本地C/C++类型,如表A所示。有些类型,如清单B里面的两个jint参数,在本地代码中可直接使用,而其他类型只有通过JNI调用操作。
表A
Java类型 | 本地类型 | 描述 |
---|---|---|
boolean | jboolean | 8位整型 |
byte | jbyte | 带符号的8位整型 |
char | jchar | 无符号的16位整型 |
short | jshort | C/C++ 带符号的16位整型 |
int | jint | C/C++带符号的32位整型 |
long | jlong | C/C++带符号的64位整型e |
float | jfloat | C/C++32位浮点型 |
double | jdouble | C/C++64位浮点型 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 Class jclass Class对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
double[] | jdoubleArray | 双浮点型数组 |
※ JNI类型映射
最后一步是把本地代码编译成共享库(比如,UNIX的so文件,Windows的dll文件)。在Java中调用方法前,共享库须通过System.loadLibrary导入。最常用的方式是在类的静态(static)初始化器里做这这个工作。
在本地代码中访问JNI
我举的例子很简单,并不能满足演示怎样写JNI方法的目标。现在,让我们看一些高级的,通过JNIEnv结构使用非简单类型的例子。
JNI通过函数的形式提供了很多功能,供本地代码通过指向JNIEnv结构的指针调用;它作为第一个参数传递给每个本地方法。JNI函数的调用有下面几种格式(这里,假设env是指向JNIEnv的指针):
//C
(*env)-><jni function>( env, <parameters>)++ 格式
env-><jni function>( <parameters> )
这篇文章中接下来的例子我将会用C++格式。
使用数组:
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表B),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
表B
函数 | Java数组类型 | 本地类型 |
---|---|---|
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
JNI数组存取函数
当你对数组的存取完成后,要确保调用相应的Relea***XXArrayElements函数,参数是对应Java数组和 GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相 关的资源。
为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。
清单D包含了一个简单的类,它演示了本地代码如何使用Java数组。这个本地实现循环遍历一个整型(int)数组,返回这些元素的总和。为简单起见,这个清单包含了java代码和本地实现。我已经省略了头文件,它可以很方便地通过javah得到。
在本地代码中访问JNI
使用对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或 方法的ID是任何处理域和方法的函数的必须参数。
表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
表C
函数 | 描述 |
---|---|
GetFieldID | 得到一个实例的域的ID |
GetStaticFieldID | 得到一个静态的域的ID |
GetMethodID | 得到一个实例的方法的ID |
GetStaticMethodID | 得到一个静态方法的ID |
※域和方法的函数
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。
表D
类型 | 符号 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
objects对象 | Lfully-qualified-class-name;L类名 |
Arrays数组 | [array-type [数组类型 |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
※确定域和方法的符号
一旦你有了类和方法或者域的ID,你就能把它保存下来以后使用,而没有必要重复去获取。
有几个分别访问域和方法的函数。实例的域可以使用对应域的GetXXXField的变体函数访问。GetStaticXXXField函数用于静态类型。设置域的值,用SetXXXField 和SetStaticXXXField函数。表E包含了所有访问域的函数列表。
表E
Java 类型 | Method方法 |
---|---|
boolean | GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField |
byte | GetByteField, GetStaticByteField, SetByteField, SetStaticByteField |
char | GetCharField, GetStaticCharField, SetCharField, SetStaticCharField |
short | GetShortField, GetStaticShortField, SetShortField, SetStaticShortField |
int | GetIntField, GetStaticIntField, SetIntField, SetStaticIntField |
long | GetLongField, GetStaticLongField, SetLongField, SetStaticLongField |
float | GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField |
double | GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField |
object | GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField |
※访问域的函数
另外,方法的访问是由CallXXXMethod 函数和CallStaticXXXMethod函数完成的,XXX表明了方法的返回值类型。这些函数的变体允许传递数组参数 (CallXXXMethodA and CallStaticXXXMethodA)或者传递一个可变大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。
一个完整的列表
表F:一个完整的列表
返回类型 | 函数 |
---|---|
boolean | CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV |
byte | CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV |
char | CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV |
short | CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV |
int | CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV |
long | CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV |
float | CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV |
double | CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV |
void | CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV |
object | CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV |
※方法访问函数
清单E演示了如何在本地代码中调用方法。本地方法printRandom得到了静态方法Math.random的ID,并且调用它几次,打印出结果。实例方法也一样处理。
当你关注java的扩展时,JNI是一个强大的工具,它不会严重降低可移植性。我这里只是接触它的表面,仅仅向你演示了JNI的能力和潜力。我鼓励你获取