《Android的NDK开发系列.docx》由会员分享,可在线阅读,更多相关《Android的NDK开发系列.docx(22页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Android的NDK开发(1)Android JNI简介与调用流程1、JNI简介JNI全称为Java Native Interface(JAVA本地调用)。从Java1.1开始,JNI成为java平台的一部分,它允许Java代码和其他语言写的代码(如C&C+)进行交互。并非从Android发布才引入JNI的概念的。2、JNI与NDK 简单来说,Android的NDK提供了一些交叉编译工具链和Android自带的库,这些Android的库可以让开发者在编写本地语言的程序时调用。而NDK提供的交叉编译工具链就对已经编写好的C&C+代码进行编译,生成库。 当然了,你也可以自己搭建交叉编译环境,而不
2、用NDK的工具和库。然后生成库,只要规范操作,一样可以生成能让JAVA层成功调用的库文件的。 利用NDK进行编译本地语言可以参考这篇博文: 3、JNI 调用流程 众所周知,Android的应用层的类都是以Java写的,这些Java类编译为Dex文件之后,必须靠Dalvik虚拟机( Virtual Machine)来执行。假如在执行java程序时,需要载入C&C+函数时,Dalvik虚拟机就会去加载C&C+的库, (System.loadLibrary(libName);)让java层能顺利地调用这些本地函数。需要清楚一点,这些C&C+的函数 并不是在Dalvik虚拟机中运行的,所以效率和速度要
3、比在Dalvik虚拟机中运行得快很多。 Dalvik虚拟机成功加载库之后,就会自动地寻找库里面的JNI_OnLoad函数,这个函数用途如下:(1)告诉Dalvik虚拟机此C库使用哪一个JNI版本。如果你的库里面没有写明JNI_OnLoad()函数,VM会默认该库使用最老的JNI 1.1版本。但是新版的JNI做了很多的扩充,也优化了一些内容,如果需要使用JNI的新版功能,就必须在JNI_OnLoad()函数声明JNI的版本。如当没有JNI_OnLoad()函数时,Android调试信息会做出如下提示(No JNI_OnLoad found)result = JNI_VERSION_1_4;(2)
4、因为Dalvik虚拟机加载C库时,第一件事是调用JNI_OnLoad()函数,所以我们可以在JNI_OnLoad()里面进行一些初始化工作,如注册JNI函数等等。注册本地函数,可以加快java层调用本地函数的效率。04-29 13:53:12.184: D/dalvikvm(361): Trying to load lib /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea9804-29 13:53:12.204: D/dalvikvm(361): Added shared lib /data/data/com.con
5、owen.helloworld/lib/libHelloWorld.so 0x44edea9804-29 13:53:12.204: D/dalvikvm(361): No JNI_OnLoad found in /data/data/com.conowen.helloworld/lib/libHelloWorld.so 0x44edea98, skipping init另外:与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。4、例子(关于jni里面的数据类型转换与常用jni方法下一篇博文
6、介绍)下面以havlenapetr的FFmpeg工程里面的onLoad.cpp为例详细说一下:Android的NDK开发(2)利用Android NDK编写一个简单的HelloWorld1、Android NDK简介NDK全称为native development kit本地语言(C&C+)开发包。而对应的是经常接触的Android-SDK,(software development kit)软件开发包(只支持java语言开发)。简单来说利用NDK,可以开发纯C&C+的代码,然后编译成库,让利用Android-SDK开发的Java程序调用。NDK开发的可以称之为底层开发或者jni(java n
7、ative interface)层开发,SDK开发可以称为上层开发。2、为何要用NDK2.1、众所周知,利用SDK编写的代码,生成的APK,很容易就可以反编译了,安全性极为不高,而利用NDK开发的库,不容易被反编译,保密性,安全性都提高了。2.2、很多开源工程和大型工程都是C&C+代码,把它们转换为纯java语言显然是不可能的。2.3、C&C+的代码运行速度和效率都比java快很多。3、NDK环境的搭建在如我在windows下面用cygwin开发。配置环境变量可以参考简单来说就是在linux或者cygwin的安装目录,跳转到/home/YourName/,然后编辑.bash_profile文件
8、,在末尾添加如下语句,具体根据自己的情况而定。 ndk=/home/conowen/android-ndk-r7b export ndk4、新建一个NDK工程新建一个目录,命名为HelloWorld,然后在里面新建一个名为jni的目录(名称一定要是jni,因为ndk-build的时候会在 HelloWorld目录下寻找jni的目录,然后进行build),在jni目录下新建如下文件HelloWorld.c和Android.mk。HelloWorld.c文件代码如下(此JNI是没有jni_onLoad函数的)#include #include jstring Java_com_conowen_he
9、lloworld_HelloWorldActivity_helloWorldFromJNI( JNIEnv* env, jobject thiz ) return (*env)-NewStringUTF(env, HelloWorld! I am from JNI !); /*注意,这里 jstring 表示返回值 Java_com_conowen_helloworld_HelloWorldActivity_helloWorldFromJNI 写法是Java+Android工程的包名+Android工程的Activity名+方法名,点号用下划线表示,这个写法很严格。 包名:com_conowe
10、n_helloworld Activity名:HelloWorldActivity 方法名:helloWorldFromJNI JNIEnv* env, jobject thiz是Native方法自带的参数,可以用来转换一个数据类型。也就是说其实这个helloWorldFromJNI是没有形参的。 */ Android.mk代码如下关于Android.mk的编写格式,下一篇博文补全LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)#LOCAL_MODULE表示生成的库的名字,前面的lib和后缀名不用写LOCAL_MODULE := HelloWo
11、rldLOCAL_SRC_FILES := HelloWorld.cinclude $(BUILD_SHARED_LIBRARY)然后在终端里面,转到HelloWorld目录下,执行$ndk/ndk-build$ndk是之前配置的NDK环境变量。编译成功后,会在在目录生成libs和obj两个文件夹,libs里面有刚刚编译成的libHelloWorld.so库。 5、新建一个Android工程在eclipse里面新建一个Android工程,java代码如下,然后把刚刚生成的libs文件夹拖到Android工程目录下。 package com.conowen.helloworld; import
12、android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class HelloWorldActivity extends Activity /* Called when the activity is first created. */ Override public void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); TextView tv = new TextView(th
13、is); tv.setText(helloWorldFromJNI(); setContentView(tv); public native String helloWorldFromJNI();/ native声明,表示这个方法来自Native层。实现过程已经在native层实现了 static System.loadLibrary(HelloWorld);/ 加载库,前面的lib和,后缀名不用写 效果图:Android的NDK开发(3)JNI数据类型的详解 在Java中有两类数据类型:primitive types,如,int, float, char;另一种为reference type
14、s,如,类,实例,数组。注意:数组,不管是对象数组还是基本类型数组,都作为reference types存在,有专门的JNI方法取数组中每个元素。1、voidjava的void与JNI的void是一致的。2、基本数据类型3、对象类型相比基本类型,对象类型的传递要复杂得多。不能对Jstring进行直接操作。/如下使用方式是错误的,因为jstring不同于C语言中的char *类型。Java_com_conowen_test_testActivity_test(JNIEnv *env, jobject obj, jstring str)/* ERROR: incorrect use of jstr
15、ing as a char* pointer */printf(%s, str);.注意:typedef jint jsize;3.1、GetStringUTFChars与ReleaseStringUTFChars函数简单说明(跳到3.2有更方便的函数) JNI支持Unicode/UTF-8字符编码互转。Unicode以16-bits值编码;UTF-8是一种以字节为单位变长格式的字符编码,并与7- bitsASCII码兼容。UTF-8字串与C字串一样,以NULL(0)做结束符, 当UTF-8包含非ASCII码字符时,以0做结束符的规则不变。7-bit ASCII字符的取值范围在1-127之间,
16、这些字符的值域与UTF-8中相同。当最高位被设置时,表示多字节编码。/调用GetStringUTFChars,把一个Unicode字串转成UTF-8格式字串Java_com_conowen_test_testActivity_test(JNIEnv *env, jobject obj, jstring str)char buf128;const jbyte *cbyte;cbyte= (*env)-GetStringUTFChars(env, str, NULL);if (cbyte= NULL) return NULL;printf(%s, cbyte);(*env)-ReleaseStri
17、ngUTFChars(env, str, cbyte);scanf(%127s, buf);return (*env)-NewStringUTF(env, buf);/或者return (*env)-NewStringUTF(env, hello world); 上述函数中,有isCopy参数,当该值为JNI_TRUE,将返回str的一个拷贝;为JNI_FALSE将直接指向 str的内容。 注意:当isCopy为JNI_FALSE,不要修改返回值,不然将改变java.lang.String的不可变语义。一般会把isCopy设为 NULL,不关心Java VM对返回的指针是否直接指向java.l
18、ang.String的内容。 注意:在调用GetStringChars之后,一定要调用ReleaseStringChars做释放,(Unicode - UTF-8转换的原因)。不管在调用GetStringChars时为isCopy赋值JNI_TRUE还是JNI_FALSE,因不同JavaVM实现 的原因,ReleaseStringChars可能释放内存,也可能释放一个内存占用标记。3.2、GetStringRegion/GetStringUTFRegion函数简单说明因为这两个函数不涉及内存操作,所以较GetStringUTFChars使用要简单。也不用进行释放指针之类的操作,非常方便。(推荐
19、使用)Java_com_conowen_test_testActivity_test(JNIEnv *env, jobject obj, jstring str)char outputbuf128, inputbuf128;int len = (*env)-GetStringLength(env, str);(*env)-GetStringUTFRegion(env, str, 0, len, outbuf);printf(%s, outputbuf);scanf(%s, inputbuf);return (*env)-NewStringUTF(env, inbuf);GetStringUTF
20、Region有两个主要的参数,start 和 length, 这两个参数以Unicode编码计算. 该函数会做边界检查,所以可能抛出StringIndexOutOfBoundsException。3.3、GetStringLength/GetStringUTFLength函数简单说明前者是Unicode编码长度,后者返回的是是UTF编码长度。4、数组类型JNI对每种数据类型的数组都有对应的函数。4.1、常见错误操作:/* 直接操作数组是错误的 */Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)int i, sum
21、= 0;for (i = 0; i 10; i+) sum += arri;4.2、使用void GetArrayRegion(JNIEnv *env, array, jsize start,jsize len, *buf);进行操作参数说明:env: the JNIEnv interface pointer.array: a reference to an array whose elements are to be copied.将要被拷贝的目标数组start: the starting index of the array elements to be copied.(数组的起始位置)l
22、en: the number of elements to be copied.(拷贝元素的个数)buf:the destination buffer.存放结果的本地数组返回值:voidJava_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)jint buf10;jint i, sum = 0;(*env)-GetIntArrayRegion(env, arr, 0, 10, buf);for (i = 0; i 10; i+) sum += bufi;return sum;JNI中数组的基类为jarray,其他如jintA
23、rray都是继承自jarray。4.3、使用 *GetArrayElements(JNIEnv *env, array, jboolean *isCopy);进行数组操作参数说明:env: the JNIEnv interface pointer.array: a reference to the primitive array whose elements are tobe accessed.(目标数组)isCopy: a pointer to a jboolean indicating whether a function返回值:返回指向Java数组的一个直接的指针使用实例:Java_In
24、tArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)jint *carr;jint i, sum = 0;carr = (*env)-GetIntArrayElements(env, arr, NULL);if (carr = NULL) return 0; /* exception occurred */for (i=0; iReleaseIntArrayElements(env, arr, carr, 0);return sum;)更多数组操作函数:5、另外一些有用的宏定义(来自jni.h)#define JNI_FALSE 0#
25、define JNI_TRUE 1#define JNI_VERSION_1_1 0x#define JNI_VERSION_1_2 0x#define JNI_VERSION_1_4 0x#define JNI_VERSION_1_6 0x#define JNI_OK (0) /* no error */#define JNI_ERR (-1) /* generic error */#define JNI_EDETACHED (-2) /* thread detached from the VM */#define JNI_EVERSION (-3) /* JNI version error
26、 */#define JNI_COMMIT 1 /* copy content, do not free buffer */#define JNI_ABORT 2 /* free buffer w/o copying back */Android的NDK开发(4)JNI数据结构之JNINativeMethod 1、JNINativeMethod 结构体的官方定义typedef struct const char* name;const char* signature;void* fnPtr; JNINativeMethod;第一个变量name是Java中函数的名字。第二个变量signature
27、,用字符串是描述了Java中函数的参数和返回值第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数的native方法名字更多内容请查看之前博文:示例:/* * 由于gMethods是一个对照表,在程序执行时, * 可多次调用registerNativeMethods()函数来更换本地函数的指针, * 从而达到弹性调用本地函数的目的。 *具体可以参看 */ static JNINativeMethod gMethods = setDataSource, (Ljava/lang/String;)V
28、, (void *)com_media_ffmpeg_FFMpegPlayer_setDataSource, _setVideoSurface, (Landroid/view/Surface;)V, (void *)com_media_ffmpeg_FFMpegPlayer_setVideoSurface, prepare, ()V, (void *)com_media_ffmpeg_FFMpegPlayer_prepare, _start, ()V, (void *)com_media_ffmpeg_FFMpegPlayer_start, _stop, ()V, (void *)com_me
29、dia_ffmpeg_FFMpegPlayer_stop, getVideoWidth, ()I, (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth, getVideoHeight, ()I, (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight, seekTo, (I)V, (void *)com_media_ffmpeg_FFMpegPlayer_seekTo, _pause, ()V, (void *)com_media_ffmpeg_FFMpegPlayer_pause, isPl
30、aying, ()Z, (void *)com_media_ffmpeg_FFMpegPlayer_isPlaying, getCurrentPosition, ()I, (void *)com_media_ffmpeg_FFMpegPlayer_getCurrentPosition, getDuration, ()I, (void *)com_media_ffmpeg_FFMpegPlayer_getDuration, _release, ()V, (void *)com_media_ffmpeg_FFMpegPlayer_release, _reset, ()V, (void *)com_
31、media_ffmpeg_FFMpegPlayer_reset, setAudioStreamType, (I)V, (void *)com_media_ffmpeg_FFMpegPlayer_setAudioStreamType, native_init, ()V, (void *)com_media_ffmpeg_FFMpegPlayer_native_init, native_setup, (Ljava/lang/Object;)V, (void *)com_media_ffmpeg_FFMpegPlayer_native_setup, native_finalize, ()V, (vo
32、id *)com_media_ffmpeg_FFMpegPlayer_native_finalize, native_suspend_resume, (Z)I, (void *)com_media_ffmpeg_FFMpegPlayer_native_suspend_resume, ; 主要是第二个参数比较复杂:括号里面表示参数的类型,括号后面表示返回值。() 中的字符表示参数,后面的则代表返回值。例如()V 就表示void Fun();(II)V 表示 void Fun(int a, int b);这些字符与函数的参数类型的映射表如下:2、第二个参数之基本数据类型3、第二个参数之对象类型与数组类型对象类型:以L开头,以;结尾,中间是用/ 隔开。如上表第1个数组类型:以开始。如上表第2个(n维数组的话,则是前面多少个而已,如D表示“double”)对象数组类型:上述两者结合,如上表第3个3.1、对象类型与数组类型的举例: