Android NDK学习记录

来源:互联网 发布:java httpclient json 编辑:程序博客网 时间:2024/06/01 14:36

一 简介

android NDK( Native Developer Kit )是个工具集,它提供一套方法,使Android应用程序(Java程序)可以内嵌native(C,C++)程序。
Android应用程序(Java程序)运行于Dalvik虚拟机中。NDK允许应用程序的一部分使用native语言(C,C++)来实现。这对很多程序都是有帮助的,比如需要利用以前完成的C,C++代码,也可以提高运行速度。


NDK提供以下:
1. 一组工具和编译文件,用来将C/C++代码生成Native库。
2. 将Native库嵌入应用程序包(APK)的方法。
3. 一组Native系统头文件和库。它们支持所有未来Android版本(从Android1.5开始)。但应用程序使用Native Activity则必须在Android2.3或更高版本使用。(因为android.app.NativeActivity从API 9才有)
4. 文档,例子和指南。


最近的NDK(NDK R7C)支持的指令集如下:
1.  ARMv5TE (including Thumb-1 instructions)
2.    ARMv7-A (including Thumb-2 and VFPv3-D16 instructions, with optional support for NEON/VFPv3-D32 instructions)
3.    x86 instructions (see CPU-ARCH-ABIS.HTML for more information) 


ARMv5TE(包括Thumb-1)与ARMv7-A的主要区别是:ARMv7-A支持硬件VFP, Thumb-2 ,Neon指令。


可以编译为任意一种或者两者的指令集, 缺省编译为ARMv5TE指令。但转换为 ARMv7-A 也是很容易的(Application.mk  中修改即可)。也可以在同一时刻编译为双指令集。( CPU-ARCH-ABIS.HTML in the NDK package 中下一步察看)


NDK提供libc(C库),libm(数学库), 3D graphics 库的稳定头文件等。


NDK的成分如下:
NDK包含APIs,文档,例子程序等。


Developer Tools包含如下:
1.交叉编译器,可以在Linux,Windows,MacOS下产生 native ARM二进制文件。
2.一组系统头文件
libc (C library) headers
libm (math library) headers
JNI interface headers
libz (Zlib compression) headers
liblog (Android logging) header
OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headers
libjnigraphics (Pixel buffer access) header (for Android 2.2 and above).
A Minimal set of headers for C++ support
OpenSL ES native audio libraries
Android native application APIS


NDK同时还提供一套编译系统,以方便使用。可以不必了解Toolchain/CPU/Platform/ABI等细节,只是创建一个简单的build 文件,指明哪个文件需要被编译,以及哪个Android应用程序将要使用它,就可以编译成功。

NDK无法产生最终的APK,只能产生被APK所使用的动态库。(Sam:其实还可以产生native C可执行程序)


NDK需要GNU Make 3.81或者以上版本(make -v可以看到)。

另外:Android1.5之后,Android 2.3之前的Android版本。使用C/C++代码,只能用JNI方式,并使用NDK编译。
但在Android2.3 之后,因为有了 android.app.NativeActivity 。所以可以不再利用JNI方式由Java调用C/C++代码编译出的库了。
注:一些Linux下单列出来的库,被合并到C库中了,例如libpthread.so. 被合并入libc.so


二  Android.mk的制作

Android.mk简介:
Android.mk文件用来告知NDK Build 系统关于Source的信息。 Android.mk将是GNU Makefile的一部分,且将被Build System解析一次或多次。所以,请尽量少的在Android.mk中声明变量,也不要假定任何东西不会在解析过程中定义。


Android.mk文件语法允许我们将Source打包成一个"modules". modules可以是:
静态库
动态库。


只有动态库可以被 install/copy到应用程序包(APK). 静态库则可以被链接入动态库。
可以在一个Android.mk中定义一个或多个modules. 也可以将同一份source 加进多个modules.


Build System帮我们处理了很多细节而不需要我们再关心。例如:你不需要在Android.mk中列出头文件和外部依赖文件。NDK Build System自动帮我们提供这些信息。这也意味着,当用户升级NDK后,你将可以受益于新的toolchain/platform而不必再去修改Android.mk.


1. 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为开始。它用于在开发tree中查找源文件。
宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。


include $(CLEAR_VARS) 
CLEAR_VARS 变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx.
例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等。但不清理LOCAL_PATH. 这个清理动作是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能避免相互影响。

LOCAL_MODULE    := hello-jni 
LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System会自动添加适当的前缀和后缀。例如,foo,要产生动态库,则生成libfoo.so.
但请注意:如果模块名被定为:libfoo.则生成libfoo.so. 不再加前缀。


LOCAL_SRC_FILES := hello-jni.c 
LOCAL_SRC_FILES变量必须包含将要打包如模块的C/C++ 源码。不必列出头文件,build System 会自动帮我们找出依赖文件。
缺省的C++源码的扩展名为.cpp. 也可以修改,通过LOCAL_CPP_EXTENSION。

include $(BUILD_SHARED_LIBRARY) 
BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。它负责收集自从上次调用 include $(CLEAR_VARS)  后的所有LOCAL_XXX信息。并决定编译为什么。

