Android之NDK初探

一、NDK产生的背景

  Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三方应用都必须使用Java语言。但这并不等同于“第三方应用只能使用Java”。在Android SDK首次发布时,Google就宣称其虚拟机Dalvik支持JNI编程方式,也就是第三方应用完全可以通过JNI调用自己的C动态库,即在Android平台上,“Java+C”的编程方式是一直都可以实现的。

  不过,Google也表示,使用原生SDK编程相比Dalvik虚拟机也有一些劣势,Android SDK文档里,找不到任何JNI方面的帮助。即使第三方应用开发者使用JNI完成了自己的C动态链接库(so)开发,但是so如何和应用程序一起打包成apk并发布?这里面也存在技术障碍。比如程序更加复杂,兼容性难以保障,无法访问Framework API,Debug难度更大等。开发者需要自行斟酌使用。

  于是NDK就应运而生了。NDK全称是Native Development Kit。

  NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。NDK将是Android平台支持C开发的开端。

二、为什么使用NDK

  1.代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。

  2.可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。

  3.提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。

  4.便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

三、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)。

四、NDK开发环境的搭建

1.下载安装Android NDK

  地址:http://developer.android.com/sdk/ndk/index.html

2.下载安装cygwin

  由于NDK编译代码时必须要用到make和gcc,所以你必须先搭建一个linux环境, cygwin是一个在windows平台上运行的unix模拟环境,它对于学习unix/linux操作环境,或者从unix到windows的应用程序移植,非常有用。通过它,你就可以在不安装linux的情况下使用NDK来编译C、C++代码了。下载地址:http://www.cygwin.com

  1)然后双击运行吧,运行后你将看到安装向导界面。

  2)点击下一步,此时让你选择安装方式:

Install from Internet:直接从Internet上下载并立即安装(安装完成后,下载好的安装文件并不会被删除,而是仍然被保留,以便下次再安装)。
Download Without Installing:只是将安装文件下载到本地,但暂时不安装。
Install from Local Directory:不下载安装文件,直接从本地某个含有安装文件的目录进行安装。

  3)选择第一项,然后点击下一步。

  4)选择要安装的目录,注意,最好不要放到有中文和空格的目录里,似乎会造成安装出问题,其它选项不用变,之后点下一步:

  5)上一步是选择安装cygwin的目录,这个是选择你下载的安装包所在的目录,默认是你运行setup.exe的目录,直接点下一步就可以:

  6)此时你共有三种连接方式选择:

Direct Connection:直接连接。
Use IE5 Settings:使用IE的连接参数设置进行连接。
Use HTTP/FTP Proxy:使用HTTP或FTP代理服务器进行连接(需要输入服务器地址、端口号)。

  用户可根据自己的网络连接的实情情况进行选择,一般正常情况下,均选择第一种,也就是直接连接方式。然后再点击“下一步”。

  7)这是选择要下载的站点,选择后点下一步。

  8)此时会下载加载安装包列表

  9)Search是可以输入你要下载的包的名称,能够快速筛选出你要下载的包。那四个单选按钮是选择下边树的样式,默认就行,不用动。View默认是Category,建议改成full显示全部包再查,省的一些包被隐藏掉。左下角那个复选框是是否隐藏过期包,默认打钩,不用管它就行,下边开始下载我们要安装的包吧,为了避免全部下载,这里列出了后面开发NDK用得着的包:autoconf2.1、automake1.10、binutils、gcc-core、gcc- g++、gcc4-core、gcc4-g++、gdb、pcre、pcre-devel、gawk、make共12个包

  10)然后开始选择安装这些包吧,点skip,把它变成数字版本格式,要确保Bin项变成叉号,而Src项是源码,这个就没必要选了。

  11)下面测试一下cygwin是不是已经安装好了。

  运行cygwin,在弹出的命令行窗口输入:cygcheck -c cygwin命令,会打印出当前cygwin的版本和运行状 态,如果status是ok的话,则cygwin运行正常。

  然后依次输入gcc –version,g++ --version,make –version,gdb –version进行测试,如果都打印出版本信息和一些描述信息,则cygwin安装成功!

3.配置 NDK 环境变量

  a.首先找到 cygwin 的安装目录,找到一个 home\< 你的用户名 >.bash_profile 文件,我的是:E:\cygwin\home\Administrator.bash_profile , ( 注意:我安装的时候我的 home 文件夹下面什么都没有,解决 的办法:首先打开环境变量,把里面的用户变量中的 HOME 变量删掉,在 E:\cygwin\home 文件夹下建立名为Administrator 的文件夹(是用户名),然后把 E:\cygwin\etc\skel.bash_profile 拷贝到该文件夹下 ) 。

  b.打开 bash_profile 文件,添加 NDK=/cygdrive/< 你的盘符 >/

