android ndk 开发教程 - v1.0


前言前言 NDK是一系列工具的集合,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成ap k。这些工具对开发者的帮助是巨大的。 为什么使用NDK为什么使用NDK • 代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。 • 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。 • 提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。 • 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。 致谢 教程源于:http://www.imobilebbs.com/wordpress/%E6%95%99%E7%A8%8B/android%E5%BC%8 0%E5%8F%91%E6%95%99%E7%A8%8B 版本信息版本信息 书中演示代码基于以下版本: 工具工具 版本信息版本信息 Android NDK android 1.5 以上 目录目录 前言前言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 第 1 章第 1 章 安装 NDK安装 NDK . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 第 2 章第 2 章 概述概述 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 第 3 章第 3 章 Hello JNI 示例Hello JNI 示例 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1414 第 4 章第 4 章 TwoLibs 示例TwoLibs 示例. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2020 第 5 章第 5 章 Android.mk 文件Android.mk 文件 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2424 第 6 章第 6 章 application.mkapplication.mk. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3737 第 7 章第 7 章 调试调试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4141 第 8 章第 8 章 Box2D 的 Android NDK 实现Box2D 的 Android NDK 实现 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4646 11 安装 NDK安装 NDK Android OS 的基本框架为 Linux-Java ,在介绍 Android 开发时用到的 Android 结构图: android 体系底层为 Linux 内核,之上提供一些 C/C++ 函数库,因此 Android 应用开发也可以使用 C /C++ 开 发,这就是 Android NDK 开发包,但 Android 提供 NDK 开发包的主要目的并不是推荐开发人员使用 C(Nati ve 代码)来编写一般的 Android 应用,而是要使用 Java 代码来编写 Android 应用来更好的处理 Android 应用 生命周期(Life-cycle)相关的事件以避免出现“应用程序不响应(ANR)”的对话框。 使用 NDK 主要是通过 JNI 使用从 Java 代码调用 C 代码,也就是使用 Native 编程主要是为上层 Java 代码提 供库函数(动态库或是静态库的形式)而不是全部使用 Native C 代码编写整个 Android 应用(尽管借助于少量 Java 代码也是可以大部分使用 C 代码来实现的)。 使用 NDK 大部分情况是需要将一些已有的 C 函数库移植到 An droid 平台的所选择的快捷方法,而不是作为提高代码效率的手段 安装 Android NDK 的方法非常简单:打开网页 http://developer.android.com/sdk/ndk/index.html 选择合适的 NDK 开发包,下载解压即可。注:安装 NDK 之前需先安装 SDK 开发包,参见 Android 简明开发 教程二:安装开发环境。 Android NDK 的前两级目录如下: ├── build │ ├── awk │ ├── core │ ├── gmsl │ └── tools ├── docs │ ├── ANDROID-ATOMICS.html │ ├── ANDROID-MK.html │ ├── APPLICATION-MK.html │ ├── CHANGES.html │ ├── CPLUSPLUS-SUPPORT.html │ ├── CPU-ARCH-ABIS.html │ ├── CPU-ARM-NEON.html │ ├── CPU-FEATURES.html │ ├── CPU-X86.html │ ├── DEVELOPMENT.html │ ├── HOWTO.html │ ├── IMPORT-MODULE.html │ ├── INSTALL.html │ ├── LICENSES.html │ ├── NATIVE-ACTIVITY.HTML │ ├── NDK-BUILD.html │ ├── NDK-GDB.html │ ├── NDK-STACK.html │ ├── openmaxal 第 1 章 安装 NDK | 4 │ ├── opensles │ ├── OVERVIEW.html │ ├── PREBUILTS.html │ ├── sidenav.html │ ├── STABLE-APIS.html │ ├── STANDALONE-TOOLCHAIN.html │ ├── system │ └── SYSTEM-ISSUES.html ├── documentation.html ├── GNUmakefile ├── ndk-build ├── ndk-build.cmd ├── ndk-gdb ├── ndk-stack ├── ndk.txt ├── platforms │ ├── android-14 │ ├── android-3 │ ├── android-4 │ ├── android-5 │ ├── android-8 │ └── android-9 ├── prebuilt │ └── linux-x86 ├── README.TXT ├── RELEASE.TXT ├── samples │ ├── bitmap-plasma │ ├── hello-gl2 │ ├── hello-jni │ ├── hello-neon │ ├── module-exports │ ├── native-activity │ ├── native-audio │ ├── native-media │ ├── native-plasma │ ├── san-angeles │ ├── test-libstdc++ │ └── two-libs ├── sources │ ├── android │ ├── cpufeatures │ └── cxx-stl ├── tests │ ├── awk 第 1 章 安装 NDK | 5 │ ├── build │ ├── device │ ├── README │ ├── run-standalone-tests.sh │ ├── run-tests.sh │ └── standalone └── toolchains ├── arm-linux-androideabi-4.4.3 └── x86-4.4.3 在开发 NDK 之前,建议先看一下 doc 子目录下的文档,后面的博客也会有所介绍。 第 1 章 安装 NDK | 6 22 概述概述 注意:在 Windows 上运行 NDK 需要有 Cygwin 支持,个人建议使用 Ubuntu 为好 。 介绍:介绍: Android SDK 是一个允许 Android 应用开发人员使用 C 或 C++源文件编译并嵌入到本机源代码中的应用程序 包的一组工 具。 重要说明:重要说明: Android NDK 只能用于 android 1.5 以上版本 1. Android NDK 的目的:1. Android NDK 的目的: Android 虚拟机允许你的应用程序源代码通过 JNI 调用在本地实现的源代码,简单的说,这就意味着: —-你的应用程序将声明一个或多个用’native’关键字的方法用来指明它们是通过本地代码实现的 例如: native byte[] loadFile(String filePath) —-你必须提供包含实现这些方法的共享库(就是.so),将共享库打包到你的应用程序包 apk 中,这些库文件必 须根据 标准的 Unix 约定来命名为 lib.so,并且是需要包含一个标准的 JNI 的接口,例如 libFileLoader.so —-你的应用程序必须明确的装载这些库文件(.so文件),比如,在程序的开始装载它,只需要简单的添加几句源 代码: static { System.loadLibrary(“FileLoader”); } 注意:这里你不必再将前缀 lib 和后缀.so 写入。 Android NDK 对于 Android SDK 只是个组件,它可以帮你: —-生成的 JNI 兼容的共享库可以在大于 Android1.5 平台的 ARM CPU 上运行 —-将生成的共享库拷贝到合适的程序工程路径的位置上,以保证它们自动的添加到你的 apk 包中(并且签名 的) —-在以后的版本中,我们将提供来帮助你的源代码通过远程 gdb 连接和尽可能多的源代码的信息。 而且,Android NDK 还提供: 第 2 章 概述 | 8 —-一组交叉编译链(编译器、链接器等)来生成可以在 Linux,OS X 和 Windows (用 Cygwin )运行的二进制 文件 —-一组与由 Android 平台提供的稳定的本地 API 列表的头文件 它们在 docs/STABLE-APIS.html 中有说明 重要提示: 记住,在以后的更新和发布平台中,Android 系统镜像中的大多数本地系统库并不是一成不变的,而是可以彻底 改变, 甚至删除的 —-一个编译系统(build system)可以允许开发者写一个非常短的编译文件(build files)去描述哪个源代码需 要编译,并且怎样编译。编译系统可以解决所有的 toolchain/platform/CPU/ABI 细节的问题。并且,较晚的 N DK 版 本中还添加了更多的可以不用改变开发者的编译文件的情况下的 toolchains,platforms,系统接口 2. Android NDK 的缺点2. Android NDK 的缺点 NDK 并不是一个可以编写通用的源代码并且可以在 Android 设备上运行的方法,你的应用程序还是需要使用 JA VA 程序,适当的处理系统事件来避免“应用程序没有反应”的对话框或者处理 Android 应用程序的生命周期 注意:可以适当的在源代码中写一个复杂的应用程序,用于启动/停止一个小型的“应用程序包” 强烈建议很好地理解的 JNI,因为许多操作在这种环境要求的开发人员,都采取具体的行动,不一定在常见典型 的本机代码。这些措施包括: —-不能通过指针直接访问 VM 的对象。比如:你不能安全的得到一个指向 String 对象的 16 位 char 数组的循 环遍历 —-需要显示引用管理本机代码时候要保持处理 JNI 调用之间的 VM 对象 NDK 在 Android 平台仅仅提供了有限的本地 API 和库文件的支持的系统头文件,然而一个标准的 Android 系统 镜像包括许多本地共享库,这些都应该被考虑在更新和发行版本的可以彻底改变的实现细节 如果 Android 系统库没有明确的被 NDK 明确的支持,然后应用程序不应该依赖于它提供的,或者打破了将来在 各种设备上的无线系统更新 选定的系统库将逐渐被添加到稳定的 NDK API 中 3. NDK开发实践3. NDK开发实践 下面将给出一个怎样用 Android NDK 开发本地代码的粗略的概述 (1) 把本地代码放在 $PROJECT/jni/…下,比如将 hello.c 放到 apps/hello/jni/目录下 第 2 章 概述 | 9 (2) 在你的 NDK 编译系统中在 $PROJECT/jni/Android.mk 来描述你的源代码 (3) 可选:在 $PROJECT/jni/Application.mk 到你的编译系统中来详细描述你的项目,尽管你开始的话不一 定需要它, 但是它允许你使用更多的 CPU 或者覆盖编译器/链接器的标记(看 docs/APPLICATION-MK.html 了解更多细节) (4) 从你的项目的目录开始通过运行”$NDK/ndk-build”来编译你的代码,或者从子目录开始 (5) 最后一步可以 copy,万一成功,剥离共享库的应用层序需要你的应用程序的项目根目录。然后你通过通常 的方法来 生成最终的 apk 现在,开始一些更 的细节 ① 配置 NDK 以前的发行版本需要你运行“build/host-setup.sh”脚本来配置你的 NDK。从 release 4(NDK r)以后就完全 去除了这一步 ② 放置 C/C++ 代码 假如我们创建的是 test 目录,创建的代码 hello.c 把 hello.c 放到 test/jni 目录下 这个项目的位置相当于你的 Android 应用程序项目的路径 这样你就很轻松的组织起来了你想要的 jni 的目录,这里项目目录的名字和结构不会影响到最终生成的 apk,所以 你不必用类似于 com.. 作为应用程序包名 注意,NDK 是支持 C 和 C++ 的,NDK 支持的 C++ 文件扩展名是’.cpp’,但是其他的扩展名也是可以被处 理的 (看 docs/ANDROID-MK.html 了解更多) 它可以通过调整你的 Android.mk 文件来将源代码放在不同的位置 ③ 创建一个 Android.mk 编译脚本 Android.mk 文件是一个小型的编译脚本,你可以在 NDK 编译系统中用它来描述你的源代码。更详细的描述在 d ocs/ANDROID-MK.html 中 总而言之,NDK 将你的源代码聚合到模块(modules)中,每个模块可以执行下列之一 —-一个静态库(lib.a) —-一个动态库(lib.so) 第 2 章 概述 | 10 你可以在 Android.mk 中定义多个模块,或者你可以编写多个 Android.mk 文件,每一个定义一个单独的模块 注意,单独的 Android.mk 也行被编译系统多次解析,以确定哪些变量没有被定义。 默认地,NDK 会通过如下的编译脚本去寻找 test/jni/Android.mk(存放位置) 如果你想定义 Android.mk 到子目录中,你需要在最高层的 Android.mk 中明确的包含它们,下面是一个帮助的 方法可以实现这个功能。 include $(call all-subdir-makefiles) 它会将所有的在子目录中的 Android.mk 文件加入到当前编译文件的路径中 ④ 写一个 Application.mk 编译文件(可选) 在你的编译系统中有一个 Android.mk 文件描述模块的同时,Application.mk 文件藐视你的应用程序本身。请看 docs/APPLICATION-MK.html 文档来理解这个文件允许我们做什么。这包括 —-你的应用程序需要模块的准确清单 —-CPU 架构生成机器代码 —-可选信息,你是否需要一个 release 或者 debug build,特殊的 C/C++ 编译器标志和其他适用于所有模块的 build 这个文件是可选文件:默认地,NDK 会提供一个对于所有的在你的 Android.mk(所有的 makefiles 都在里 面)中的所有模块的简单编译并且指定默认的 CPU ABI 使用 Application.mk 有两种方法: —-把它放到 test/jni/Application.mk,它就会自动的被’ndk-build’脚本找出来 —-把它放在 NDK//Application.mk,也就是 NDK 安装的路径下,然后从 NDK 目录下执行”make APP=” 这个方法是 Android NDK r4 以前的。现在仍然兼容。但是我们强烈建议你使用第一种方法,因为它更简单并且 不用修改 NDK 安装树的目录。 再次看看 docs/APPLICATION-MK.html 对于它的完整说明 ⑤ 调用 NDK 编译系统 用 NDK 编译成机器码的最好方法是使用”ndk-build”脚本,你还可以使用第二个,这取决于你早起常见 的”$NDK/apps”子目录 第 2 章 概述 | 11 在两种情况下,成功构建将 copy 应用程序所需的最终的已经剥离的二进制模块(即共享库)到应用程序的项目 路径中(注意,未剥离的版本主要是用于调试目的,无需拷贝未剥离的二进制文件到设备中) [1]:使用’ndk-build’命令 ‘ndk-build’脚本位于NDK安装目录最顶层,可以直接被应用程序项目目录(你的AndroidManifest.xml文件 所在位置)或者其他任何子目录 $ cd $PROJECT $ $NDK/ndk-build(注意是 $NDK/ndkbuild,这是个命令) 将启动 NDK 的 build 脚本,它会自动探测您开发的 系统和应用程序项目文件,以确定 build 设么 例如: $ndk-build $ndk-build clean à 清理生成的二进制文件 $ndk-build –B V=1 à 强制完全重新 build,显示命令 默认的,它期望的是可选文件 $PROJECT/jni/Application.mk 和必须的文件 $PROJECT/jni/Android.mk 成功的话,它讲话就复制生成的二进制模块(即共享库.so文件)到你的项目树中的适当位置。您可以在以后重新 build 完整的 Android 应用程序包或者通过“ant”命令,或者 ADT 插件。 可以看 docs/NDK-BUILD.html 来了解更多的信息 [2]:使用 $NDK/apps//Application.mk 这种 build 方法是在 Android NDK r4 版本之前的,不过依然兼容现在的。我们强烈建议您尽可能的使用’ndk- build’,因为我们可能会删除在以后的 NDK 发行版本中的支持 ① 创建一个子目录为 $NDK/apps// ② 在 $NDK/apps//目录下写一个 Application.mk 文件,然后需要定义一个 APP_PROJECT_PATH 来执行 你的应用程序项目的目录。 ③ 进入到 NDK 安装目录,然后再输入如下的命令 $cd $NDK 注意:输入 cd $NDK 后,会自动跳到你设置的 ndk 的目录中 第 2 章 概述 | 12 $make APP= 或 $make APP= -B 表示重新编译 结果跟第一种方法一样,除了中间文件被放置到了 $NDK/out/apps// 4. 从新 build 你的应用程序包4. 从新 build 你的应用程序包 在 NDK 生成的二进制文件后,你需要使用一般的方法来重新 build 你的 Android 应用程序包文件(apk),或者 用“ant”命令或者 ADT 插件 有关详细信息,请参阅 Android SDK 的文档,新的.apk 会嵌入到您的共享库中,他们将自动提取安装时由系统 安装的软件包到你的 Android 设备上 5. 调试支持5. 调试支持 NDK 提供了一个服务脚本,名字叫”ndk-gdb”,很容易推出一个应用程序的本地调试会话。 本机调试仅仅能运行在 Android 2.2 或者更高版本,并且不需要 root 权限或者特权访问,所以可以随意调试你的 应用程序。 有关详细信息,请阅读 DOCS / NDK- GDB.html。总括而言,本机调试 遵循这个简单的计划: (1)确保您的应用程序调试(如设置机器人:调试“真”,在您的 AndroidManifest.xml) (2) “NDK 构建”构建您的应用程序,然后安装在您的 设备/模拟器 (3)启动应用程序。 (4)运行“ndk-gdb”从你的应用程序项目目录。 你会得到一个 gdb 提示符。一个有用的列表,请参阅 GDB 用户手册命令。 第 2 章 概述 | 13 33 Hello JNI 示例Hello JNI 示例 Android NDK 開發包帶有不少例子,一個簡單的例子 Hello-Jni ,介紹了如何使用 Java 調用 C 函數。 1. 可以使用 Eclipse 的 import 將該項目添加到工作目錄中. 該項目目錄結構如下: ├── AndroidManifest.xml ├── default.properties ├── hellojni.txt ├── jni │ ├── Android.mk │ └── hello-jni.c ├── res │ └── values │ └── strings.xml ├── src │ └── com │ └── example │ └── hellojni │ └── HelloJni.java └── tests ├── AndroidManifest.xml ├── default.properties └── src └── com └── example └── HelloJni └── HelloJniTest.java 上面列出使用 NDK 開發項目的基本的目錄結構,C 代碼一般放在 jni 目錄下。 2.編譯 Native Code i) 在命令行(Windows 環境下使用 Cygwin 的命令行) ,將當前目錄改動到 /samples/hello-jni ii) 生成 build.xml android update project -p . -s (註:Windows 下可能需要 使用 android.bat ) iii) 編譯 C 代碼 cd /samples/hello-jni /ndk-build 3.下面就可以使用 Eclipse 編譯運行 Hello Jni. 第 3 章 Hello JNI 示例 | 15 图片 3.1图片 3.1 picture3.1 編寫 NDK 的一般步驟, 1.參考 Hello-Jni ,修改 jni/Android.mk 文件,將所要編譯的文件改成自己的文件。 第 3 章 Hello JNI 示例 | 16 LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY) 2.定義 Native 函數,如HelloJni 中 public native String stringFromJNI(); 3.在 Eclipse 中編譯該項目,注意此時,你無需定義對於的 Native 文件中 C 函數,因為手工定義對於的 C 函數 很容易出錯,可以藉助 javah 工具來完成(包括在 JDK 中)。 1. 使用 Javah 生成對應 C 函數定義 在命令行下 運行 javah com.example.hellojni.HelloJni 其中 com.example.hellojni 為包名,注意運行 Javah 的當前目錄 為 /examples/hello-jni/bin/classes (你也 可以使用 javah 的選項來指定 classpath). 正確運行好,Javah 產生 com_example_hellojni_HelloJni.h 定義如下: /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_hellojni_HelloJni */ #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 第 3 章 Hello JNI 示例 | 17 * Method:unimplementedStringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 從中可以找到 native 方法對應的 C 函數定義,Java_com_example_hellojni_HelloJni_stringFromJNI 5.定義對應的 C 函數,如 Hello-jni 中定義 jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI1 !"); } 6.下面就可以使用 ndk-build 編譯 C 代碼,編譯成功後會在 libs 目錄下生成 libhello-jni.so 图片 3.2图片 3.2 picture3.2 7.在 Java 代碼中調入編譯好的 C 動態庫 static { System.loadLibrary("hello-jni"); } 8.編譯運行,為了測試你的 NDK 例子的確成功運行,可以對 Java_com_example_hellojni_HelloJni_stringF romJNI ,做點小改動 返回 “Hello world from JNI1 !。 注意要 Clean Project ,否則 Eclipse 可能不會重編 譯。 第 3 章 Hello JNI 示例 | 18 图片 3.3图片 3.3 picture3.3 第 3 章 Hello JNI 示例 | 19 44 TwoLibs 示例TwoLibs 示例 隨 Android NDK 提供的另外一個例子 TwoLibs,其中有兩個庫,一個為動態庫,一個為靜態庫,最終供 Androi d Application 使用的動態庫使用靜態庫中的函數,如下圖所示: 图片 4.1图片 4.1 picture4.1 其中在 first.c 中定義了一個簡單的 C 函數 int first(int x, int y) { return x+y; } second.c 調用這個函數 jint Java_com_example_twolibs_TwoLibs_add( JNIEnv* env, jobject this, jint x, jint y ) { return first(x, y); } 為了介紹動態庫調用靜態庫的方法,這個例子將兩個 C 文件編譯成兩個模塊,這是通過 android.mk 來定義的 第 4 章 TwoLibs 示例 | 21 LOCAL_PATH:= $(call my-dir) # first lib, which will be built statically # include $(CLEAR_VARS) LOCAL_MODULE:= libtwolib-first LOCAL_SRC_FILES := first.c include $(BUILD_STATIC_LIBRARY) # second lib, which will depend on and include the first one # include $(CLEAR_VARS) LOCAL_MODULE:= libtwolib-second LOCAL_SRC_FILES := second.c LOCAL_STATIC_LIBRARIES := libtwolib-first include $(BUILD_SHARED_LIBRARY) 註:當然對於這個簡單的例子,大可不必編譯成兩個模塊。 在 Android 應用中調用動態庫(Android 應用只可以調用動態庫) public class TwoLibs extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); int x = 1000; int y = 42; // here, we dynamically load the library at runtime // before calling the native method. // System.loadLibrary("twolib-second"); int z = add(x, y); 第 4 章 TwoLibs 示例 | 22 tv.setText( "The sum of " + x + " and " + y + " is " + z ); setContentView(tv); } public native int add(int x, int y); } 下面就可以使用 ndk-build ,編譯 C 代碼,然後再使用 Eclipse 編譯 Java 代碼,運行結果如下: 图片 4.2图片 4.2 picture4.2 第 4 章 TwoLibs 示例 | 23 55 Android.mk 文件Android.mk 文件 NDK 項目一個重要組成是它的 make 文件 –android.mk. 下面部分來自網路翻譯(省得我再翻譯了:-). 註:大部分情況只需參考 HelloJni 和 twoLibs 的 android.mk 文件即可,如果你想搞清楚 android.mk 中定義 變數的具體含義,可以參考下面翻譯。 Android.mk 文件語法詳述 介绍: 這篇文檔是用來描述你的 C或 C++源文件中 Android.mk 編譯文件的語法的,為了理解她們我們需要您先看完 d ocs/OVERVIEW.html文件來了解它的作用 概覽: Android.mk 文件是用來描述 build system(編譯系統)的,更準確的說: –該文件是一個微型的 GNU Makefile 片段,將由 build system 解析一次或者多次。這樣,您就可以盡量減少 您聲明的變數,並且不要以為在解析過程中沒有任何定義。 • 這個文件但語法是用來允許你將源文件組織成模塊,這個模塊中含有: • 一個靜態庫(.a 文件) • 一個動態庫(.so 文件) 只有動態庫才會被安裝/複製到你的應用程序包,儘管靜態庫可以被用來生成動態庫。你可以在每個模塊中 都定義 一個 Android.mk 文件,你也可以讓多個模塊共用一個 Android.mk 文件。 • build system 可以為你處理許多細節,例如:你不許要在 Android.mk 文件中列出頭文件或者其他的依賴關 係,這些 NDK 的 build system 會自動為你計算並處理。 這也意味著,當更新到新版本的 NDK 的時候,你應該得益於新的 toolchain/platform 的支持,而無需修改你的 Android.mk 文件。 注意:這些語法非常接近於分布在完整的開源的 Android 源代碼中的 Android.mk 文件,儘管是 build system 實現的,但是它們的用法是不同的。這樣故意設計的決定是為了讓應用程序開發者重用「外部」庫的源代碼更容 易。 簡單實例: 再詳細講解語法之前,讓我們先看看一個簡單的例子”hello JNI”,它在 apps/hello-jni/project 下 第 5 章 Android.mk 文件 | 25 • ‘src’目錄下用於存放 java 源文件 • 『jni』目錄下用於存放本地源文件,例如”jni/hello-jni.c” 這個源文件實現了一個簡單的共享庫(shared library):實現了一個本地方法,為 VM 應用程序返回一個字元串。 • 『jni/Android.mk』文件描述了如何生成一個共享庫,它的內容是: —————–Android.mk———————— LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c include $(BUILD_SHARED_LIBRARY) ————————————————— 現在,讓我們分別解釋這幾行 LOCAL_PATH:=$(call my-dir) Android.mk 文件必須以 LOCAL_PATH 變數開始,它用於在樹中定位文件。在這個例子中,宏功能’my-di r’是由 build system 提供的,用於返回當前目錄路徑(包括 Android.mk 文件本身) include $(CLEAR_VARS) CLEAR_VARS 變數是由 build system 提供的,並且指明了一個 GNU makefile 文件,這個功能會清理掉所 有以 LOCAL_ 開頭的內容(例如 LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES 等),除了 LOCAL_PATH,這句話是必須的,因為如果所有的變數都是全局變數的話,所有的可控的編譯文件 都需要在一個單獨的 GNU 中被解析並執行 LOCAL_MODULE :=hello-jni LOCAL_MODULE 變數必須被定義,用來區分 Android.mk 中的每一個模塊。文件名必須是唯一的,不能有空 格。注意,這裡編譯器會為你自動加上一些前綴和後綴,來保證文件是一致的,比如:這裡表明一個動態連接庫 模塊被命名為”hello-jni”,但是最後會生成為”libhello-jni.so”文件。但是在 Java 中裝載這個庫的時候還是 使用”hello-jni”名稱。當然如果我們使用”IMPORTANT NOTE:”,編譯系統就不會為你加上前綴,但是為了 支持 Android 平台源碼中的 Android.mk 文件,也同樣會生成 libhello-jni.so 這樣的文件。 重要提示:如果你將你的模塊命名為 ’libfoo’,編譯系統將不會將前綴 ’lib’ 加上去,並且也會生成 libfoo.s o 文件。 第 5 章 Android.mk 文件 | 26 LOCAL_SRC_FILES := hello-jni.c LOCAL_SRC_FILES 變數被需包括一個 C 和 C++ 源文件的列表,這些會編譯並聚合到一個模塊中。 注意:這裡並不需要你列出頭文件和被包含的文件,因為編譯系統會自動為你計算相關的屬性,源代碼中的列表 會直接傳遞給編譯器。 C++ 默認文件的擴展名是「.cpp」,我們可以通過定義一個 LOCAL_DEFAULT_CPP_EXTENSION 變數來 定義一個不同的 C 文件。不要忘記在初始化前面的「.」點(也就是說”.cpp”可以正常工作,但是 cpp 不能正 常工作) include $(BUILD_SHARED_LIBRARY) BUILD_SHARED_LIBRARY 這個變數是由系統提供的,並且指定給 GNU Makefile 的腳本,它可以收集所有 你定義的”include $(CLEAR_VARS)”中以 LOCAL_ 開頭的變數,並且決定哪些要被編譯,哪些應該做的更 加準確。編譯生成的是以”lib.so”的文件,這個就是共享庫了。我們同樣也可以使用 BUILD_STATIC_LIBRA RY 編譯系統便會生成一個以”lib.a”的文件來供動態庫調用。 在 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) 我們繼續: NDK 提供的變數: 在您的 Android.mk 文件被解析之前這些 GNU Make 變數由編譯系統定義,注意,在某些 情況下,NDK 可能被解析幾次,每次以不同的變數的定義解析的 第 5 章 Android.mk 文件 | 27 CLEAR_VARS CLEAR_VARS 這個變數由系統提供,功能是清理掉所有以 LOCAL_ 開頭的內容,再開始一個新的模塊之 前,你必須包括這段腳本 include ($CLEAR_VARS) BUILD_SHARED_LIBRARY 在編譯腳本中收集所有以 LOCAL_ 開頭的信息並且決定從列出的源代碼中編譯一個目標共享庫。注意,你必須 定義了 LOCAL_MODULE 和 LOCAL_SRC_FILES 變數,使用它的時候,可以這樣定義 include $(BUILD_SHARED_LIBRARY) 注意,我們會生成一個以 lib.so 為名的文件 BUILD_STATIC_LIBRARY 用來構建一個靜態庫,該靜態庫將不會被拷貝到你的 project/packages 下,但是可以被用於動態庫 (看下面的 L OCAL_STATIC_LIBRARY 和 LOCAL_WHOLE_STATIC_LIBRARY 介紹) 例如: include $(BUILD_STATIC_LIBRARY) 注意,這將生成一個 lib.a 為名字的模塊 PREBUILD_SHARED_LIBRARY 在編譯腳本中用於指定一個預先編譯的動態庫,不像 BUILD_SHARED_LIBRARY 和 BUILD_STATIC_LIBR ARY,LOCAL_SRC_FILES 的預先共享庫必須是一個單獨的路徑(如:foo/libfoo.so),而不是源文件。 你可以在另一個模塊中引用預編譯的庫(參見 docs/pribuilds.html) PRIBUILD_STATIC_LIBRARY 這個變數類似於 PREBUILD_SHARED_LIBRARY,但是是針對靜態庫的,(詳見 docs/prebuilds.html) TARGET_ARCH TARGET_ARCH 指框架中 CPU 的名字已經被 Android 開源代碼明確指出了,這裡的 arm 包含了任何 ARM- 獨立結構的架構,以及每個獨立的 CPU 版本 第 5 章 Android.mk 文件 | 28 TARGET_PLATFORM Android 平台的名字在 Android.mk 中被解析,比如”android-3″對應 Android 1.5 系統鏡像,對於平台的名 稱對應 Android 系統的列表,請看 docs/STABLE-APIS.html TARGET_ARCH_ABI 在 Android.mk 中被解析時指CPU+ABI的名字。 目前支持的兩個值 armeabi for ARMv5TE armeabi-v7a 注意,到 Android NDK 1.6_r1,這個值被簡化為”arm”。然而,這個值被重定義可以更好的匹配 Android 平 台內部使用的是什麼 更多的信息可以參見 docs/CPU-ARCH-ABIS.html 未來的 NDK 版本中得到支持,它們會有一個不同的名字,注意所有基於 ARM 的 ABI 都會有一個”TARGE T_ARCH”被定義給 arm,但也有可能有不同的”TARGET_ARCH_ABI” TARGET_ABI 目標平台和 ABI 的鏈接,這裡要定義$(TARGET_PLATFORM)-$(TARGET_ARCH_ABI)它們都非常有 用,特別是當你想測試一下具體的系統鏡像在一個真實設備環境的時候 默認地,這個是”android-3-armeabi” (到 Android NDK 16_R1 版本,使用”android-3-arm”作為默認) NDK 提供的宏功能 以下是使用 GNU make 的宏功能,必須通過使用”$(call )”,返回一個文本信息。 my-dir 返回最後包含的 makefile 的路徑,這通常是當前 Android.mk 所在目錄的路徑,在 Android.mk 開始之前定義 LOCAL——PATH 是很有用的。 在 Android.mk 文件的開始位置定義 LOCAL_PATH :=$(call my-dir) …聲明一個模塊 第 5 章 Android.mk 文件 | 29 include $(LOCAL_PATH)/foo/Android.mk LOCAL_PATH :=($call my-dir) …聲明另一個模塊 這裡的問題是第二次調用”my-dir”定義 LOCAL_PATH 替換 $PATH 為 $PATH/foo,由 於在此之前執行過。 對於這個原因,最好是將額外的其他所有東西都在 Android.mk 中包含進來 LOCAL_PATH :=$(call my-dir) …聲明一個模塊 LOCAL_PATH :=$(call my-dir) …聲明另一個模塊 #在 Android.mk 的最後額外包括進來 include $(LOCAL_PATH)/foo/Android.mk 如果這樣不方便的話,保存第一個 my-dir 調用的值到另一個變數中,例如 MY_LOCAL_PATH :=$(call my-dir) LOCAL_PATH :=$(MY_LOCAL_PATH) …聲明一個模塊 include $(LOCAL_PATH)/foo/Android.mk LOCAL_PATH :=$(MY_LOCAL_PATH) …聲明另一個模塊 all-subdir-makefiles 返回一個 Android.mk 文件所在位置的列表,以及當前的 my-dir 的路徑。比如 sources/foo/Android.mk sources/foo/lib1/Android.mk sources/foo/lib2/Android.mk 如果 sources/foo/Android.mk 包含了這行語句 include $(call all-subdir-makefiles) 那麼,它將會自動將 sources/foo/lib1/Android.mk 和 sources/foo/lib2/Android.mk 包含進來。 此功能可以用於提供深層嵌套的源代碼目錄 build system 的層次結構。請注意,默認情況下,NDK 只會尋找 s ources/*Android.mk 第 5 章 Android.mk 文件 | 30 this-makefile 返回當前 makefile 的路徑(也就是那個功能被調用了) parent-makefile 返回 makefile 的包含樹,也就是包含 Makefile 當前的文件 grand-parent-makefile 你猜? import-module 一個允許你通過名字找到並包含另一個模塊的的 Android.mk 的功能,例如 $(call import-module,) 這將會找到通過 NDK_MODULE_PATH 環境變數引用的模塊的目錄列表,並且將其自動包含到 Android.mk 中 詳細信息請參閱:docs/IMPORT-MODULE.html 模塊變數描述: 下面的這些變數是用來描述怎樣用你的模塊來編譯系統的。你可以定義它們中的一些比如 “include $(CLEA R_VARS)”和”include $(BUILD_XXX)”,正如前面所寫的,$(CLEAR_VARS)是一個可以取消定義/清楚 所有變數的腳本。 LOCAL_PATH 這個變數是用來給出當前文件的路徑。您比系再您的 Android.mk 開始位置定義: LOCAL_PATH :=$(call my-dir) 注意,這個變數是不被$(CLEAR_VARS)清除的,其他的都要被清除(我們可以定義幾個模塊到一個文件中) LOCAL_MODULE 這個是你模塊的名稱,它在你的所有模塊中名稱必須是唯一的,並且不能包含空格。你必須在包含任何 $(BUIL D-XXX)腳本之前定義它。 默認情況下,模塊的名稱決定了生成的文件的名稱,例如 lib.so,它是 foo 模塊的名字。 你可以用 LOCAL_MODULE_FILENAME 覆蓋默認的那一個 LOCAL_MODULE_FILENAME 第 5 章 Android.mk 文件 | 31 這個變數是可選的,並且允許你重新定義生成文件的名字。默認的,模塊將始終生成 lib.a 或者 lib.so 文件,這是 標準的 UNIX 公約 你可以通過 LOCAL_MODULE_FILENAME 覆蓋它 LOCAL_MODULE :=foo-version-1 LOCAL_MODULE_FILENAME :=libfoo 注意:你不能將文件路徑或者文件擴展名寫到 LOCAL_MODULE_FILENAME 里,這些將有 build system 自 動處理。 LOCAL_SRC_FILES 這是你模塊中將要編譯的源文件列表。只列出將被傳遞到編譯器的文件,因為 build system 自動為您計算了它們 的依賴。 注意:源文件的名稱都是相對 LOCAL_PATH 的,您可以使用路徑組件,例如 LOCAL_SRC_FILES :=foo.c\ toto/bar.c 注意:在 build system 時請務必使用 UNIX 風格的斜杠(/),windows 風格的斜杠將不會得到處理 LOCAL_CPP_EXTENSION 這是個可選的變數,可以被定義為文件擴展名為 c++的源文件,默認是”.cpp”,但是你可以改變它,比如 LOCAL_CPP_EXTENSION:=.cxx LOCAL_C_INCLUDES 可選的路徑列表,相對於 NDK 的根目錄,當編譯所有的源文件(C、C++、或者彙編)時將被追加到搜索路徑 中 例如: LOCAL_C_INCLUDES:=sources/foo 或者 LOCAL_C_INCLUDES:=$(LOCAL_PATH)/../foo 這些都在任何相應列入標誌之前被放置在 LOCAL_CFLAGS / LOCAL_CPPFLAGS 當用用 ndk-gdb 啟動本機調試時,LOCAL_C_INCLUDES 也會自動被使用到 LOCAL_CFLAGS 當編譯 C/C++源文件時傳遞一個可選的編譯器標誌。 這對於指定額外的宏定義或編譯選項很有用 第 5 章 Android.mk 文件 | 32 重要提示:盡量不要改變 Android.mk 中的優化/調試級別,這個可以通過在 Application.mk 中設置相應的信息 來自動為你處理,並且會會讓 NDK 生成在調試過程中使用的有用的數據文件。 注意:在 Android-ndk-1.5_r1 中,只使用於 C 源文件,而不適用於 C++源文件。在匹配所有 Android build s ystem 的行為已經得到了糾正。(現在你可以為 C++源文件使用 LOCAL_CPPFLAGS 來指定標誌) 它可以用 LOCAL_CFLAGS += -I來指定額外的包含路徑,然而,如果使用 LOCAL_C_INCLUDES 會更 好,因為用 ndk-gdk 進行本地調試的時候,那些路徑依然是需要使用的 LOCAL_CXXFLAGS LOCAL_CPPFLAGS 的別名。請注意,這個標誌在 NDK 的未來的版本中將會消失 LOCAL_CPPFLAGS 當只編譯 C++源代碼的時候,將傳遞一個可選的編譯器標誌。它們將會出現再 LOCAL_CFLAGS 之後。 注意:在 Android NDK-1.5_r1 版本中,相應的標誌可以應用於 C 或 C++ 源文件上。在配合完整的 Android b uild system 的時候,這已經得到了糾正。(你可以使用 LOCAL_CFLAGS 去指定 C 或 C++源文件) LOCAL_STATIC_LIBRARIES 靜態庫模塊的列表(通過 BUILD_STATIC_LIBRARY 創建)應與此模塊鏈接。這僅僅是為了使動態庫敏感。 LOCAL_SHARED_LIBRARY 共享庫的列表「模塊」,這個模塊依賴於運行時.這在鏈接的時候和在生成的文 件中嵌入相應的信息是非常必要的 LOCAL_WHOLE_STATIC_LIBRARIES LOCAL_WHOLE_STATIC_LIBRARIES 是一個用於表示相應的庫模塊被用作為「整個檔案」到鏈接程序的變 數。 當幾個靜態庫之間有循環依賴關係的時候,通常是很有益的。注意,當用來編譯一個動態庫的時候,這將迫使你 將所有的靜態庫中的對象文件添加到最終的二進位文件中。但生成可執行程序時,這是不確定的。 LOCAL_LDLIBS 當額外的鏈接標誌列表被用於在編譯你的模塊時,通過用”-l”前綴的特定系統庫傳遞名字是很有用的。例 如,下面的舊愛哪個告訴你生成一個在載入時鏈接到/system/lib/libz.so 的模塊。 LOCAL_LDLIBS :=-lz LOCAL_ALLOW_UNDEFINED_SYMBOLS 默認情況下,當試圖編譯一個共享庫的時候遇到任何未定義的引用都可能導致”未定義符號”(undefined symb ol)的錯誤。這在你的源代碼中捕獲 bug 會很有用。 第 5 章 Android.mk 文件 | 33 然而,但是由於某些原因,你需要禁用此檢查的話,設置變數為”true”即可。需要注意的是,相應的共享庫在 運行時可能載入失敗。 LOCAL_ARM_MODE 默認情況下,在”thumb”模式下會生成 ARM 目標二進位,其中每個指令都是 16 位寬。你可以定義這個變數 為”arm”,如果你想在”arm”模式下(32 位指令)強迫模塊對象文件的生成。例如: LOCAL_ARM_MODE := arm 注意,你需要執行編譯系統為在 ARM 模式下通過文件的名字增加後綴的方式編譯指定的源文件。比如: LOCAL_SRC_FILES :=foo.c bar.c.arm 這會告訴編譯系統一直以 ARM 模式編譯”bar.c”,並且通過 LOCAL_ARM_MODE 的值編譯 foo.c。 注意:在 Application.mk 文件中設置 APP_OPTIM 為”debug”也會強制 ARM 二進位文件的生成。這是因為 工具鏈調試其中的 bug 不會處理 thumb 代碼。 LOCAL_ARM_NEON 定義這個變數為”true”會允許在你的 C 或 C++源文件的 GCC 的內部函數中使用 ARM 高級 SIMD(又名 NE ON),以及在聚合文件中的 NEON 指令。 當針對”armeabi-v7a”ABI對應的 ARMv7 指令集時你應該定義它。注意,並不是所有的 ARMv7 都是基於 N EON 指令集擴展的 CPU,你應該執行運行時來檢測在運行時中這段代碼的安全。 另外,你也可以指定特定的源文件,比如用支持 NEON”.neon”後綴的源文件也可以被編譯。 LOCAL_SRC_FILES :=foo.c.neon bar.c zoo.c.arm.neon 在這個例子中,”foo.c”將會被編譯在 thumb+neon 模式中,”bar.c”以 thumb 模式編譯,zoo.c 以 ar m+neon 模式編譯。 注意,如果你使用兩個的話,”.neon”後綴必須出現在”.arm”後綴之後 (就是 foo.c.arm.neon 可以工 作,但是 foo.c.neon.arm 不工作) LOCAL_DISABLE_NO_EXECUTE Android NDK r4 開始添加了支持”NX位”安全功能特性。它是默認啟用的,如果你需要的話,可以通過設置變 數為「true」來禁用它。 注意:此功能不修改 ABI,並且只在 ARMv6 及以上的 CPU 設備的內核上被啟用。 第 5 章 Android.mk 文件 | 34 更多信息,可以參見: http://en.wikipedia.org/wiki/NX_bit http://www.gentoo.org/proj/en/hardened/gnu-stack.xml LOCAL_EXPORT_CFLAGS 定義這個變數用來記錄 C/C++編譯器標誌集合,並且會被添加到其他任何以 LOCAL_STATIC_LIBRARIES 和 LOCAL_SHARED_LIBRARIES 的模塊的 LOCAL_CFLAGS 定義中。 例如:這樣定義”foo”模塊 include $(CLEAR_VARS) LOCAL_MODULE :=foo LOCAL_SRC_FILES :=foo/foo.c LOCAL_EXPORT_CFLAGS :=-DFOO=1 include $(BUILD_STATIC_LIBRARY) 另一個模塊,叫做”bar”,並且依賴於上面的模塊 include $(CLEAR_VARS) LOCAL_MODULE :=bar LOCAL_SRC_FILES :=bar.c LOCAL_CFLAGS:=-DBAR=2 LOCAL_STATIC_LIBRARIES:=foo include $(BUILD_SHARED_LIBRARY) 然後,當編譯 bar.c 的時候,標誌”-DFOO=1 -DBAR=2″將被傳遞到編譯器。 輸出的標誌被添加到模塊的 LOCAL_CFLAGS 上,所以你可以很容易複寫它們。它們也有傳遞性:如果”zo o”依賴”bar”,「bar」依賴”foo”,那麼”zoo”也將繼承”foo”輸出的所有標誌。 最後,當編譯模塊輸出標誌的時候,這些標誌並不會被使用。在上面的例子中,當編譯 foo/foo.c 時, -DFO O=1 將不會被傳遞給編譯器。 LOCAL_EXPORT_CPPFLAGS 類似 LOCAL_EXPORT_CFLAGS,但適用於 C++標誌。 LOCAL_EXPORT_C_INCLUDES 類似 LOCAL_EXPORT_C_CFLAGS,但是只有 C 能包含路徑,如果”bar.c”想包含一些由”foo”模塊提供 的頭文件的時候這會很有用。 LOCAL_EXPORT_LDLIBS 第 5 章 Android.mk 文件 | 35 類似於 LOCAL_EXPORT_CFLAGS,但是只用於鏈接標誌。注意,引入的鏈接標誌將會被追加到模塊的 LOC AL_LDLIBS,這是因為UNIX連接器的工作方式。 當模塊 foo 是一個靜態庫的時候並且代碼依賴於系統庫時會很有用的。LOCAL_EXPORT_LDLIBS 可以用於輸 出依賴,例如: include $(CLEAR_VARS) LOCAL_MODULE := foo LOCAL_SRC_FILES := foo/foo.c LOCAL_EXPORT_LDLIBS := -llog include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := bar LOCAL_SRC_FILES := bar.c LOCAL_STATIC_LIBRARIES := foo include $(BUILD_SHARED_LIBRARY) 這裡,在連接器命令最後,libbar.so 將以-llog 參數進行編譯來表明它依賴於系統日誌庫,因為它依賴於 foo。 LOCAL_FILTER_ASM 這個變數定義了一個 shell 命令,將用於過濾,從你的 LOCAL_SRC_FILES 中產生的或者聚合文件。 當它被定義了,將會出現如下的情況: –任何 C 或 C++源文件將會生成到一個臨時的聚合的文件中(而不是被編譯成目標文件) –任何臨時聚合文 件,任何在 LOCAL_SRC_FILES 中列出的聚合文件將通過 LOCAL_FILER_ASM 命令生成另一個臨時聚合 文件 –這些過濾聚合文件被編譯成目標文件。 換種說法,如果 LOCAL_SRC_FILES := foo.c bar.S LOCAL_FILTER_ASM := myasmfilter foo.c –1–> $OBJS_DIR/foo.S.original –2–> $OBJS_DIR/foo.S –3–> $OBJS_DIR/foo.o bar.S –2–> $OBJS_DIR/bar.S –3–> $OBJS_DIR/bar.o 「1」對應的編譯器,「2」的過濾器,和「3」的彙編。過濾器必須是一個獨立的 shell 命令作為第一個參數輸入 文件的名稱,和輸出的名稱第二,如文件為: myasmfilter$ OBJS_DIR/ foo.S.original$ OBJS_DIR/ foo.S myasmfilter bar.S$ OBJS_DIR/ bar.S 第 5 章 Android.mk 文件 | 36 66 application.mkapplication.mk 配合 android.mk 使用的 make 文件还有一个 application.mk ,大部分情况无需修改该文件,下面也来自网络 翻译 Application.mk 文件 简介: 要将 C\C++ 代码编译为 SO 文件,光有 Android.mk 文件还不行,还需要一个 Application.mk 文件。 本文档 是描述你的 Android 应用程序中需要的本地模块的 Application.mk 的语法使用,要明白如下。 Application.mk 目的是描述在你的应用程序中所需要的模块(即静态库或动态库)。 Application.mk 文件通常被放置在 $PROJECT/jni/Application.mk 下,$PROJECT 指的是您的项目。 另一种方法是将其放在顶层的子目录下: $NDK/apps 目录下,例如: $NDK/apps//Application.mk 是一个简称,用于描述你的 NDK 编译系统的应用程序(这个名字不会生成共享库或者最终的包) 下面是 Application.mk 中定义的几个变量。 APP_PROJECT_PATH 这个变量是强制性的,并且会给出应用程序工程的根目录的一个绝对路径。这是用来复制或者安装一个没有任何 版本限制的 JNI 库,从而给 APK 生成工具一个详细的路径。 APP_MODULES 这个变量是可选的,如果没有定义,NDK 将由在 Android.mk 中声明的默认的模块编译,并且包含所有的子文 件(makefile 文件) 如果 APP_MODULES 定义了,它不许是一个空格分隔的模块列表,这个模块名字被定义在 Android.mk 文件 中的 LOCAL_MODULE 中。注意 NDK 会自动计算模块的依赖 注意:NDK 在 R4 开始改变了这个变量的行为,再次之前: - 在您的Application.mk中,该变量是强制的 - 必须明确列出所有需要的模块 APP_OPTIM 这个变量是可选的,用来定义“release”或”debug”。在编译您的应用程序模块的时候,可以用来改变优先 级。 “release”模式是默认的,并且会生成高度优化的二进制代码。”debug”模式生成的是未优化的二进制代 码,但可以检测出很多的 BUG,可以用于调试。 注意:如果你的应用程序是可调试的(即,如果你的清单文件中设置了 android:debuggable 的属性是”tru e”)。默认的是”debug”而不是”release”。这可以通过设置 APP_OPTIM 为”release”来将其覆盖。 第 6 章 application.mk | 38 注意:可以在”release”和”debug”模式下一起调试,但是”release”模式编译后将会提供更少的 BUG 信 息。在我们清楚 BUG 的过程中,有一些变量被优化了,或者根本就无法被检测出来,代码的重新排序会让这些 带阿弥变得更加难以阅读,并且让这些轨迹更加不可靠。 APP_CFLAGS 当编译模块中有任何 C 文件或者 C++文件的时候,C 编译器的信号就会被发出。这里可以在你的应用中需要这 些模块时,进行编译的调整,这样就不许要直接更改 Android.mk 为文件本身了 重要警告:+++++++++++++++++++++++++++++++++++++++++++++++ + + + + 在这些编制中,所有的路径都需要于最顶层的 NDK 目录相对应。 + 例如,如果您有以下设置: + +sources/foo/Android.mk +sources/bar/ Android.mk + 编译过程中,若要在 foo/Android.mk 中指定你要添加的路径到 bar 源代码中, + 你应该使用 + APP_CFLAGS += -Isources/bar + 或者交替: + APP_CFLAGS += -I $(LOCAL_PATH )/../bar + + 使用’-l../bar/’将不会工作,以为它将等同于”-l$NDK_ROOT/../bar” ++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++ 注意:在 Android 的 NDK 1.5_r1,只适用于 C 源文件,而不适合 C++。 这已得到纠正,以建立完整相匹配的 Android 系统。 APP_CXXFLAGS APP_CPPFLAGS 的别名,已经考虑在将在未来的版本中废除了 APP_CPPFLAGS 当编译的只有 C++源文件的时候,可以通过这个 C++编译器来设置 注意:在 Android NDK-1.5_r1 中,这个标志可以应用于 C 和 C++源文件中。并且得到了纠正,以建立完整的 与系统相匹配的 Android 编译系统。你先可也可以使用 APP_CFLAGS 来应用于 C 或者 C++源文件中。 建议 使用 APP_CFLAGS APP_BUILD_SCRIPT 默认情况下,NDK 编译系统会在 $(APP_PROJECT_PATH)/jni 目录下寻找名为 Android.mk 文件: $(AP P_PROJECT_PATH)/jni/Android.mk 第 6 章 application.mk | 39 如果你想覆盖此行为,你可以定义 APP_BUILD_SCRIPT 来指定一个备用的编译脚本。一个非绝对路径总是被 解释为相对于 NDK 的顶层的目录。 APP_ABI 默认情况下,NDK 的编译系统回味”armeabi”ABI生成机器代码。喜爱哪个相当于一个基于 CPU 可以进行浮 点运算的 ARMv5TE。你可以使用 APP_ABI 来选择一个不同的 ABI。 比如:为了在 ARMv7 的设备上支持硬件 FPU 指令。可以使用 APP_ABI := armeabi-v7a 或者为了支持 IA-32 指令集,可以使用 APP_ABI := x86 或者为了同时支持这三种,可以使用 APP_ABI := armeabi armeabi-v7a x86 APP_STL 默认情况下,NDK 的编译系统为最小的 C++运行时库(/system/lib/libstdc++.so)提供 C++头文件。 然而,NDK 的 C++的实现,可以让你使用或着链接在自己的应用程序中。 例如: APP_STL := stlport_static –> static STLport library APP_STL := stlport_shared –> shared STLport library APP_STL := system –> default C++ runtime library 下面是一个 Application.mk 文件的示例: APP_PROJECT_PATH := 第 6 章 application.mk | 40 77 调试调试 開發應用一個關鍵的步驟是調試,對於 NDK 的 C 代碼調試有很多種方法, • 對於和 Android 平台相關性不大的部分代碼,可以單獨創建一個 C/C++項目,編寫測試代碼,測試完成 後,再編譯成 NDK 動態庫或靜態庫模塊。 • 使用 NDK-GDB,NDK-GDB 的命令行調試方法和 GDB 類似,網路有很多關於 GDB 的教程 • 使用 Eclipse+CDT+GDB 調試 android NDK 程序 實時調試,不過這種方法設置起來不是十分方便,調試 起來需要在 GDB 和 Eclipse 之間來回切換,適合於有經驗的程序員。 • 這裡介紹一個開發嵌入式系統調試的「終極工具:-)」-printf. 開發嵌入式系統調試常用的也是最簡單的方 法,是使用 printf 列印調試信息。 修改一下 two-lib 的例子 ,使用 first.c 中的 first 函數實現一個加法計算器 第 7 章 调试 | 42 图片 7.1图片 7.1 picture7.1 這裡我們想在調用 first(int x,int y) 顯示出傳入的 x ,y 值。Android NDK 中提供了一個 Log 庫,其頭文件為 an droid/log.h ,可以提供 Androd Java 代碼中的 Log 功能,也是可以在 LogCat 中列印信息。 第 7 章 调试 | 43 具體方法如下: 1.修改 first.c ,添加合適的列印語句 #include "first.h" #include int first(int x, int y) { __android_log_print(ANDROID_LOG_INFO, "MYPROG", "x = %d, y =%d", x,y); return x + y; } 2.修改 android.mk 文件,添加需要鏈接的 Log 庫 LOCAL_PATH:= $(call my-dir) # first lib, which will be built statically # include $(CLEAR_VARS) LOCAL_MODULE:= libtwolib-first LOCAL_SRC_FILES := first.c include $(BUILD_STATIC_LIBRARY) # second lib, which will depend on and include the first one # include $(CLEAR_VARS) LOCAL_MODULE:= libtwolib-second LOCAL_SRC_FILES := second.c LOCAL_LDLIBS := -llog LOCAL_STATIC_LIBRARIES := libtwolib-first include $(BUILD_SHARED_LIBRARY) 然後就可以編譯 Native C 代碼,運行這個例子,可以在 LogCat 看到列印的信息: 图片 7.2图片 7.2 picture7.2 第 7 章 调试 | 44 本例下載 第 7 章 调试 | 45 88 Box2D 的 Android NDK 实现Box2D 的 Android NDK 实现 Box2D 是一个用于游戏的 2D 刚体仿真库。程序员可以在他们的游戏里使用它,它可以使物体的运动更加可 信,让世界看起来更具交互性。从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系 统,而不是由动画师去移动你的物体。你可以让牛顿来做导演。 Box2D 是用可移植的 C++ 来写成的。因此也可以通过 Android NDK 将它引用到 Android 平台,从而也可以在 Android 平台使用 Box2D 引擎来编写游戏。 这里介绍的是基于 AndEngine 的 Box2D 库的扩展,它就是通过 NDK 将 Box2D C++ 函数通过 JNI 实现了对 应的 Java 接口。源码可以从 http://code.google.com/p/andengine/ 下载,或是从本地下载 (210 MM 包括所 有源码及示例)。 第 8 章 Box2D 的 Android NDK 实现 | 47 图片 8.1图片 8.1 picture8.1 src 目录提供了 Box2D 的 Java 接口,主要是通过调用 native Box2D C++函数库来实现。NDK 最常见的用法 是将一些 C/C++函数库移植到 Java 平台,而不是直接用来写 Android 应用。 这是使用 Box2D 函数库实现的一个实例 PhysicsRevoluteJointExample,源码在上面 tar 包中。性能相当不 错。 可以参考其中 Android.mk 文件的内容。 后面将专门介绍 AndEngine 开发包,如果你对写手机游戏感兴趣的话,请留意我们的博客。 第 8 章 Box2D 的 Android NDK 实现 | 48 图片 8.2图片 8.2 picture8.2 第 8 章 Box2D 的 Android NDK 实现 | 49 更多信息请访问 http://wiki.jikexueyuan.com/project/android-ndk-development-tutorial/
还剩50页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

文杰天下

贡献于2016-10-25

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf