谈谈 Android NDK 编译选项设置 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
playgamele
V2EX    Android

谈谈 Android NDK 编译选项设置

  •  2
     
  •   playgamele 2016-08-16 17:27:04 +08:00 29176 次点击
    这是一个创建于 3349 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    V2EX格式编辑起来比较累,大家可以查看原文链接 http://crash.163.com/#news/!newsId=24

     一、 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 <function>)’来调用,返回文本信息。 变量:包括 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,<option>选项是告诉编译器,将后面选项<option>传递给连接器,-Wl,--gc-sections 的意思是使用连接器 ld 链接时删除不用的段。若使用 LOCAL_CFLAGS += -ffunction-sections -fdata-sections ,则代码和数据均被分割成不同的段,若某个函数或数据未被任何函数调用,则 ld 不会链接未被调用的函数,从而减小 so 文件体积,达到优化 so 的目的。 ⑦ LOCAL_LDFLAGS += -fPIC PIC(position independent code)用于编译位置无关代码,生成可用于共享库的位置独立代码。若不添加-fPIC ,则加载.so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段内容,这样就导致没使用这个.so ,代码段的进程在内核中就会生成这个文件的拷贝。 ⑧ LOCAL_LDFLAGS += -Wall 这个的意思是 wring all 意思在编译和链接过程中显示所有警告信息。 ⑨其他 若需要了解其他编译标志,可以查看 GCC Command Options 文档。 
    8 条回复    2017-09-08 10:17:27 +08:00
    allenx
        1
    allenx  
       2016-08-16 18:18:20 +08:00
    不错的 hello world
    psklf
        2
    psklf  
       2016-08-16 19:34:02 +08:00
    写的不错,支持,我的建议:

    1. 贴文章的时候稍微调一下格式, md 不难吧。
    2. 希望后续能介绍使用 Android Studio gradle 编译运行 ndk 的方法。
    FrankHB
        3
    FrankHB  
       2016-08-16 19:47:54 +08:00
    放弃治疗这坨.md 了。
    然而官方又作死 deprecated 了 make-standalone-toolchain.sh ,又是一堆 py 交易的依赖……
    看来自己玩也迟早得转向 CrystaX 。
    ilotuo
        4
    ilotuo  
       2016-08-16 20:04:37 +08:00 via Android
    放到博客再分享吧
    pangliang
        5
    pangliang  
       2016-08-17 09:39:18 +08:00
    新人的话, 不建议学老的编译方式了.... 最新版的 as 的 gradle 编译已经很好用了
    cxl008
        6
    cxl008  
       2016-08-17 14:18:13 +08:00
    排版再好点就更好了
    nashge
        7
    nashge  
       2016-08-17 16:08:36 +08:00
    不错的 hello world
    wiket
        8
    wiket  
       2017-09-08 10:17:27 +08:00
    现在基本都用 gradle+cmake 了吧
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2403 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 15:33 PVG 23:33 LAX 08:33 JFK 11:33
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86