Android基于JNI与NDK开发.docx
- 文档编号:26258467
- 上传时间:2023-06-17
- 格式:DOCX
- 页数:22
- 大小:216.98KB
Android基于JNI与NDK开发.docx
《Android基于JNI与NDK开发.docx》由会员分享,可在线阅读,更多相关《Android基于JNI与NDK开发.docx(22页珍藏版)》请在冰豆网上搜索。
Android基于JNI与NDK开发
Android之NDK开发
1、NDK产生的背景
Android平台从诞生起,就已经支持C、C++开发。
众所周知,Android的SDK基于Java实现,这意味着基于AndroidSDK进行开发的第三方应用都必须使用Java语言。
但这并不等同于“第三方应用只能使用Java”。
在AndroidSDK首次发布时,Google就宣称其虚拟机Dalvik支持JNI编程方式,也就是第三方应用完全可以通过JNI调用自己的C动态库,即在Android平台上,“Java+C”的编程方式是一直都可以实现的。
不过,Google也表示,使用原生SDK编程相比Dalvik虚拟机也有一些劣势,AndroidSDK文档里,找不到任何JNI方面的帮助。
即使第三方应用开发者使用JNI完成了自己的C动态链接库(so)开发,但是so如何和应用程序一起打包成apk并发布?
这里面也存在技术障碍。
比如程序更加复杂,兼容性难以保障,无法访问FrameworkAPI,Debug难度更大等。
开发者需要自行斟酌使用。
于是NDK就应运而生了。
NDK全称是NativeDevelopmentKit。
NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。
NDK将是Android平台支持C开发的开端。
二、使用NDK的情况
1.代码的保护。
由于apk的java层代码很容易被反编译,而C/C++库反编译难度较大。
2.可以方便地使用现存的开源库。
大部分现存的开源库都是用C/C++代码编写的。
3.提高程序的执行效率。
将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
4.便于移植。
用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
3.NDK简介
1.NDK是一系列工具的集合
NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。
这些工具对开发者的帮助是巨大的。
NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。
NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
2.NDK提供了一份稳定、功能有限的API头文件声明
Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。
从该版本的NDK中看出,这些API支持的功能非常有限,包含有:
C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog)。
4.开发环境的搭建
这里把整套环境都介绍下,方便android初级开发者
1.androidSDK下载安装
说明:
官网的sdk里包含eclipse,此款eclipse是专门针对android研发的,集合了ADT,不需要再下载安装,且不需要配置java的环境变量(当然还好配上的好)。
2.NDK的下载安装
由于NDK编译代码时必须要用到make和gcc,所以你必须先搭建一个linux环境,cygwin是一个在windows平台上运行的unix模拟环境,它对于学习unix/linux操作环境,或者从unix到windows的应用程序移植,非常有用。
通过它,你就可以在不安装linux的情况下使用NDK来编译C、C++代码了。
NDKr7.0以下的版本,都是要这样配置的,NDKr7.0以上的版本则只需要下载NDK即可
下面看看NDK在eclipse中的配置
Window--->Preference--->Anriod--->NDK,填上NDK安装目录
3.Eclipse中安装CDT插件
Eclipse中集成c/c++的开发环境,需要安装CDT插件。
这里介绍下在线安装
a.登录http:
//www.eclipse.org/cdt/downloads.php,找到对应你Eclipse版本的CDT插件的在线安装地址。
b.然后点Help菜单,找到InstallNewSoftware菜单
c.点击Add按钮,把取的地址填进去,出来插件列表后,选SelectAll,然后选择下一步即可完成安装。
d.安装完成后,在eclispe中右击新建一个项目,如果出现了c/c++项目,则表明你的CDT插件安装成功。
编译器的配置,会结合下面的实例来说。
5.NDK程序开发实例
1.新建一个Android的项目,命名为NDKTest
2.首先编写java代码,这里不建议把本地方法写在子类里面,因为子类在编译时会依赖父类,在执行javah进行生成头文件时可能会因找不到父类而失败
JNITool.java类
packagecom.example.jnitestcpp;
publicclassJNITool{
publicstaticnativeStringsayJni(Stringjnistr);
}
MainActivity.java类
packagecom.example.jnitestcpp;
importandroid.support.v7.app.ActionBarActivity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.TextView;
publicclassMainActivityextendsActionBarActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextViewtx=(TextView)findViewById(R.id.mytext);
tx.setText(JNITool.sayJni("hello"));
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifitispresent.
getMenuInflater().inflate(R.menu.main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
//Handleactionbaritemclickshere.Theactionbarwill
//automaticallyhandleclicksontheHome/Upbutton,solong
//asyouspecifyaparentactivityinAndroidManifest.xml.
intid=item.getItemId();
if(id==R.id.action_settings){
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
static{
System.loadLibrary("nativecpp");
}
}
3.生成头文件
执行javahcom.example.jnitestcpp.JNITool
注意:
java文件,首先是要编译成class文件的,eclipse下开发的话,会自动编译成class文件,在bin目录下(在eclipse工程里是不显示的,需要到文件下才可以看到哦),命令必须在com上层的目录下执行,且一定要是全限定性的类名哦.
com_example_jnitestcpp_JNITool.h
4.项目下新建jni文件
将生成的头文件copy至该文件夹下,下面就是根据头文件来编写c/c++代码
com_example_jnitestcpp_JNITool.cpp
#include"com_example_jnitestcpp_JNITool.h"
#include
extern"C"{
/*
*Class:
com_example_jnitestcpp_JNITool
*Method:
sayJni
*Signature:
(Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORTjstringJNICALLJava_com_example_jnitestcpp_JNITool_sayJni
(JNIEnv*env,jclasscalss,jstringjnistr){
returnjnistr;
}
}
注意:
c++中供外部调用的方法一定要写在extern“C”{}内。
5.配置c/c++编译器
●项目处右键,属性--->Builder--->New
●Location:
NDK的安装,如
D:
\android-ndk-r9d\ndk-build.cmd
WorkingDirectory:
工程目录,如
${workspace_loc:
/NDKTest}
●File:
${workspace_loc:
/NDKTest/jni},勾选项如下图
SpecifyResources--->勾选当前项目的jni文件
6.Android.mk
这里直接给出实例,附录里会有更详细的用法
LOCAL_PATH:
=$(callmy-dir)
include$(CLEAR_VARS)
LOCAL_MODULE:
=nativecpp//生成库的名称
LOCAL_SRC_FILES:
=com_example_jnitestcpp_JNITool.cpp//资源文件
include$(BUILD_SHARED_LIBRARY)
完成上述步骤后,自动编译,会自动生成libs和obj两个文件
下面就是启动你的模拟器/设备,项目右键--->RunAs--->AndroidApplication
注:
直接在本项目里开发c/c++代码的话,可以
项目右键--->AndroidTools--->AddNativeSupports
填上动态库的名称
附:
android.mk简介
Android.mk文件是GNUMakefile的一小部分,它用来对Android程序进行编译。
因为所有的编译文件都在同一个GNUMAKE执行环境中进行执行,而Android.mk中所有的变量都是全局的。
因此,您应尽量少声明变量,不要认为某些变量在解析过程中不会被定义。
一个Android.mk文件可以编译多个模块,每个模块属下列类型之一:
1)APK程序
一般的Android程序,编译打包生成apk文件
2)JAVA库
java类库,编译打包生成jar文件
3)C\C++应用程序
可执行的C\C++应用程序
4)C\C++静态库
编译生成C\C++静态库,并打包成.a文件
5)C\C++共享库
编译生成共享库(动态链接库),并打包成.so文,有且只有共享库才能被安装/复制到您的应用软件(APK)包中。
可以在每一个Android.mk文件中定义一个或多个模块,你也可以在几个模块中使用同一个源代码文件。
编译系统为你处理许多细节问题。
例如,你不需要在你的Android.mk中列出头文件和依赖文件。
编译系统将会为你自动处理这些问题。
这也意味着,在升级NDK后,你应该得到新的toolchain/platform支持,而且不需要改变你的Android.mk文件。
注意,NDK的Anroid.mk语法同公开发布的Android平台开源代码的Anroid.mk语法很接近,然而编译系统实现他们的方式却是不同的,这是故意这样设计的,可以让程序开发人员重用外部库的源代码更容易。
在描述语法细节之前,咱们来看一个简单的"helloworld"的例子,比如,下面的文件:
sources/helloworld/helloworld.c
sources/helloworld/Android.mk
'helloworld.c'是一个JNI共享库,实现返回"helloworld"字符串的原生方法。
相应的
Android.mk文件会象下面这样:
LOCAL_PATH:
=$(callmy-dir)
include$(CLEAR_VARS)
LOCAL_MODULE:
=helloworld
LOCAL_SRC_FILES:
=helloworld.c
include$(BUILD_SHARED_LIBRARY)
解释一下几行代码:
LOCAL_PATH:
=$(callmy-dir)
一个Android.mk文件首先必须定义好LOCAL_PATH变量。
它表示是当前文件的路径。
在这个例子中,宏函数‘my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk文件的目录)。
include$(CLEAR_VARS)
CLEAR_VARS由编译系统提供(可以在android安装目录下的/build/core/config.mk文件看到其定义,为CLEAR_VARS:
=$(BUILD_SYSTEM)/clear_vars.mk),指定让GNUMAKEFILE该脚本为你清除许多LOCAL_XXX变量(例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES,等等…),除LOCAL_PATH。
这是必要的,因为所有的编译文件都在同一个GNUMAKE执行环境中,所有的变量都是全局的。
所以我们需要先清空这些变量(LOCAL_PATH除外)。
又因为LOCAL_PATH总是要求在每个模块中都要进行设置,所以并需要清空它。
另外注意,该语句的意思就是把CLEAR_VARS变量所指向的脚本文件包含进来。
LOCAL_MODULE:
=helloworld
LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。
名称必须是唯一的,而且不包含任何空格。
注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'foo'的共享库模块,将会生成'libfoo.so'文件。
注意:
如果把库命名为‘libhelloworld’,编译系统将不会添加任何的lib前缀,也会生成libhelloworld.so。
LOCAL_SRC_FILES:
=helloworld.c
LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。
不用在这里列出头文件和包含文件,编译系统将会自动找出依赖型的文件,当然对于包含文件,你包含时指定的路径应该正确。
注意,默认的C++源码文件的扩展名是‘.cpp’。
指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是定义为 ‘.cxx’,而不是‘cxx’)
include$(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY是编译系统提供的变量,指向一个GNUMakefile脚本(应该
就是在build/core 目录下的shared_library.mk),将根据LOCAL_XXX系列变量中的值,来编译生成共享库(动态链接库)。
如果想生成静态库,则用BUILD_STATIC_LIBRARY
在NDK的sources/samples目录下有更复杂一点的例子,写有注释的 Android.mk文件。
二、自定义变量
以下是在Android.mk中依赖或定义的变量列表,可以定义其他变量为自己使用,但是NDK编译系统保留下列变量名:
-以LOCAL_开头的名字(例如LOCAL_MODULE)
-以PRIVATE_,NDK_或APP_开头的名字(内部使用)
-小写名字(内部使用,例如‘my-dir’)
如果为了方便在Android.mk中定义自己的变量,建议使用MY_前缀,一个小例子:
MY_SOURCES:
=foo.c
ifneq($(MY_CONFIG_BAR),)
MY_SOURCES+=bar.c
endif
LOCAL_SRC_FILES+=$(MY_SOURCES)
注意:
‘:
=’是赋值的意思;'+='是追加的意思;‘$’表示引用某变量的值。
三、GNUMake系统变量
这些GNUMake变量在你的Android.mk文件解析之前,就由编译系统定义好了。
注意在
某些情况下,NDK可能分析Android.mk几次,每一次某些变量的定义会有不同。
(1)CLEAR_VARS:
指向一个编译脚本,几乎所有未定义的LOCAL_XXX变量都在"Module-description"节中列出。
必须在开始一个新模块之前包含这个脚本:
include$(CLEAR_VARS),用于重置除LOCAL_PATH变量外的,所有LOCAL_XXX系列变量。
(2)BUILD_SHARED_LIBRARY:
指向编译脚本,根据所有的在LOCAL_XXX变量把列出的源代码文件编译成一个共享库。
注意,必须至少在包含这个文件之前定义LOCAL_MODULE和LOCAL_SRC_FILES。
(3)BUILD_STATIC_LIBRARY:
一个BUILD_SHARED_LIBRARY变量用于编译一个静态库。
静态库不会复制到的APK包中,但是能够用于编译共享库。
示例:
include$(BUILD_STATIC_LIBRARY)
注意,这将会生成一个名为lib$(LOCAL_MODULE).a的文件
(4)TARGET_ARCH:
目标CPU平台的名字, 和android开放源码中指定的那样。
如果是
arm,表示要生成ARM兼容的指令,与CPU架构的修订版无关。
(5)TARGET_PLATFORM:
Android.mk解析的时候,目标Android平台的名字.详情可参考/development/ndk/docs/stable-apis.txt.
android-3->OfficialAndroid1.5systemimages
android-4->OfficialAndroid1.6systemimages
android-5->OfficialAndroid2.0systemimages
(6)TARGET_ARCH_ABI:
暂时只支持两个value,armeabi和armeabi-v7a。
在现在的版本中一般把这两个值简单的定义为arm,通过android 平台内部对它重定义来获得更好的匹配。
其他的ABI将在以后的NDK版本中介绍,它们会有不同的名字。
注意虽然所有基于
ARM的ABI都会把'TARGET_ARCH'定义成‘arm’,但是会有不同的‘TARGET_ARCH_ABI’。
(7)TARGET_ABI:
目标平台和ABI的组合,它事实上被定义成$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI) ,在想要在真实的设备中针对一个特别的目标系统进行测试时,会有用。
在默认的情况下,它会是'android-3-arm'。
五、模块描述变量
下面的变量用于向编译系统描述你的模块。
你应该定义在'include $(CLEAR_VARS)'和'include$(BUILD_XXXXX)'之间。
正如前面描写的那样,$(CLEAR_VARS)是一个脚本,清除所有这些变量。
(1)LOCAL_PATH:
这个变量用于给出当前文件的路径。
必须在Android.mk的开头定义,可以这样使用:
LOCAL_PATH:
=$(callmy-dir) 这个变量不会被$(CLEAR_VARS)清除,因此每个Android.mk只需要定义一次(即使在一个文件中定义了几个模块的情况下)。
(2)LOCAL_MODULE:
这是模块的名字,它必须是唯一的,而且不能包含空格。
必须在包含任一的$(BUILD_XXXX)脚本之前定义它。
模块的名字决定了生成文件的名字。
例如,如果一个一个共享库模块的名字是,那么生成文件的名字就是lib.so。
但是,在的NDK生成文
件中(或者Android.mk或者Application.mk),应该只涉及(引用)有正常名字的其他模块。
(3)LOCAL_SRC_FILES:
这是要编译的源代码文件列表。
只要列出要传递给编译器的文件,因为编译系统自动计算依赖。
注意源代码文件名称都是相对于LOCAL_PATH的,你可以使用路径部分,例如:
LOCAL_SRC_FILES:
=foo.ctoto/bar.c\
Hello.c
文件之间可以用空格或Tab键进行分割,换行请用"\".如果是追加源代码文件的话,请用LOCAL_SRC_FILES+=
注意:
在生成文件中都要使用UNIX风格的斜杠(/).windows风格的反斜杠不会被正确的处理。
注意:
可以LOCAL_SRC_FILES:
=$(callall-subdir-java-files)这种形式来包含local_path目录下的所有java文件。
(4)LOCAL_CPP_EXTENSION:
这是一个可选变量,用来指定C++代码文件的扩展名,默认是'.cpp',但是可以改变它,比如:
LOCAL_CPP_EXTENSION:
=.cxx
(5)LOCAL_C_INCLUDES:
可选变量,表示头文件的搜索路径。
默认的头文件的搜索路径是LOCAL_PATH目录。
示例:
LOCAL_C_INCLUDES:
=sources/foo或LOCAL_C_INCLUDES:
=$(LOCAL_PATH)/../foo
LOCAL_C_INCLUDES需要在任何包含LOCAL_CFLAGS/LOCAL_CPPFLAGS标志之前进行设置。
(6)LOCAL_CFLAGS:
可选的编译器选项,在编译C代码文件的时候使用。
这可能是有
用的,指定一个附加的包含路径(相对于NDK的顶层目录),宏定义,或者编译选项。
注意:
不要在Android.mk中改变optimization/debugging级别,只要在Applica
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android 基于 JNI NDK 开发