BUILD_STATIC_LIBRARY:编译为静态库。 BUILD_SHARED_LIBRARY :编译为动态库 BUILD_EXECUTABLE:编译为Native C可执行程序
2. NDK Build System变量:
NDK Build System 保留以下变量名:
以LOCAL_  为开头的
以PRIVATE_ ,NDK_ 或者APP_ 开头的名字。
小写字母名字:如my-dir


如果想要定义自己在Android.mk中使用的变量名,建议添加 MY_ 前缀。


2.1: NDK提供的变量:
此类GNU Make变量是NDK Build System在解析Android.mk之前就定义好了的。


2.1.1:CLEAR_VARS:指向一个编译脚本。必须在新模块前包含之。
include $(CLEAR_VARS)
2.1.2:BUILD_SHARED_LIBRARY:指向一个编译脚本,它收集自从上次调用 include $(CLEAR_VARS)  后的所有LOCAL_XXX信息。并决定如何将你列出的Source编译成一个动态库。 注意,在包含此文件前,至少应该包含:LOCAL_MODULE and LOCAL_SRC_FILES 例如:
include $(BUILD_SHARED_LIBRARY)
2.1.3:BUILD_STATIC_LIBRARY:与前面类似,它也指向一个编译脚本,收集自从上次调用 include $(CLEAR_VARS)  后的所有LOCAL_XXX信息。并决定如何将你列出的Source编译成一个静态库。
静态库不能够加入到Project 或者APK中。但它可以用来生成动态库。
LOCAL_STATIC_LIBRARIES and LOCAL_WHOLE_STATIC_LIBRARIES将描述之。
include $(BUILD_STATIC_LIBRARY)
2.1.4: BUILD_EXECUTABLE: 与前面类似,它也指向一个编译脚本,收集自从上次调用 include $(CLEAR_VARS)  后的所有LOCAL_XXX信息。并决定如何将你列出的Source编译成一个可执行Native程序。
include $(BUILD_EXECUTABLE)
2.1.5:PREBUILT_SHARED_LIBRARY:把这个共享库声明为 “一个” 独立的模块。
指向一个build 脚本,用来指定一个预先编译好多动态库。
与BUILD_SHARED_LIBRARY and BUILD_STATIC_LIBRARY不同,此时模块的LOCAL_SRC_FILES应该被指定为一个预先编译好的动态库,而非source file.
 LOCAL_PATH := $(call my-dir)
    
include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt     # 模块名
LOCAL_SRC_FILES := libfoo.so     # 模块的文件路径(相对于 LOCAL_PATH)
    
include $(PREBUILT_SHARED_LIBRARY) # 注意这里不是 BUILD_SHARED_LIBRARY


这个共享库将被拷贝到 $PROJECT/obj/local 和 $PROJECT/libs/(stripped) 
主要是用在将已经编译好的第三方库使用在本Android Project中。为什么不直接将其COPY到libs/armabi目录呢?因为这样做缺陷很多。下一节再详细说明。








2.1.6: PREBUILT_STATIC_LIBRARY: 预先编译的静态库。
同上。




2.1.7: TARGET_ARCH:
目标CPU架构名。如果为 “arm” 则声称ARM兼容的指令。 与CPU架构版本无关。




2.1.8: TARGET_PLATFORM: 
目标平台的名字。




2.1.9:TARGET_ARCH_ABI
Name of the target CPU+ABI
armeabi For ARMv5TE  armeabi-v7a
2.1.10:TARGET_ABI 2.2: NDK提供的功能宏: GNU Make 提供的功能宏,只有通过类似: $(call function)   的方式来得到其值,它将返回文本化的信息。 2.2.1: my-dir: $(call my-dir): 返回最近一次include的Makefile的路径。通常返回Android.mk所在的路径。它用来作为Android.mk的开头来定义LOCAL_PATH.
LOCAL_PATH := $(call my-dir)
请注意:返回的是最近一次include的Makefile的路径。所以在Include其它Makefile后,再调用$(call my-dir)会返回其它Android.mk 所在路径。 例如:
LOCAL_PATH := $(call my-dir)   ... declare one module   include $(LOCAL_PATH)/foo/Android.mk   LOCAL_PATH := $(call my-dir)   ... declare another module
则第二次返回的LOCAL_PATH 为:$PATH/foo。 而非$PATH. 2.2.2: all-subdir-makefiles: 返回一个列表,包含'my-dir'中所有子目录中的Android.mk。 例如: 结构如下:
sources/foo/Android.mk  sources/foo/lib1/Android.mk  sources/foo/lib2/Android.mk
在If sources/foo/Android.mk 中, include $(call all-subdir-makefiles) 那则自动include 了sources/foo/lib1/Android.mk and sources/foo/lib2/Android.mk。 2.2.3:this-makefile: 当前Makefile的路径。 2.2.4:parent-makefile: 返回include tree中父Makefile 路径。 也就是include 当前Makefile的Makefile Path。 2.2.5:import-module: 允许寻找并inport其它modules到本Android.mk中来。 它会从NDK_MODULE_PATH寻找指定的模块名。
$(call import-module,)
2.3: 模块描述变量: 此类变量用来给Build System描述模块信息。在'include $(CLEAR_VARS)' 和 'include $(BUILD_XXXXX)'之间。必须定义此类变量。
include $(CLEAR_VARS) script用来清空这些变量。 
include $(BUILD_XXXXX)收集和使用这些变量。 2.3.1: LOCAL_PATH:  这个值用来给定当前目录。必须在Android.mk的开是位置定义之。 例如:
LOCAL_PATH := $(call my-dir) LOCAL_PATH不会被include $(CLEAR_VARS) 清理。 2.3.2: LOCAL_MODULE:  modules名。在include $(BUILD_XXXXX)之前,必须定义这个变量。此变量必须唯一且不能有空格。 通常,由此变量名决定最终生成的目标文件名。 2.3.3: LOCAL_MODULE_FILENAME: 可选。用来override LOCAL_MODULE. 即允许用户重新定义最终生成的目标文件名。
LOCAL_MODULE := foo-version-1  LOCAL_MODULE_FILENAME := libfoo
2.3.4:LOCAL_SRC_FILES: 为Build Modules而提供的Source 文件列表。不需要列出依赖文件。 注意:文件相对于LOCAL_PATH存放,且可以提供相对路径。 例如:
LOCAL_SRC_FILES := foo.c              \ toto/bar.c
2.3.5: LOCAL_CPP_EXTENSION: 指出C++ 扩展名。(可选)
LOCAL_CPP_EXTENSION := .cxx 从NDK R7后,可以写多个:
LOCAL_CPP_EXTENSION := .cxx .cpp .cc 2.3.6:LOCAL_CPP_FEATURES: 可选。用来指定C++ features。
LOCAL_CPP_FEATURES := rtti
LOCAL_CPP_FEATURES := exceptions




