转自:http://crash.163.com/#news/!newsId=24

在Android NDK开发中,有两个重要的文件:Android.mk和Application.mk,各尽其责,指导编译器如何编译程序,并决定编译结果是什么。本文将详细说明几个常见的NDK选项的配置,帮助大家理解相应的配置选项。


     一、Application.mk


       Application.mk实际上是轻量级Makefile,通常在$PROJECT/jni目录下,用于配置所有modules的编译变量,例子如下:

APP_ABI := armeabi arm64-v8a x86_64 x86 armeabi-v7a
NDK_TOOLCHAIN_VERSION := clang3.5
APP_STL := stlport_static
APP_OPTIM:= debuge


      1、APP_ABI(目标平台ABI类型)

       NDK编译中,APP_ABI默认选择armeabi ABI,可通过设置APP_ABI设置一个或者多个ABI,表一为不同的APP_ABI所对应的指令集。  

Instrunction set Value
ARMv5TE based CPU APP_ABI := armeabi
ARMv7 based CPU APP_ABI := armeabi-v7a
ARMv8 AArch64 APP_ABI := arm64-v8a
IA-32 APP_ABI := x86
Intel64 APP_ABI := x86_64
MIPS32 APP_ABI := mips
MIPS64(r6) APP_ABI := mips64
All supported instruction sets APP_ABI := all

表一:ABI类型

       在开发时可根据需求选择APP_ABI,对于ABI的选择需要考虑到效率和APK大小。由于armeabi-v7a指令集兼容armeabi;市面上的x86手机为了兼容性,基本都使用libhoudini模块,兼容arm指令集;64位机型默认支持32位abi的so,因此在对大小要求比较高的情况下,可以只选择市面上设备基本兼容的armeabi ABI,如果对性能有些许要求,可以再添加x86 ABI。

      2、 NDK_TOOLCHAIN_VERSION(编译器类型、版本)

       默认采用的是GCC编译器,对于GCC版本的选择与NDK版本有关系,本人使用的是NDK R12,在64位ABI默认是GCC 4.9,32位ABI默认是GCC 4.8,当然也可以像上面例子中给出的设置一样,设置clang编译器。

      3、 APP_STL(运行库类型)

       Android NDK 默认使用的是最小支持的C++运行库,如果你需要你的NDK程序中使用STL,则可以设置APP_STL := stlport_static,APP_STL有表二中的几种取值。

Name Explanation
system(default) 系统默认的C++运行库
stlport_static 以静态链接方式使用的sttport版本的STL
stlport_shared 以动态链接方式使用的sttport版本的STL
gnustl_static 以静态链接方式使用的gnustl版本的STL
gnustl_shared 以动态链接方式使用的gnustl版本的STL
gabi++_static 以静态链接方式使用的gabi++
gabi++_shared 以动态链接方式使用的gabi++
c++_static 以静态链接方式使用的LLVM libc++
c++_shared 以动态链接方式使用的LLVM libc++

表二:NDK运行库

       若APK中有多个SO文件用到STL,建议都使用动态方式链接STL,这样可以减小整个APK文件大小。
另外需要注意的是官方提供的NDK运行库除了默认的以外都支持RTTI和异常,然而默认是禁用的,将在下面的Android.mk中说明如何开启。

      4、APP_OPTIM(编译模式)

       “release”模式为默认的,生成的是优化后的二进制;也可以设置为“debug”模式,“debug”模式生成的是未优化二进制,提供很多BUG信息,便于调试和分析。
