Android NDK开发总结

来源:互联网 发布:通讯软件排名 编辑:程序博客网 时间:2024/04/29 10:57

Android NDK开发总结

1,搭建本地NDK环境
build path

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为例

  1. 在jni目录下的Android.mk文件中下添加脚本
[html] view plaincopyinclude $(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的能力和潜力。我鼓励你获取

0 0