2.3.7:LOCAL_C_INCLUDES: 一个可选的path列表。相对于NDK ROOT 目录。编译时,将会把这些目录附上。
LOCAL_C_INCLUDES := sources/foo  LOCAL_C_INCLUDES := $(LOCAL_PATH)/../foo
2.3.8: LOCAL_CFLAGS: 一个可选的设置,在编译C/C++ source 时添加如Flags。 用来附加编译选项。 注意:不要尝试在此处修改编译的优化选项和Debug等级。它会通过您Application.mk中的信息自动指定。 也可以指定include 目录通过:LOCAL_CFLAGS += -I。 这个方法比使用LOCAL_C_INCLUDES要好。因为这样也可以被ndk-debug使用。 2.3.9: LOCAL_CXXFLAGS: 
LOCAL_CPPFLAGS的别名。
2.3.10: LOCAL_CPPFLAGS:  C++ Source 编译时添加的C Flags。这些Flags将出现在LOCAL_CFLAGS flags 的后面。 2.3.11: LOCAL_STATIC_LIBRARIES: 要链接到本模块的静态库list。(built with BUILD_STATIC_LIBRARY) 2.3.12: LOCAL_SHARED_LIBRARIES:  要链接到本模块的动态库。 2.3.13:LOCAL_WHOLE_STATIC_LIBRARIES: 静态库全链接。 不同于LOCAL_STATIC_LIBRARIES,类似于使用--whole-archive2.3.14:LOCAL_LDLIBS:  linker flags。 可以用它来添加系统库。 如 -lz: 
 LOCAL_LDLIBS := -lz 2.3.15: LOCAL_ALLOW_UNDEFINED_SYMBOLS: 2.3.16: LOCAL_ARM_MODE:  缺省模式下,ARM目标代码被编译为thumb模式。每个指令16位。如果指定此变量为:arm。 则指令为32位。
LOCAL_ARM_MODE := arm 其实也可以指定某一个或者某几个文件的ARM指令模式。
2.3.17: LOCAL_ARM_NEON:  设置为true时,会讲浮点编译成neon指令。这会极大地加快浮点运算(前提是硬件支持) 只有targeting 为 'armeabi-v7a'时才可以。 2.3.18:LOCAL_DISABLE_NO_EXECUTE: 2.3.19: LOCAL_EXPORT_CFLAGS: 定义这个变量用来记录C/C++编译器标志集合,并且会被添加到其他任何以LOCAL_STATIC_LIBRARIES和LOCAL_SHARED_LIBRARIES的模块的LOCAL_CFLAGS定义中 
LOCAL_SRC_FILES := foo.c bar.c.arm
附1: Android.mk与jni目录的关系:
在某目录下,如/src/modules1/下存放Android.mk和Application.mk,调用ndk-build试图编译时,会遇到如下错误:
Android NDK: Could not find application project directory !    
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.    
/opt/android-ndk-r9d/build/core/build-local.mk:148: *** Android NDK: Aborting    .  Stop.
这是因为,当前Android.mk 未放置在jni目录内。所以ndk-build无法找到Android.mk. (ndk-build会从此目录向上一直找到jni目录,并从jni目录中找到Android.mk)且NDK_PROJECT_PATH, APP_BUILD_SCRIPT, NDK_APPLICATION_MK 是一个空的值。
如何解决这个问题呢。首先当然是创建jni目录,并在其中添加Android.mk和Application.mk.
但另一方面,也可以显性的指出这三个值:
目录结构如下:
/src/modules1/Android.mk
在此目录下:
/opt/android-ndk-r9d/ndk-build -B V=1 NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk

也可以正常编译。


三  Android.mk实例和NDK实用技巧

例1:JNI程序使用libhello-jni.so的符号。
libhello-jni.so由hello-jni.c组成。


hello-jni.c如下:

#include
#include
#include
#define  LOG_TAG    "libhello-jni"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

void Java_com_example_hellojni_HelloJni_functionA(JNIEnv* env, jobject thiz)
{
LOGE("SamInfo: Enter Native functionA");
return;
}


Android.mk如下:
LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)


../../../ndk-build -B V=1
可以正常编译,再使用Eclipse编译Android工程,可正常运行。


例2:JNI程序使用libhello-jni.so的符号。
libhello-jni.so由hello-jni.c, hell-jniB.c,头文件hello-jni.h组成。
 
hello-jni.h如下:
#ifndef _HELLO_JNI_H
#define _HELLO_JNI_H


#include
#include
#include


#define  LOG_TAG    "libhello-jni"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)


#endif


hello-jni.c如下:
#include "hello-jni.h"


void Java_com_example_hellojni_HelloJni_functionA(JNIEnv* env, jobject thiz)
{
LOGE("SamInfo: Enter Native functionA");
return;
}


hell-jniB.c如下:
#include "hello-jni.h"
void Java_com_example_hellojni_HelloJni_functionB(JNIEnv* env, jobject thiz)
{
LOGE("SamInfo: Enter Native functionB");
return;
}

Android.mk如下:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c hell-jniB.c


LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)


注意:LOCAL_SRC_FILES := hello-jni.c hell-jniB.c
此模块hello-jni由两个C文件组成,因为hello-jni.h只是依赖文件,所以不必加入。
又因为hello-jni.h在project/jni目录中,此目录本身为-I,所以也不用再Android.h中指出。

例3:JNI程序使用 libhello-jni.so的符号。 
libhello-jni.so依赖于libB.a.
libhello-jni.so由hello-jni.c, hell-jniB.c,头文件hello-jni.h组成。 
libB.a由libstatic/B1.c,libstatic/B2.c头文件libstatic/B.h组成。


B.h 如下:
#ifndef _B_H
#define _B_H


#include
#include
#include
int iFunctionB1();
int iFunctionB2();
#define  LOG_TAG    "libStatic"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)


#endif


B1.c:
#include "B.h"
int iFunctionB1()
{
LOGI("SamInfo: Enter static function iFunctionB1()");
return 0;
}


B2.c
#include "B.h"
int iFunctionB2()
{
LOGI("SamInfo: Enter static function iFunctionB2()");
return 0;
}

hello-jni.h:
#ifndef _HELLO_JNI_H
#define _HELLO_JNI_H
#include
#include
#include
#include "libstatic/B.h"
#define  LOG_TAG    "libhello-jni"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#endif
hello-jni.c:
#include "hello-jni.h" 


void Java_com_example_hellojni_HelloJni_functionA(JNIEnv* env, jobject thiz)
{
LOGE("SamInfo: Enter Native functionA");
iFunctionB1();
return;
}




hell-jniB.c:
#include "hello-jni.h"




void Java_com_example_hellojni_HelloJni_functionB(JNIEnv* env, jobject thiz)
{
LOGE("SamInfo: Enter Native functionB");
iFunctionB2();
return;
}

Android.mk如下:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)


LOCAL_MODULE    := hello-B
LOCAL_SRC_FILES := libstatic/B1.c libstatic/B2.c


include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)


LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c hell-jniB.c
LOCAL_STATIC_LIBRARIES := hello-B
LOCAL_LDLIBS := -llog


include $(BUILD_SHARED_LIBRARY)


这就是典型的一个Android.mk中包含2个Modules的例子。其中一个是产生静态库,另一个产生动态库。
动态库依赖于静态库。(一定要添加 LOCAL_STATIC_LIBRARIES := hello-B ,否则不生成静态库)

例4:JNI程序使用libA.so(由A1.c,A2,c,A.h组成)
libA.so依赖于libB.so(由B1.c,B2.c,B.h组成)


情况分析:
因为libB.so被libA.so使用。所以肯定要加入:
LOCAL_LDLIBS := -L$(LOCAL_PATH)/../libs/armeabi/   -lhello-B


但请注意:此时libA.so中所用到的libB.so 的符号只是一个空穴。并为将实现加进来。


而如果使用:
LOCAL_SHARED_LIBRARIES := hello-B
也仅仅是将libhello-B.so 添加进编译选项。并未将符号添加进去。


在Linux下,此类情况可以将动态库放置于某个目录下,然后使用export LD_LIBRARY_PATH 来解决。但Android下并无此方法。导致运行时会找不到libhello-B.so中的符号。




针对此类情况,有两种方法,但都不是特别好用。分别描述如下:


方法1.
将libhello-B.so放置于/system/lib下。
且Android.mk如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)


LOCAL_MODULE    := hello-B
LOCAL_SRC_FILES := libstatic/B1.c libstatic/B2.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)


LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c hell-jniB.c
#LOCAL_SHARED_LIBRARIES := hello-B
LOCAL_LDLIBS := -llog  -L$(LOCAL_PATH)/../libs/armeabi/   -lhello-B


include $(BUILD_SHARED_LIBRARY) 


此Android.mk有两个模块,分别产生libhello-B.so, libhello-jni.so. 后者依赖于前者。


而因为Java层程序使用:System.loadLibrary("hello-jni");
则libhello-jni.so的符号加入到Java应用程序中。而它使用到的libhello-B.so,则能够在/system/lib下找到。




方法2. 将libhello-B.so也添加如Java程序中。且放置于hello-jni之前。
System.loadLibrary("hello-B");
System.loadLibrary("hello-jni");


方法3:
可以使用dlopen()方式调用。
 据说使用-rpath可以指定应用程序查找库的目录。但Sam觉得理论上并不可能(因为Java无法指定Wl,-rpath).也没有尝试出来。


例5:JNI程序使用libA.so(由A1.c A2.c, A.h组成)
libA.so依赖于libB.so(由B1.c, B2.c, B.h组成)
libB.so依赖于libC.so(由c.c组成)和libD.a(由d.c组成)
libC.so依赖于libD.a (由d.c组成) 
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)


LOCAL_MODULE    := hello-B
LOCAL_SRC_FILES := libstatic/B1.c libstatic/B2.c
LOCAL_STATIC_LIBRARIES := D
LOCAL_LDLIBS := -llog -lC -L$(LOCAL_PATH)/../libs/armeabi/
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)


LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c hell-jniB.c
#LOCAL_SHARED_LIBRARIES := hello-B
LOCAL_LDLIBS := -llog  -L$(LOCAL_PATH)/../libs/armeabi/   -lhello-B
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := C
LOCAL_SRC_FILES := c.c
LOCAL_LDLIBS := -llog
LOCAL_STATIC_LIBRARIES := D
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE    := D
LOCAL_SRC_FILES := d.c
LOCAL_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)

请注意:如此写法:则libD.a中的符号分别被:libC.so, libB.so两次引用。

例6: JNI程序使用libA.so(由A1.c A2.c, A.h组成) 
并引用外部生成的:libE.so, libF.a.


Sam:经常发生这样的情况,某NDK工程A生成一个动态库,供另一个NDK工程B使用。我们常把此动态库放到B工程的lib/armeabi下。 但ndk-build时,它会首先删除lib/armeabi下所有.so文件。
所以可以使用 PREBUILT_SHARED_LIBRARY 
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := hello-B
LOCAL_SRC_FILES := libstatic/B1.c libstatic/B2.c
LOCAL_SHARED_LIBRARIES := prelib
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)


include $(CLEAR_VARS)


LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c hell-jniB.c
#LOCAL_SHARED_LIBRARIES := hello-B
LOCAL_LDLIBS := -llog  -L$(LOCAL_PATH)/../libs/armeabi/   -lhello-B
include $(BUILD_SHARED_LIBRARY)


include $(CLEAR_VARS)
LOCAL_MODULE := prelib
LOCAL_SRC_FILES := libE.so


include $(PREBUILT_SHARED_LIBRARY)




注意:当 PREBUILT_SHARED_LIBRARY 时,LOCAL_SRC_FILES不再是.c文件,而是动态库。它可以放置在jni下。编译时,它会自动被copy到lib/armaebi下。
请注意模块名。它将被用作LOCAL_SHARED_LIBRARIES 


注1:
NDK实用技巧:
1. 显示NDK Build过程中所有编译选项和动作:
../../ndk-build V=1
这样就可以看到编译时所用编译选项是否我们期望使用的。


2.重新编译:
../../ndk-build -B
或者:
../../ndk-build clean
../../ndk-build


第三方库中编译方法: 
Sam常需要NDK编译第三方库,但又不想破坏其原有目录结构.所以通常在src目录中,添加一个目录叫:jni.
在其中添加Android.mk,  Application.mk文件。 Source Code 还是继续使用原有src目录下的源码文件。


在Android.mk中,只需要添加上一级目录的所有.c或者.cpp文件即可。
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/..//*.c)


四  Application.mk简介 



Application.mk简介:


0. 基础知识介绍:
Application.mk用来描述应用程序需要哪些modules。Application.mk通常放在:$PROJECT/jni/Application.mk。




2. Application.mk变量:
Application.mk是GNU Makefile的一个小片段。它可以定义以下变量。




APP_PROJECT_PATH:
以绝对路径指向Project root目录。


APP_MODULES:
可选项,如果没有定义,则NDK编译所有Android.mk中的modules.
如果定义了,则只编译Android.mk中被APP_MODULES指定的模块以及他们所依赖的模块。


APP_OPTIM:
可选项。设置为:'release' or 'debug'。ndk build system根据此项决定优化等级。


release是缺省模式,会使用高等级优化。 debug则使用低等级优化项目。
但请注意:如果AndroidManifest.xml中设置了android:debuggable="true"。则缺省设置为debug.






APP_CFLAGS: 
C/C++ CFLAGS.这个CFLAGS将取代Android.mk中module中指定的。






APP_CPPFLAGS:
只对C++代码有效的CPPFLAGS




APP_BUILD_SCRIPT:
通常情况下,NDK Build System会查找Android.mk从$(APP_PROJECT_PATH)/jni/
即:$(APP_PROJECT_PATH)/jni/Android.mk
如果想改变这个行为,则修改:APP_BUILD_SCRIPT.




APP_ABI:
缺省情况下,NDK build system会产生'armeabi' ABI。它为ARMv5TE指令集,并使用软浮点。
可以通过修改这个变量来改变。


Sam机的armeabi可以在ARMv5TE和ARMv7-a 上都可以使用。
如果想使用硬浮点。FPU。


APP_ABI := armeabi-v7a

或者两个都支持:
APP_ABI := armeabi armeabi-v7a

APP_STL:
缺省情况下,NDK Build System 提供最小的C++ 支持(/system/lib/libstdc++.so).


可以修改如下:


APP_STL := stlport_static --> static STLport library 
APP_STL := stlport_shared --> shared STLport library 
APP_STL := system --> default C++ runtime library


APP_CXXFLAGS:
APP_CFLAGS的别名。 

APP_PLATFORM:
这个条目很重要,因为Sam经常要编译一些NDK下可执行程序,但发现Include却直接指向Android-3下面。
-I/opt/Android-NDK/android-ndk-r8b/platforms/android-3/arch-arm/usr/include


觉得很奇怪。怎么都修改不过来,一直觉得是Android ndk-build的Bug。 
后来无意中发现使用这个选项,就可以正常修改。
APP_PLATFORM = android-8


五 C++ 支持 

0.基础知识:
Android Platform提供一个非常小的C++ runtime 支持库(/system/lib/libstdc++)和头文件。
但此System C++库支持非常有限,不支持以下 :
  - Standard C++ Library support (except a few trivial headers). 
  - C++ exceptions support 
  - RTTI support


但Android也提供几个其它的选择。可以通过Application.mk中APP_STL 作设置(见Android NDK学习 <四> Application.mk简介)。
设置选项有如下选择:


system -> Use the default minimal system C++ runtime library. 
gabi++_static -> Use the GAbi++ runtime as a static library. 
gabi++_shared -> Use the GAbi++ runtime as a shared library. 
stlport_static -> Use the STLport runtime as a static library. 
stlport_shared -> Use the STLport runtime as a shared library. 
gnustl_static -> Use the GNU STL as a static library. 
gnustl_shared -> Use the GNU STL as a shared library.




当APP_STL没有设置时,则缺省使用system的这个。


以上几种C++库能力集对:C++  Exceptions  , C++  RTTI  ,Standard Library支持分别如下:


system no no no 
gabi++ no yes no 
stlport no yes yes 
gnustl yes yes yes
刚才有网友提示stlport支持RTTI和异常,要说明一下,NDK是不断更新的。所以在R7C时代,Google移植的STLport并不支持异常(刚查了R8也还不支持异常,但在NDK R10中,stlport已经支持异常了)
同样,gabi++, 在R10版本内也已经支持RTTI和异常了。
谢谢网友(ttbtr)提示。






1. 各Runtime简介:
1.1:System Runtime:
Android提供的C++ Runtime,它只提供几个非常少的C++ 标准头文件。如果使用它,则应用程序二进制自动的链接此Android系统libstdc++。


提供的头文件只有以下这些:
cassert cctype cerrno cfloat climits cmath csetjmp csignal cstddef cstdint cstdio cstdlib cstring ctime cwchar new stl_pair.h typeinfo utility


不支持:std::string or std::vector.


1.2:GAbi++ runtime:
这是另一个小的C++ runtime, 与System C++ 提供同样多的头文件。但它支持RTTI。 现在已经很少用了。




1.3: STLport runtime:
STLport(http://www.stlport.org)的Android 移植版。提供完整的C++ 标准库头文件,支持RTTI,但不支持EXCEPTIONS.(不支持异常很麻烦,就需要改不少代码)


静态动态均支持:
APP_STL := stlport_shared 
APP_STL := stlport_static




1.4:GNU STL runtime:
GNU 标准的C++ library. 支持更多特性。库名叫:libgnustl_shared.so,而不是其它平台通常的:libstdC++.so






2.其它事项:
2.1: C++ Exceptions:
自从NDK r5,NDK Toolchain就支持异常,可是,所有C++ 代码都缺省使用-fno-exceptions编译,为的是向前兼容。


为了Enable C++ Exceptions,可以作如下动作:
在Android.mk中:
LOCAL_CPP_FEATURES += exceptions   (推荐)
或者:
LOCAL_CPPFLAGS += -fexceptions


或者在Application.mk中:
APP_CPPFLAGS += -fexceptions




2.2:RTTI support:
与异常类似,自动NDK r5, NDK ToolChain就支持RTTI,但在缺省情况下都是用-fno-rtti来编译C++代码。


如果想Enable RTTI; 
在Andriod.mk中:
LOCAL_CPP_FEATURES += rtti (推荐)
或者
LOCAL_CPPFLAGS += -frtti
或者在:Application.mk: 
APP_CPPFLAGS += -frtti




2.3: Static runtimes:
当工程只有一个动态库用到C++ library. 则其使用静态C++库没有问题。
但当工程中有多个动态库用到C++ library(或其它静态库)时,则问题来了,每个动态库会包含静态库进去。这就有问题了,因为在内存中,就有多份COPY,那全局变量等都会有问题。


所以,当工程中多个动态库链接C++ library时,不要使用静态C++库方式。




2.4: Shared runtimes:
在使用动态库时,则需要确保C++ 动态库在其它库之前被Load到内存。


例如:
libfoo.so 
libbar.so which is used by libfoo.so 
libstlport_shared.so, used by both libfoo and libbar


则:
static 

 System.loadLibrary("gnustl_shared"); 
 System.loadLibrary("bar"); 
 System.loadLibrary("foo"); 
 }