还有其他配置选项,有兴趣可以查看Application.mk官方文档。
 

     二、Android.mk


       Android.mk也是一个轻量级的Makefile,其将C/C++源码组织到一个个module中,module可以是静态库、共享库或者独立的可执行文件, 一个Android.mk文件可以有一个,也可以是多个module,modules之间也可以有依赖关系。

      1、基本概念

       Android.mk中包括NDK提供的宏、变量以及模块描述变量,这些宏、变量以及变量的赋值共同组成了Android.mk文件,其在NDK编译中各尽其责,指导着NDK的编译。
      宏:包括my-dir、all-subdir-makefiles等,通过‘$(call )’来调用,返回文本信息。
      变量:包括CLEAR_VARS、BUILD_SHARED_LIBRARY、TARGET_ARCH等,由NDK编译系统提供,并且在Android.mk文件被解析前就已经存在。Android.mk文件有可能被多次解析,因此每次解析时这些变量的值都有可能不同。
      模块描述变量:Module-description,包括LOCAL_PATH、LOCAL_MODULE、LOCAL_SRC_FILES等LOCAL_前缀变量,这些变量除LOCAL_PATH外,均填写在语句include $(CLEAR_VARS)和include $(BUILD_XXX)之间。
其他Android.mk配置可以查看Android.mk官方文档。

      2、基础

      在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(当前目录) 
       LOCAL_PATH为模块描述变量,一个Android.mk必须定义LOCAL_PATH,用于定位源文件,在本例中,使用的是编译系统提供的宏“my-dir”(“my-dir”返回最近一次包括Makefile文件路径,通常为当前Android.mk所在目录),用于返回当前目录。
此变量不会被CLEAR_VARS清除,所以每个Android.mk文件只需要定义一次就可以了。

       CLEAR_VARS(变量清除)
       CLEAR_VARS变量由编译系统提供,顾名思义,作用是清除模块变量(在include $(CLEAR_VARS)和include $(BUILD_XXX)之间的LOCAL_XXX模块变量),当然LOCAL_PATH除外。由于所有的编译控制文件都是单一的GNU Make可执行上下文环境中解析,而这个上下文环境中所有的变量都是全局的,所以编译module前需要清理相应变量。

       LOCAL_MODULE(module名称)
       LOCAL_MODULE是Android.mk文件中module的唯一标识,这个名字必须是唯一的,且中间不能有空格。在默认情况下,它决定了生成的文件名,如“hello-jni”对应的动态库名称为libhello-jni.so,然而要索引它时,需要“hello-jni”即可,也可以通过变量LOCAL_MODULE_FILENAME来覆盖这个默认名称。

       LOCAL_SRC_FILES (源码文件)
       LOCAL_SRC_FILES 变量包括C/C++源文件列表,这些源文件会被编译到一个module中,不过也不必列出头文件和包括文件,编译系统会自动为你找打所有需要的依赖关系。值得注意的是linux下路径使用顺斜杠(/)。

       BUILD_SHARED_LIBRARY(动态库编译)
       BUILD_SHARED_LIBRARY是编译器提供的变量,表示编译成动态库,它指向一个GNU Makefile脚本,这个脚本收集从include $(CLEAR_VARS)后所有的LOCAL_XXX变量中定义的所有信息,决定编译什么以及怎么编译。
还有BUILD_STATIC_LIBRARY,和BUILD_SHARED_LIBRARY类似,表示编译成静态库,静态库不会被拷贝到APK中。

       PREBUILT_SHARED_LIBRARY(预编译)
       指向一个编译脚本,用来指定一个预编译动态库.使用此变量时,不像BUILD_SHARED_LIBRARY和BUILD_STATIC_LIBRARY那样,LOCAL_SRC_FILES的值必须是只能有一个指向预编译动态库的路径,如foo/libfoo.so,而不是源文件。如下栗子。

include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libtest.so
include $(PREBUILT_SHARED_LIBRARY)


PREBUILD_STATIC_LIBRARY和PREBUILD_SHARED_LIBRARY一样,只不过是用于引用静态库。

      TARGET_ARCH_ABI(目标ABI名称)
      如表一所示,目标ABI名称。若定义了多个ABI,则每次解析Android.mk时,值都不一样,主要使用场景为根本不同的ABI定义不同的文件等。

      3、其他模块变量LOCAL_LDLIBS(链接库)

      用于额外链接选项,所有的库都有“-l”前缀。可同时列出多个库,用空格隔开,例如:

LOCAL_LDLIBS := -llog -ldl

       
      Android NDK默认链接了多个库,不需要显示的添加到LOCAL_LDLIBS中,包括 the standard C libraries,the standard C++ libraries,real-time extensions和 pthread库。同时也提供了一些需要显示添加的库,这些库版本有关系,如表三所示。