include

ifndef _Included_com_example_hellojni_HelloJni

define _Included_com_example_hellojni_HelloJni

ifdef __cplusplus

extern “C” {

endif

/*

  • Class: com_example_hellojni_HelloJni

  • Method: stringFromJNI

  • Signature: ()Ljava/lang/String;

    */

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI

(JNIEnv *, jobject);

/*

  • Class: com_example_hellojni_HelloJni

  • Method: unimplementedStringFromJNI

  • Signature: ()Ljava/lang/String;

    */

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI

(JNIEnv *, jobject);

ifdef __cplusplus

}

endif

endif

上面代码中的JNIEXPORT 和 JNICALL 是jni的宏,在android的jni中不需要,当然写上去也不会有错。从上面的源码中可以看出这个函数名那是相当的长啊。。。。 不过还是很有规律的, 完全按照:java_pacakege_class_mathod 形式来命名。

  也就是说:

  Hello.java中 stringFromJNI() 方法对应于 C/C++中的 Java_com_example_hellojni_HelloJni_stringFromJNI() 方法

  HelloJni.java中的 unimplementedStringFromJNI() 方法对应于 C/C++中的 Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI() 方法

  注意下其中的注释:

  Signature: ()Ljava/lang/String;

  ()Ljava/lang/String;()表示函数的参数为空(这里为空是指除了JNIEnv , jobject 这两个参数之外没有其他参数,JNIEnv, jobject是所有jni函数必有的两个参数,分别表示jni环境和对应的java类(或对象)本身),Ljava/lang/String; 表示函数的返回值是java的String对象。

b. 编写相应的.c文件:

  hello-jni.c :
 #include

include

Licensed under the Apache License, Version 2.0 (the “License”);

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

#

http://www.apache.org/licenses/LICENSE-2.0

#

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an “AS IS” BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := hello-jni

LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

 这个Androd.mk文件很短,下面我们来逐行解释下:

    LOCAL_PATH := $(call my-dir)

  一个Android.mk 文件首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。

    include $( CLEAR_VARS)

  CLEAR_VARS由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等…), 除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。

    LOCAL_MODULE := hello-jni

  编译的目标对象,LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。

注意:编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为’hello-jni’的共享库模块,将会生成’libhello-jni.so’文件。

  重要注意事项:如果你把库命名为‘libhello-jni’,编译系统将不会添加任何的lib前缀,也会生成 ‘libhello-jni.so’,这是为了支持来源于Android平台的源代码的Android.mk文件,如果你确实需要这么做的话。

    LOCAL_SRC_FILES := hello-jni.c

  LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。

  注意,默认的C++源码文件的扩展名是’.cpp’. 指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是’.cxx’,而不是’cxx’)

    include $(BUILD_SHARED_LIBRARY)

  BUILD_SHARED_LIBRARY表示编译生成共享库,是编译系统提供的变量,指向一个GNU Makefile脚本,负责收集自从上次调用’include (CLEARVARS)LOCALXXXBUILDSTATICLIBRARYlib (LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可执行文件。

b. 生成.so共享库文件

  Andro文件已经编写好了,现在可以用android NDK开发包中的 ndk-build脚本生成对应的.so共享库了,方法如下:

    braincol@ubuntu:~/workspace/android/NDK/hello-jni/jni cd..    braincol@ubuntu: /workspace/android/NDK/hellojni ls
    AndroidManifest.xml assets bin default.properties gen jni libs obj res src
    braincol@ubuntu:~/workspace/android/NDK/hello-jni$ ndk-build
    Gdbserver : [arm-linux-androideabi-4.4.3] libs/armeabi/gdbserver
    Gdbsetup : libs/armeabi/gdb.setup
    Install : libhello-jni.so => libs/armeabi/libhello-jni.so

  可以看到已经正确的生成了libhello-jni.so共享库了, 我们去 libs/armeabi/ 目录下看看:

    braincol@ubuntu:~/workspace/android/NDK/hello-jni cdlibs/    braincol@ubuntu: /workspace/android/NDK/hellojni/libs ls
    armeabi
    braincol@ubuntu:~/workspace/android/NDK/hello-jni/libs cdarmeabi/    braincol@ubuntu: /workspace/android/NDK/hellojni/libs/armeabi ls
    gdbserver gdb.setup libhello-jni.so

4)在eclipse重新编译HelloJni工程,生成apk

  eclipse中刷新下HelloJni工程,重新编译生成apk,libhello-jni.so共享库会一起打包在apk文件内。在模拟器中看看运行结果。
参考资料:

http://blog.csdn.net/hhao137/article/details/4304664

http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html

http://yueguc.iteye.com/blog/946724

http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html

猜你喜欢

转载自blog.csdn.net/u014120638/article/details/46791259