Android NDK开发总结

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 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类型本地类型描述
booleanjboolean8位整型
bytejbyte带符号的8位整型
charjchar无符号的16位整型
shortjshortC/C++ 带符号的16位整型
intjintC/C++带符号的32位整型
longjlongC/C++带符号的64位整型e
floatjfloatC/C++32位浮点型
doublejdoubleC/C++64位浮点型
Objectjobject任何Java对象,或者没有对应java类型的对象 Class jclass Class对象
Stringjstring字符串对象
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数组类型本地类型
GetBooleanArrayElementsjbooleanArrayjboolean
GetByteArrayElementsjbyteArrayjbyte
GetCharArrayElementsjcharArrayjchar
GetShortArrayElementsjshortArrayjshort
GetIntArrayElementsjintArrayjint
GetLongArrayElementsjlongArrayjlong
GetFloatArrayElementsjfloatArrayjfloat
GetDoubleArrayElementsjdoubleArrayjdouble

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

类型符号
booleanZ
byteB
charC
shortS
intI
longL
floatF
doubleD
voidV
objects对象Lfully-qualified-class-name;L类名
Arrays数组[array-type [数组类型
methods方法(argument-types)return-type(参数类型)返回类型

※确定域和方法的符号
一旦你有了类和方法或者域的ID,你就能把它保存下来以后使用,而没有必要重复去获取。
有几个分别访问域和方法的函数。实例的域可以使用对应域的GetXXXField的变体函数访问。GetStaticXXXField函数用于静态类型。设置域的值,用SetXXXField 和SetStaticXXXField函数。表E包含了所有访问域的函数列表。
表E

Java 类型Method方法
booleanGetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField
byteGetByteField, GetStaticByteField, SetByteField, SetStaticByteField
charGetCharField, GetStaticCharField, SetCharField, SetStaticCharField
shortGetShortField, GetStaticShortField, SetShortField, SetStaticShortField
intGetIntField, GetStaticIntField, SetIntField, SetStaticIntField
longGetLongField, GetStaticLongField, SetLongField, SetStaticLongField
floatGetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField
doubleGetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField
objectGetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField

※访问域的函数
另外,方法的访问是由CallXXXMethod 函数和CallStaticXXXMethod函数完成的,XXX表明了方法的返回值类型。这些函数的变体允许传递数组参数 (CallXXXMethodA and CallStaticXXXMethodA)或者传递一个可变大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。

一个完整的列表
表F:一个完整的列表

返回类型函数
booleanCallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV
byteCallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV
charCallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV
shortCallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV
intCallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV
longCallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV
floatCallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV
doubleCallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV
voidCallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV
objectCallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV

※方法访问函数
清单E演示了如何在本地代码中调用方法。本地方法printRandom得到了静态方法Math.random的ID,并且调用它几次,打印出结果。实例方法也一样处理。
当你关注java的扩展时,JNI是一个强大的工具,它不会严重降低可移植性。我这里只是接触它的表面,仅仅向你演示了JNI的能力和潜力。我鼓励你获取

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值