




已阅读5页,还剩38页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
AndroidNDKJNI入门进阶,AndroidNDK简介下载和安装AndroidNDK配置AndroidNDK的开发环境AndroidJNI接口设计编写AndroidNDK程序的步骤配置Android.mk文件AndroidNDK定义的变量AndroidNDK定义的函数配置Application.mk文件关于JNI的一些相关操作JNI调用Java方法本章小结,AndroidNDK简介,AndroidNDK(NativeDevelopmentkit)是一套允许开发人员将本地代码嵌入Android应用程序的开发包。众所周知,Android应用程序运行在Dalvik虚拟机上。而NDK允许开发人员将Android应用程序中的部分功能(由于NDK只开发了部分接口,因此,无法使用NDK编写完整的Android应用程序)用c/c+语言来实现,并将部分c/c+代码编译成可直接运行在Android平台上的本地代码。这些本地代码以动态链接库(libso)的形式存在。NDK的这个特性既有利于代码的重用,也可以在某种程度上提高程序的运行速度。,AndroidNDK简介,NDK由如下几部分组成:提供了一套工具集,这套工具集可以将c/c+源代码生成本地代码。用于定义NDK接口的C头文件(*.h)和实现这些接口的库文件。一套编译系统。可以通过非常少的配置生成目标文件。,下载和安装AndroidNDK,AndroidNDK需要一个c/c+编译环境才能使用。因此不仅要安装AndroidNDK,还需要安装相应的c/c+环境。如果在Linux下使用AndroidNDK,因为一般Linux安装包都自带了c/c+编译环境,所以只需要在安装Linux时选中相应的开发工具即可。如果在windows下使用AndroidNDK,仍然需要使用Linux环境的c/c+编译器来生成libso文件。这是Linux/UNIX下的动态链接库文件,相当于window下的dll文件。文件名必须以lib开头,文件扩展名必须是.so。例如libLog.so、libImage.so等。可以从如下地址下载AndroidNDK的最新版本,配置AndroidNDK的开发环境,设置AndroidNDK的路径在Linux环境下(fedora)安装了android-ndk-r5b-mips-linux(用于板子上生成so)和android-ndk-r5c(用于手机或模拟器上生成so),现在主要用android-ndk-r5c为大家讲解。在根目录下vi.bash_profile,在该文件上更改路径如下:#.bash_profile#Getthealiasesandfunctionsif-f/.bashrc;then./.bashrcfi#UserspecificenvironmentandstartupprogramsPATH=$PATH:$HOME/bin:/home/xiongwq/ndk-x86/android-ndk-r5cexportPATHANDROID_NDK_ROOT=:/home/xiongwq/ndk-x86/android-ndk-r5cexportANDROID_NDK_ROOT,配置AndroidNDK的开发环境,安装AndroidNDK开发环境在完成第一步后,重启linux系统。然后在终端控制台中执行下面的命令进入AndroidNDK中的根目录:cd$ANDROID_NDK_ROOT然后运行ndk里面自带的samples/hello-jni的例子,输入ndk-buildB命令,如果出现图片所示的内容,说明AndroidNDK开发环境已经安装成功。,AndroidJNI接口设计,AndroidNDK应用程序的接口实际上就是在JNI(JavaNativeInterface)规范中定义的接口。JNI规范中定义了Java调用动态链接库(*.dll或*.so文件,由于Android是Linux内核的操作系统,因此只有*.so文件)的约定。这里的接口就是指函数,包括函数名称、函数参数个数、函数参数类型及函数返回值的类型。,AndroidJNI接口设计,举个简单的例子,看一下hello-jni.c文件中的c语言函数,代码如下:JstringJava_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv*env,jobjectthiz)return(*env)-NewStringUTF(env,“HellofromJNI!”);上面的代码中的函数从表面上看只是一个普通的c语言函数,但这个函数和普通的c语言函数有如下3点不同:,AndroidJNI接口设计,类型不同该函数的返回值类型和参数类型都是在JNI的头文件中定义的类型(如jstring、jobject等)。这些类型与Java中的数据类型对应,例如,jstring对应Java中的String;jobject对应Java中的Object。在定义被Java调用的JNI函数时必须使用这些类型,否则Java无法成功调用这些函数。参数env和thiz上面的函数有两个参数:env和thiz。这两个参数必须包含在JNI函数中,而且必须是头两个参数。其中env表示JNI的调用环境,thiz表示定义native方法的Java类的对象本身。,AndroidJNI接口设计,JNI函数名不同打开hellojni工程中的HelloJni.java文件,看到在HelloJni类中定义的native方法是stringFromJNI,该方法调用了上面的C语言函数。但上面的C语言函数名却为Java_com_example_hellojni_HelloJni_stringFromJNI。从这个函数名可以看出,HelloJni类中的native方法stringFromJNI是该函数名的结尾部分。前面有一个由“_”分隔而成的组合前缀。其中Java是固定的,而com_example_hellojni_HelloJni是HelloJni类的全名(包名+类名),只是将“.”换成了“_”。从这一点看出,一个完整的JNI函数名由3部分组成:Java、定义native方法的类的全名、实际的函数名。这3部分用“_”进行连接。,AndroidJNI接口设计,总结上面3个与普通c语言函数的区别也是编写JNI函数的关键。不过开发人员也不需要太关注这3点,因为一般在编写JNI函数之前,需要先编写一个调用JNI函数的Java类(定义native方法的Java类),然后使用JDK中的javah.exe命令自动生成定义JNI函数的C语言头文件(*.h文件)。该文件中定义的函数会完全采用上面的3个规则,开发人员只需要将这个函数复制到C语言源文件(*.c)中,然后编写具体的实现即可。,编写AndroidNDK程序步骤,创建一个eclipseAndroid工程创建一个定义native方法的Java类,并在该类中定义native方法。方法名就是之前介绍的函数名的3部分。使用javah命令根据这个Java类生成c语言头文件。根据c语言头文件中定义的JNI函数编写c语言源文件(*.c文件)。函数的实现过程要根据具体的业务逻辑而定。在sourcessamples目录中建立一个子目录(也就是保存c语言源文件的目录),然后将c语言源文件复制到该目录中。在上一步建立的目录中创建一个Android.mk文件,也可以将hello-jni目录中的Android.mk文件复制到该目录下。,编写AndroidNDK程序步骤,利用ndk命令生成so库启动控制台,输入cd$ANDROID_NDK_ROOT命令进入AndroidNDK的根目录,并使用ndk-build-B命令编译c语言源文件。如果编译成功,我们会在上一步建立的project目录中看到一个libs目录。进入该目录中的armeabi目录,会看到一个lib*.so文件。然后直接将libs目录复制到EclipseAndroid工程的根目录(与src目录平级)。,编写AndroidNDK程序步骤,总结上面的7步描述了编写和调用ndk程序的完整步骤,接下来就是使用Java来调用native方法了。为了能更充分地理解编写AndroidNDK程序的步骤和具体实现细节,编写了一个将指小写字母转换成大写字母的例子。在该例子中,转换部分使用JNI函数编写。,编写AndroidNDK程序步骤,工程目录:srcch16_lowertoupper创建一个EclipseAndroid工程,工程名为ch16_lowertoupper.创建一个LowerToUpper类。在该类中定义了native方法,代码如下:,packagenet.blogjava.mobile.jni;importandroid.app.Activity;importandroid.os.Bundle;importandroid.widget.TextView;publicclassLowerToUpperextendsActivity/*Calledwhentheactivityisfirstcreated.*/OverridepublicvoidonCreate(BundlesavedInstanceState)super.onCreate(savedInstanceState);/*CreateaTextViewandsetitscontent.thetextisretrievedbycallinganativefunction.*/TextViewtv=newTextView(this);tv.setText(convert(a)+);setContentView(tv);/*Anativemethodthatisimplementedbythe*LowerToUpperinativelibrary,whichispackagedwiththisapplication.*/publicnativecharconvert(charch);/*thisisusedtoloadtheLowerToUpperlibraryonapplicationstartup.*/staticSystem.loadLibrary(LowerToUpper);,编写AndroidNDK程序步骤,在编写上面代码时应注意如下两点:1.native方法为convert。该方法的参数ch为待转换的字母。2.本例使用的lib*.so文件名是libLowerToUpper.so。必须在static块中使用System.loadLibrary方法加载该文件,但指定的文件名是不包括lib和.so部分的。,编写AndroidNDK程序步骤,生成C语言头文件打开windowscmd命令窗口,进入bin目录,并输入如下命令生成C语言头文件:javahjninet.blogjava.mobile.jni.LowerToUpper在执行完上面的命令后,会在当前目录生成一个net_blogjava_mobile_jni_LowerToUpper.h文件,内容如下:,编写AndroidNDK程序步骤,/*DONOTEDITTHISFILE-itismachinegenerated*/#include/*Headerforclassnet_blogjava_mobile_jni_LowerToUpper*/#ifndef_Included_net_blogjava_mobile_jni_LowerToUpper#define_Included_net_blogjava_mobile_jni_LowerToUpper#ifdef_cplusplusexternC#endif/*Class:net_blogjava_mobile_jni_LowerToUpper*Method:convert*Signature:(C)C*/JNIEXPORTjcharJNICALLJava_net_blogjava_mobile_jni_LowerToUpper_convert(JNIEnv*,jobject,jchar);#ifdef_cplusplus#endif#endif虽然在net_blogjava_mobile_jni_LowerToUpper.h文件中有很多代码,不过我们不需要管那么多,只要关注黑体字部分即可,该部分就是JNI函数的定义。,编写AndroidNDK程序步骤,在当前目录建立一个LowerToUpper.c文件,实现小写字母转换成大写字母的方法,代码如下:#include#include#include“net_blogjava_mobile_jni_LowerToUpper.h”JNIEXPORTjcharJNICALLJava_net_blogjava_mobile_jni_LowerToUpper_convert(JNIEnv*env,jobjectobj,jcharch)if(ch=97,编写AndroidNDK程序步骤,建立一个LowerToUpper目录在sourcessamples目录中建立一个LowerToUpper目录,然后将LowerToUpper.c文件复制到该目录。建立Android.mk文件在LowerToUpper目录中建立一个Android.mk文件,并输入如下内容:LOCAL_PATH:=$(callmy-dir)include$(CLEAR_VARS)LOCAL_MODULE:=LowerToUpperLOCAL_SRC_FILES:=LowerToUpper.cinclude$(BUILD_SHARED_LIBRARY)其中LOCAL_MODULE指定生成的lib*.so文件名(不包括lib和.so部分),LOCAL_SRC_FILES指定C语言文件名(LowerToUpper.c)。,编写AndroidNDK程序步骤,用ndk命令生成so库打开fedora终端,输入命令cd$ANDROID_NDK_ROOT进入NDK安装目录,然后进入LowerToUpper目录下输入命令ndk-buildB,目录下生成一个libs目录,在libsarmeabi目录中会看到一个libLowerToUpper.so文件。将libs目录复制到ch16_lowertoupper工程的根目录,然后启动Java应用程序,出现如下界面:,配置Android.mk文件,Android.mk文件的简介Android.mk文件主要用来指定要编译的c/c+源文件的位置。由于Android使用了GNU的make,因此Android.mk的语法格式与GNUMakefile的语法格式相同。Android.mk文件的核心部分是模块(modules),可以在模块中指定c/c+源文件的位置。模块可以用来指定静态库或共享库,其中只有共享库会被安装或复制到Android应用程序包(apk文件)中,而静态库可以用来生成共享库。,配置Android.mk文件,回顾一下刚才的那个例子,Android.mk文件如下:LOCAL_PATH:=$(callmy-dir)include$(CLEAR_VARS)LOCAL_MODULE:=LowerToUpperLOCAL_SRC_FILES:=LowerToUpper.cinclude$(BUILD_SHARED_LIBRARY)上面的代码涉及到一些变量和make命令。下面来解释一下这些内容。,配置Android.mk文件,LOACL_PATH:=$(callmy-dir)Android.mk文件的第一行必须是LOCAL_PATH变量,该变量用来指定参与编译的c/c+源文件的位置。在上面的例子中,宏函数my-dir是由系统提供的,用来返回当前目录的路径,也就是包含Android.mk文件的目录的路径。include$(CLEAR_VARS)CLEAR_VARS变量是在系统中定义的,用来指定一个特殊的GNUMake文件,该文件用来清空很多以LOCAL_开头的变量,例如,LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等。但这些变量不包括LOCAL_PATH。之所以要清空这些变量,是因为这些都是全局变量。同时这些变量又要在不同的GNUMake文件中使用,为了多个GNUMake文件不相互影响,就需要在执行每一个GNUMake文件(Android.mk文件)之前先清空这些变量。,配置Android.mk文件,LOCAL_MODULE:=LowerToUpper在每个模块中必须定义LOCAL_MODULE变量,用来指定模块名。该变量的值必须是唯一的,而且不能包含任何空白分隔符(例如空格、tab等)。实际上,LOCAL_MODULE变量的值就是生成共享库的文件名(不包括lib和.so),在编译时,系统会自动在文件名的前后添加lib和.so,例如,本例生成的共享库文件名是libLowerToUpper.so。要注意的是,如果模块名加了前缀lib,在生成共享库时系统不会再自动添加前缀lib。,配置Android.mk文件,LOCAL_SRC_FILES:=LowerToUpper.cLOCAL_SRC_FILES变量必须指定一个C/C+源文件列表。用该变量指定的源文件将被编译进当前模块中。但要注意,该变量并不需要指定C/C+的头文件列表(*.h),这是因为系统会自动计算当前的c/c+源文件include的头文件。而系统会直接将LOCAL_SRC_FILES变量指定的源文件传给编译器,这种处理方式会取得更好的效果。C+源文件的默认扩展名是.cpp。Include$(BUILD_SHARED_LIBRARY)BUILD_SHARED_LIBRARY是在系统中定义的,用来指定一个GNUMake脚本文件。该脚本文件会根据以LOCAL_开头的变量来生成共享库文件。如果想生成静态库文件,可以使用BUILD_STATIC_LIBARY变量。,AndroidNDK定义的变量,在系统分析Android.mk文件之前,会定义一些全局变量。在某些情况下,系统可以对Android.mk文件分析多次,而每次分析时,这些变量的值可能会不一样。下面介绍一下这些变量。,AndroidNDK定义的变量,CLEAR_VARS:指定一个用于清空几乎所有以“LOCAL_”开头的变量(除了LOCAL_PATH变量)的GNUMake脚本文件。在Android.mk的第2行(第一行设置LOCAL_PATH变量)必须执行这个脚本,例如,include$(CLEAR_VARS)BUILD_SHARED_LIBRARY:指定一个建立共享库的GNUMake脚本文件。该脚本文件会根据以“LOCAL_”开头的变量决定如何生成共享库。其中LOCAL_MODULE和LOCAL_SRC_FILES是必须设置的两个变量。该变量的用法:include$(BUILD_SHARED_LIBRARY)。生成的共享库文件名是lib$(LOCAL_MODULE).so。,AndroidNDK定义的变量,BUILD_STATIC_LIBRARY:指定一个建立静态库的GNUMake脚本文件。静态库不能被复制到Android应用程序包(apk文件)中,但可以用于建立共享库。使用该变量的用法:include$(BULD_STATIC_LIBRARY)。生成的静态库文件名是$(LOCAL_MODULE).a。TARGET_ARCH:编译Android的目标CPU架构的名称。例如与ARM兼容的CPU架构名称为arm。TARGET_PLATFORM:指定分析Android.mk文件的Android平台名称,目前只支持android-1.5。在NDKRevision2中该变量的值是android-3,该值与Android1.5平台相对应。,AndroidNDK定义的变量,TARGET_ARCH_ABI:用于分析Android.mk的目标CPU+ABI的名称。在这里ABI是指应用程序二进制接口(ApplicationBinaryInterface)。所有基于ARM的ABI都必须将TARGET_ARCH的值设为arm,但可以设置不同的TARGET_ARCH_ABI变量值。TARGET_ABI:该变量用于连接目标平台和ABI,也就是$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI),主要用来测试真实设备中特定的目标系统映像(TargetSystemImage)。,AndroidNDK定义的函数,在AndroidNDK中还定义了很多GNUMake函数宏。这些函数需要使用如下语法格式来调用,并返回信息:$(call)my-dir:返回Android.mk文件所在目录的路径。该函数一般用于设置LOCAL_PATH变量。用法:LOCAL_PATH:=$(callmy-dir)。all-subdir-makefiles:返回Android.mk文件所在目录(my-dir返回的路径)中所有包含Android.mk文件的子目录列表。例如,有如下的目录结构:sources/foo/Android.mksources/foo/lib1/Android.mksources/foo/lib2/Android.mk在sources/foo目录的Android.mk文件中使用了include$(callall-sudir-makefiles)。这个Android.mk文件会自动包含lib1和lib2目录中的Android.mk文件。,AndroidNDK定义的函数,this-makefile:返回当前的GNUMakefile的路径。parent-makefile:返回当前调用树中父一级的Makefile的路径。garand-parent-makefile:从这个函数的名字不难看出它的功能。返回parent的parentmakefile的路径。,关于JNI的一些相关操作,jclassFindClass(JNIEnv*env,constchar*name);查找类该函数可能做过Java开发的不会陌生,这个是JNI层的实现,需要注意的是第二个参数为constchar*类型的,我们如果从Java从层传入unicode编码的jstring类型需要使用GetStringUTFChars函数转换成utf8的constchar*,如果成功返回这个Java类的对象jclass,相关的异常可能有(1.ClassFormatError类的数据格式无效(2.ClassCircularityError该类或接口是自身的超类或超接口(3.NoClassDefFoundError没有找到指定名称的类或接口(4.OOM内存不足错误,即OutOfMemoryError2.jclassGetSuperclass(JNIEnv*env,jclassclazz);获取父类或者说超类该函数的第二个参数为jclass类,我们调用时传入的是子类,否则返回将是NULL,jbooleanIsAssignableFrom(JNIEnv*env,jclassclazz1,jclassclazz2);判断class1对象能否安全的强制转换为class2对象如果可以将返回JNI_TRUE,JNI_TRUE的定义值为1,否则返回JNI_FALSE即0,这里Android123详细说明下哪些情况可能返回真:(1这两个类参数引用同一个Java类(2第一个类是第二个类的子(3第二个类是第一个类的某个接口,关于JNI的一些相关操作,jclassGetObjectClass(JNIEnv*env,jobjectobj);通过对象获取这个类该函数比较简单,唯一注意的是对象不能为NULL,否则获取的class肯定返回也为NULL。jbooleanIsInstanceOf(JNIEnv*env,jobjectobj,jclassclazz);判断对象是否为某个类的实例这个函数是JNI层的实现,相信大家都不陌生,需要注意的是返回值可能产生异议,就是如果传入的第二个参数为NULL对象,NULL对象可以强制转换为各种类,所以这种情况也将会返回JNI_TRUE,所以一定判断传入的对象是否为空。jbooleanIsSameObject(JNIEnv*env,jobjectref1,jobjectref2);判断两个对象是否引用同一个类需要注意的是如果两个对象均为空,返回的值也会是JNI_TRUE所以使用时判断对象为空。,JNI调用Java方法,jmethodIDGetMethodID(JNIEnv*env,jclassclazz,constchar*name,constchar*sig);获取一个Java方法的ID这个函数将返回非静态类或接口实例方法的方法ID。这个方法可以是某个clazz的超类中定义,也可从clazz继承,最后一个参数为签名,最后两个参数是constchar*类型,是utf8类型。需要注意的是Android123提醒大家执行GetMethodID()函数将导致未初始化的类初始化,如果要获得构造函数的方法ID,使用作为方法名,同时将void(V)作为返回类型,如果找不到指定的ID将返回NULL,同时异常可能有:(1NoSuchMethodError找不到指定的Java方法。(2ExceptionInInitializerError如果由于异常而导致类初始化程序失败(3OutOfMemoryError内存不足,JNI调用Java方法,NativeTypeCallXXXMethod(JNIEnv*env,jobjectobj,jmethodIDmethodID,va_listargs);调
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 校园周边环境问题与学校环境文化建设研究论文
- 花椒烘干房管理制度
- 茶叶加工厂管理制度
- 防老人走失管理制度
- 六年级上学期期中质量检测
- 财务会计岗位实训心得
- 财务工作半年度总结(25篇)
- 解析汇编化学-专题18有机化学基础(选修)
- 自动化管道维修策略
- 计量专业考试之计量基础、法律法规知识考试题
- 宠物清洁卫生用品猫砂
- 大模型备案-落实算法安全主体责任基本情况-XX集团有限公司
- 【低空遥感】拓恒技术有限公司 -提供从无人机到场景应用垂直产业价值链的整体解决方案项目商业计划书
- 2025-2030中国蔬菜温室大棚市场消费趋势分析与经营管理风险报告
- 学校外来人员登记制度
- 店铺装修工程施工方案(3篇)
- 腰椎间盘突出症中医护理查房
- 多重耐药菌医院感染预防与控制技术指南(试行)
- 地面注浆施工方案
- 委托种植水果协议
- 深圳“20+8”之生物医药产业-前景机遇与技术趋势探析报告-前瞻产业研究院
评论
0/150
提交评论