有一点需要注意:
因为C++ 程序调用xxx-xxx-g++编译,所以使用C++命名规范命名 符号,这样,Java JNI程序就找不到对应符号了。
所以需要添加:


#ifdef   __cplusplus
extern   "C"{
#endif




function 声明。
#ifdef   __cplusplus
}
#endif




3. 对std::wstring支持:
在NDK R7中,如果不做处理,则std::wstring会报未定义。
于是Sam查找之:
在:android-ndk-r7/sources/cxx-stl/gnu-libstdc++/include/bits/stringfwd.h


#ifdef _GLIBCXX_USE_WCHAR_T
  template<> struct char_traits;


  typedef basic_string wstring;
#endif


所以,如果想要支持wstring,则需要定义 _GLIBCXX_USE_WCHAR_T
于是:在Android.mk中,
在LOCAL_CXXFLAGS,LOCAL_CFLAGS中添加:
-D_GLIBCXX_USE_WCHAR_T
即可支持wstring. 


更新:Sam发现,wstring在R7C版本中,编译虽然没有问题,但链接时会报错。


只有替换了NDK R8之后,才真正没有问题了。

Android C++库支持有些不妥之处,例如: 
在多Thread中使用cout,或者cout 与printf混和使用,会造成程序卡死。


 六 复杂结构动态库处理和第三方库的移植

 0. 动态库加载方式:
方法1:在运行时动态链接库, 动态地将程序和共享库链接并让 Linux 在执行时加载库(动态链接,系统加载)
方法2:动态加载库并在程序控制之下使用它们。(动态加载)


动态链接(系统加载):是指在编译应用程序时,使用 -lxxx 来指定需要链接哪个库。此时,应用程序(ELF)中会指明哪些符号未被填充,且放在哪些动态库中。 LD_LIBRARY_PATH则指明动态库在哪里存放。系统会将此动态库加载到内存中使用。


动态加载:使用dlopen打开指定的动态库。并使用 dlsym 获取符号地址。这是插件式程序的必备方式。


1. C++大型程序常见的模块组织方式:


不少C++大型程序以如下方式设计:
1. 有一个主程序块,此程序块通常作用是读取配置文件,并根据配置文件动态加载以插件形式提供的功能模块。(即dlopen(插件)) 并维护主循环,每次循环中依次调用每个插件中的某功能(dlsym(符号)并运行)。
在退出时,使用dlclose() 将各插件移出内存并退出主循环。


2. 不同插件同样利用配置文件维护他们所用到的自有模块。(dlopen, dlsym)


3. 如有必要,各插件以动态库的形式共同使用某模块(尤其是第三方模块)。 (使用动态链接,系统加载的方式)

例如:
某程序以如下方式构建:
1. 核心程序为一个应用程序,此应用程序主要通过分析配置文件,决定加载哪些插件。同时,它维护一个循环,每个循环中调用插件的特定符号(update)。


2. 插件包括:音频库,文件系统,动作分析系统等。同时,各个游戏逻辑也作为插件存在。即从大厅进入游戏,其实就是替换一些插件。


3. 游戏模块依赖于引擎A。(以动态链接,系统加载方式使用)


这样,在Linux下,可以将引擎以及底层库放置于LD_LIBRARY_PATH制定位置。整个软件可正常运行。

2. Android 下对NDK动态库的使用:
Android下,对于动态加载的库(dlopen(lib)),使用绝对路径就可以解决问题。
对于动态链接(系统加载)的动态库,因为没有LD_LIBRARY_PATH方式,则可以采用在Activity的静态模块中使用System.LoadLibrary()方式提前加载于内存空间。


 static
{
    System.loadLibrary("gnustl_shared"); 
    System.loadLibrary("testcplus");    
        System.loadLibrary("hello-jni");
        
}

3. 复杂情况下NDK动态库的组织:
使用上面所说的方法,可以解决非常简单的动态加载的动态库。但对于比较复杂的动态库。则有很多问题。
例如:某些动态库有很多全局变量,且包含一些状态信息。
而Activity在onDestory()后,进程并未完全退出。此时,动态库依然存在于内存之中。此时,再次运行应用程序。全局变量和状态还是使用之前的值。这会对逻辑造成很大困难。

于是采用如下方式:
首先:Java程序使用System.LoadLibrary()仅调用C++库(libgnustl_shared.so)以及一个非常干净简单的动态库A。


此动态库A,提供2个主要功能。Init(), UnInit().
在Init()中,它作两件事:
1. 采用dlopen()方式将所有动态链接(系统加载)的库(如上面所说引擎)加入内存(关键点)
2. 采用dlopen()方式将需要dlopen() 的模块(如游戏逻辑)加入。
在UnInit()中。依次dlclose();

其次,动态库A所dlopen()的游戏逻辑动态库,直接使用函数名而非dlopen,dlsym的方式使用符号。这样,Linux下的代码,就不需要做任何修改即可。


在NDK 编译游戏逻辑时,指明LOCAL_SHARED_LIBRARIES := 引擎。
也就是说:System.LoadLibrary(A),将最简单的壳子动态库加入内存。
此壳子动态库A在onCreate()时调用dlopen将所有所需动态库加入内存(引擎,游戏)。
在onDestory()时,则dlclose(),将动态库移出内存。
而在循环中,游戏动态库被dlopen,dlsym取出相应几个有限的符号并调用。
而在这些符号内,他们直接使用引擎提供的函数。因为此时引擎已经被加载入内存。所以可以使用。




dlclose时,游戏和引擎都被移出内存。
(注:有很多游戏,但引擎只有一个,游戏使用此共同的引擎提供的函数)

第三方C++库的移植:
在开发Android大型程序时,不可避免要用到很多第三方OpenSouce.而大多数第三方库采用AutoConfig,或者CMake等特定编译工具编译。现在就说说如何利用NDK将其编译成咱们能够使用的静态库或者动态库。


现在以xiph-ogg为例,演示如何使用NDK编译这些第三方库。


0. 准备工作:
下载xiph source code.
要编译的内容是:
xiph/ogg:生成libogg.so或者libogg.a
xiph/vorbis: 生成libvorbisfile.so  libvorbis.so 或者对应.a .


1. 确定库所包含的源文件:
编译libogg.so,需要编译的源文件包括:
bitwise.c framing.c

2.组织文件和写Android.mk+Application.mk
Sam作如下目录结构:
xiph/ogg/jni/
这里包含4个文件: bitwise.c framing.c, Android.mk, Application.mk


作Android.mk如下:注意需要填写的资源文件(两个.c)以及指定-I目录。
LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := ogg
LOCAL_SRC_FILES := bitwise.c framing.c
LOCAL_CXXFLAGS :=
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/ogg/include
#LOCAL_SHARED_LIBRARIES :=                                                                                                          
LOCAL_LDLIBS := -llog
#include $(BUILD_EXECUTABLE)                                                                                                        
include $(BUILD_SHARED_LIBRARY)
#include $(BUILD_STATIC_LIBRARY) 


编译之:
../../../../../ndk-build -B V=1
在xiph/ogg/libs/armeabi-v7a/下生成libogg.so






libvorbisfile.so  libvorbis.so的建立也一样,Android.mk如下:
LOCAL_PATH := $(call my-dir)


include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := vorbis
LOCAL_SRC_FILES := mdct.c smallft.c block.c envelope.c window.c lsp.c lpc.c  analysis.c synthesis.c psy.c info.c floor1.c floor0.c \
 res0.c mapping0.c registry.c codebook.c sharedbook.c lookup.c bitrate.c
LOCAL_CXXFLAGS :=
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/ogg/include
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/vorbis/include
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/vorbis/lib


LOCAL_LDLIBS := -lm
LOCAL_LDLIBS += -logg


#LOCAL_SHARED_LIBRARIES :=                                                                                                          
LOCAL_LDLIBS += -llog
LOCAL_LDLIBS += -L/opt/Android-NDK/android-ndk-r8b/samples/xiph/NDK_Build/ogg/libs/armeabi-v7a
#include $(BUILD_EXECUTABLE)                                                                                                        
include $(BUILD_SHARED_LIBRARY)
#include $(BUILD_STATIC_LIBRARY)                                                                                                    


include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_MODULE := vorbisfile
LOCAL_SRC_FILES := vorbisfile.c
LOCAL_CXXFLAGS :=
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/ogg/include
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/vorbis/include
LOCAL_CFLAGS += -I/opt/Android-NDK/android-ndk-r8b/samples/xiph/xiph/vorbis/lib


LOCAL_LDLIBS := -lm
LOCAL_LDLIBS += -logg
LOCAL_LDLIBS += -lvorbis


#LOCAL_SHARED_LIBRARIES :=                                                                                                         \
                                                                                                                                    
LOCAL_LDLIBS += -llog
LOCAL_LDLIBS += -L/opt/Android-NDK/android-ndk-r8b/samples/xiph/NDK_Build/ogg/libs/armeabi-v7a
LOCAL_LDLIBS += -L/opt/Android-NDK/android-ndk-r8b/samples/xiph/NDK_Build/vorbis/libs/armeabi-v7a
#include $(BUILD_EXECUTABLE)  

0 0
原创粉丝点击