Android level Lib Explanation
 
Android-3
-llog Android Log
-lz Zlib Compression Library
-ldl Dynamic Linker Library
Android-4 -lGLESv1_CM OpenGL ES 1.x Library
Android-5 -lGLESv2 OpenGL ES 2.0 Library
Android-8 -ljnigraphics The jnigraphics Library
 
Android-9
-lEGL The EGL graphics library
-lOpenSLES Open ES native audio Library
-landroid Natice Android API
Android-14 -lOpenMAXAL OpenMAX AL natice multimedia Library
Android-18 -lGLESv3 OpenGL ES 3.0 Library
Android-21 -lGLESv3 OpenGL ES 3.1 Library

表三:链接库

        LOCAL_CFLAGS、LOCAL_CPPFLAGS和LOCAL_LDFLAGS(编译、链接标志)

        LOCAL_CFLAGS定义的是在编译C/C++时,传递给编译器的标志集合,LOCAL_CPPFLAGS只支持C++,作用也是传递给编译器一些信息,LOCAL_LDFLAGS是指传递给连接器一些额外的参数。

在NDK开发中难免会用到这些标志位,特别是在优化编译时,下面的是本人在开发中遇到的编译选项。

       ① LOCAL_CPPFLAGS += -fexceptions
       由于NDK编译从R5开始才支持C++异常控制,为了通用性,异常处理默认是禁用的(-fno-exceptions),因此需要在指定module中添加LOCAL_CPPFLAGS += -fexceptions编译选项方可编译带异常处理的C++代码。也可以直接在Application.mk中配置APP_CPPFLAGS += -fexceptions。

       ② LOCAL_CPPFLAGS += -frtti
       从NDK R5开始,NDK也开始支持C++ RTTI了,但为了通用性,所有的C++源文件被构建的时候默认是不支持RTTI的(-fno-rtti),可以通过在Android.mk中添加:LOCAL_CPPFLAGS += -frtti或者在Application.mk添加APP_CPPFLAGS += -frtti来开启RTTI。

       ③ LOCAL_CFLAGS += -fvisibility=hidden
       在NDK开发中,源文件的函数都有一个默认的visibility属性为public,编译生成的so文件中几乎所有的函数名、全局变量名均被导出,其实只需要导出java_com开头的jni函数即可,其他函数不需要暴露出来,在Android.mk中设置LOCAL_CFLAGS += -fvisibility=hidden,就可以隐藏不需要导出的函数,若某个函数需要导出,则添加JNIEXPORT或者__attribute__ ((visibility ("default")))即可。
除了安全,不导出不必要的函数外,还能减小so体积。

       ④ LOCAL_CFLAGS += -ffunction-sections
       不添加此参数时,编译文件.o中代码部分只有.text段,使用此参数,会使每个函数单独有一个段,举个栗子,函数func1()会编译成.text.func1段,虽然段多了,但对链接后代码大小并没有影响。

       ⑤ LOCAL_CFLAGS += -fdata-sections
       同上,每个data都有一个单独的段。

       ⑥ LOCAL_LDFLAGS += -Wl --gc-sections
       -Wl,

更多相关文章

  1. Android 通过命令将文件导出
  2. Android开发8――利用pull解析器读写XML文件
  3. Android 生成android的keystore文件
  4. Android 使用webview 打开浏览ppt文件
  5. 有关android中加载MP4,3gp等视频文件,无法播放问题的解决
  6. Android的文件监控系统不可靠
  7. Android Studio 使用API29无法向手机存储写文件问题

随机推荐

  1. android 获取实际view 宽度高度
  2. 关于Android的文字排版和换行问题,彻底解
  3. Android(安卓)Loader的使用以及手机通讯
  4. Android Post Get 示例
  5. PopupWindow的使用
  6. 预防手机变砖头 Android刷机问题汇总
  7. From Zero to Boot: Porting Android to
  8. android调用系统邮件组件(intent匹配的流
  9. Android异常总结---W/System.err(661): j
  10. Android 拦截短信配置