Facebook Twitter LinkedIn E-mail
magnify
Home Posts tagged "NDK"

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/ 下载,或是从本地下载 (210MM 包括所有源码及示例)。

src 目录提供了Box2D的Java接口,主要是通过调用native Box2D C++函数库来实现。NDK最常见的用法是将一些C/C++函数库移植到Java平台,而不是直接用来写Android应用。

这是使用Box2D函数库实现的一个实例PhysicsRevoluteJointExample,源码在上面tar包中。性能相当不错。

可以参考其中Android.mk 文件的内容。

后面将专门介绍AndEngine开发包,如果你对写手机游戏感兴趣的话,请留意我们的博客。

 

Android NDK 开发教程七:调试

开发应用一个关键的步骤是调试,对于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 函数实现一个加法计算器

这里我们想在调用first(int x,int y) 显示出传入的x ,y 值。Android NDK 中提供了一个Log库,其头文件为android/log.h ,可以提供Androd Java代码中的Log功能,也是可以在LogCat中打印信息。

具体方法如下:

1. 修改first.c ,添加合适的打印语句

#include "first.h"
#include <android/log.h>

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看到打印的信息:

本例下载

 

Android NDK 开发教程六: application.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/<myapp>/Application.mk

<myapp>是一个简称,用于描述你的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的属性是”true”)。默认的是”debug”而不是”release”。这可以通过设置APP_OPTIM为”release”来将其覆盖。

注意:可以在”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++。
这已得到纠正,以建立完整相匹配的Andr​​oid系统。

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文件:
$(APP_PROJECT_PATH)/jni/Android.mk

如果你想覆盖此行为,你可以定义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 := <path to project>

 

Android NDK 开发教程五:Android.mk文件

NDK项目一个重要组成是它的make 文件 –android.mk. 下面部分来自网络翻译(省得我再翻译了:-).

注:大部分情况只需参考HelloJni 和twoLibs 的android.mk 文件即可,如果你想搞清楚android.mk 中定义变量的具体含义,可以参考下面翻译。

Android.mk文件语法详述

介绍:
————

这篇文档是用来描述你的C或C++源文件中Android.mk编译文件的语法的,为了理解她们我们需要您先看完
docs/OVERVIEW.html(http://hualang.iteye.com/blog/1135105)文件来了解它的作用

概览:
————
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下

–‘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-dir’是由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.so文件。

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<module_name>.so”的文件,这个就是共享库了。我们同样也可以使用BUILD_STATIC_LIBRARY编译系统便会生成一个以”lib<module_name>.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可能被解析几次,每次以不同的变量的定义解析的

CLEAR_VARS
CLEAR_VARS这个变量由系统提供,功能是清理掉所有以LOCAL_开头的内容,再开始一个新的模块之前,你必须包括这段脚本

include ($CLEAR_VARS)

BUILD_SHARED_LIBRARY

在编译脚本中收集所有以LOCAL_开头的信息并且决定从列出的源代码中编译一个目标共享库。注意,你必须定义了LOCAL_MODULE和LOCAL_SRC_FILES变量,使用它的时候,可以这样定义

include $(BUILD_SHARED_LIBRARY)

注意,我们会生成一个以lib<module_name>.so为名的文件

BUILD_STATIC_LIBRARY

用来构建一个静态库,该静态库将不会被拷贝到你的project/packages下,但是可以被用于动态库
(看下面的LOCAL_STATIC_LIBRARY和LOCAL_WHOLE_STATIC_LIBRARY介绍)

例如:
include $(BUILD_STATIC_LIBRARY)

注意,这将生成一个lib<module_name>.a为名字的模块

PREBUILD_SHARED_LIBRARY
在编译脚本中用于指定一个预先编译的动态库,不像BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY,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版本

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都会有一个”TARGET_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 <function>)”,返回一个文本信息。

my-dir

返回最后包含的makefile的路径,这通常是当前Android.mk所在目录的路径,在Android.mk开始之前定义
LOCAL——PATH是很有用的。

在Android.mk文件的开始位置定义
LOCAL_PATH :=$(call my-dir)

…声明一个模块
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只会寻找
sources/*Android.mk

this-makefile
返回当前makefile的路径(也就是那个功能被调用了)

parent-makefile
返回makefile的包含树,也就是包含Makefile当前的文件

grand-parent-makefile
你猜?

import-module
一个允许你通过名字找到并包含另一个模块的的Android.mk的功能,例如

$(call import-module,<name>)

这将会找到通过NDK_MODULE_PATH环境变量引用的模块<name>的目录列表,并且将其自动包含到
Android.mk中

详细信息请参阅:docs/IMPORT-MODULE.html

模块变量描述:
———————————-
下面的这些变量是用来描述怎样用你的模块来编译系统的。你可以定义它们中的一些比如
“include $(CLEAR_VARS)”和”include $(BUILD_XXX)”,正如前面所写的,$(CLEAR_VARS)是一个可以取消定义/清楚所有变量的脚本。

LOCAL_PATH
这个变量是用来给出当前文件的路径。您比系再您的Android.mk开始位置定义:
LOCAL_PATH :=$(call my-dir)
注意,这个变量是不被$(CLEAR_VARS)清除的,其他的都要被清除(我们可以定义几个模块到一个文件中)

LOCAL_MODULE
这个是你模块的名称,它在你的所有模块中名称必须是唯一的,并且不能包含空格。你必须在包含任何
$(BUILD-XXX)脚本之前定义它。

默认情况下,模块的名称决定了生成的文件的名称,例如lib<foo>.so,它是foo模块的名字。

你可以用LOCAL_MODULE_FILENAME覆盖默认的那一个

LOCAL_MODULE_FILENAME

这个变量是可选的,并且允许你重新定义生成文件的名字。默认的,模块<foo>将始终生成lib<foo>.a或者lib<foo>.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++源文件时传递一个可选的编译器标志。
这对于指定额外的宏定义或编译选项很有用

重要提示:尽量不要改变Android.mk中的优化/调试级别,这个可以通过在Application.mk中设置相应的信息来自动为你处理,并且会会让NDK生成在调试过程中使用的有用的数据文件。

注意:在Android-ndk-1.5_r1中,只使用于C源文件,而不适用于C++源文件。在匹配所有Android build system的行为已经得到了纠正。(现在你可以为C++源文件使用LOCAL_CPPFLAGS来指定标志)

它可以用LOCAL_CFLAGS += -I<path>来指定额外的包含路径,然而,如果使用LOCAL_C_INCLUDES会更好,因为用ndk-gdk进行本地调试的时候,那些路径依然是需要使用的

LOCAL_CXXFLAGS
LOCAL_CPPFLAGS的别名。请注意,这个标志在NDK的未来的版本中将会消失

LOCAL_CPPFLAGS
当只编译C++源代码的时候,将传递一个可选的编译器标志。它们将会出现再LOCAL_CFLAGS之后。

注意:在Android NDK-1.5_r1版本中,相应的标志可以应用于C或C++源文件上。在配合完整的Android build 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 symbol)的错误。这在你的源代码中捕获bug会很有用。

然而,但是由于某些原因,你需要禁用此检查的话,设置变量为”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(又名NEON),以及在聚合文件中的NEON指令。

当针对”armeabi-v7a”ABI对应的ARMv7指令集时你应该定义它。注意,并不是所有的ARMv7都是基于NEON指令集扩展的CPU,你应该执行运行时来检测在运行时中这段代码的安全。

另外,你也可以指定特定的源文件,比如用支持NEON”.neon”后缀的源文件也可以被编译。

LOCAL_SRC_FILES :=foo.c.neon bar.c zoo.c.arm.neon

在这个例子中,”foo.c”将会被编译在thumb+neon模式中,”bar.c”以thumb模式编译,zoo.c以arm+neon模式编译。

注意,如果你使用两个的话,”.neon”后缀必须出现在”.arm”后缀之后
(就是foo.c.arm.neon可以工作,但是foo.c.neon.arm不工作)

LOCAL_DISABLE_NO_EXECUTE

Android NDK r4开始添加了支持”NX位”安全功能特性。它是默认启用的,如果你需要的话,可以通过设置变量为“true”来禁用它。

注意:此功能不修改ABI,并且只在ARMv6及以上的CPU设备的内核上被启用。

更多信息,可以参见:
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上,所以你可以很容易复写它们。它们也有传递性:如果”zoo”依赖”bar”,“bar”依赖”foo”,那么”zoo”也将继承”foo”输出的所有标志。

最后,当编译模块输出标志的时候,这些标志并不会被使用。在上面的例子中,当编译foo/foo.c时,
-DFOO=1将不会被传递给编译器。

LOCAL_EXPORT_CPPFLAGS

类似LOCAL_EXPORT_CFLAGS,但适用于C++标志。

LOCAL_EXPORT_C_INCLUDES

类似LOCAL_EXPORT_C_CFLAGS,但是只有C能包含路径,如果”bar.c”想包含一些由”foo”模块提供的头文件的时候这会很有用。

LOCAL_EXPORT_LDLIBS

类似于LOCAL_EXPORT_CFLAGS,但是只用于链接标志。注意,引入的链接标志将会被追加到模块的LOCAL_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

 

Android NDK 开发教程四:TwoLibs示例

随Android NDK 提供的另外一个例子TwoLibs,其中有两个库,一个为动态库,一个为静态库,最终供Android Application使用的动态库使用静态库中的函数,如下图所示:

其中在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 来定义的。

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);

 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代码,运行结果如下:

 

Android NDK 开发教程三: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的命令行) ,将当前目录改动到 <ndk-root>/samples/hello-jni

ii) 生成build.xml

android update project -p . -s  (注:Windows下可能需要  使用android.bat )

iii) 编译C代码

cd <ndk-root>/samples/hello-jni

<ndk_root>/ndk-build

3. 下面就可以使用Eclipse 编译运行 Hello Jni.

编写NDK的一般步骤,

1. 参考Hello-Jni ,修改jni/Android.mk 文件,将所要编译的文件改成自己的文件。

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中)。

4. 使用Javah 生成对应C函数定义

在命令行下  运行 javah com.example.hellojni.HelloJni

其中com.example.hellojni 为包名,注意运行Javah 的当前目录 为 <ndk>/examples/hello-jni/bin/classes (你也可以使用javah 的选项来指定classpath).

正确运行好,Javah产生 com_example_hellojni_HelloJni.h

定义如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include &lt;jni.h&gt;
/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern &quot;C&quot; {
#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

从中可以找到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)-&gt;NewStringUTF(env, &quot;Hello from JNI1 !&quot;);
}

6. 下面就可以使用ndk-build编译C代码,编译成功后会在libs 目录下生成libhello-jni.so

7. 在Java代码中调入编译好的C动态库

static {
 System.loadLibrary(&quot;hello-jni&quot;);
 }

8. 编译运行,为了测试你的NDK例子的确成功运行,可以对Java_com_example_hellojni_HelloJni_stringFromJNI ,做点小改动 返回 “Hello world from JNI1 !。 注意要Clean Project,否则Eclipse可能不会重编译。