【NDK系列】ndk-build使用说明-程序员宅基地

技术标签: NDK  android  

ndk-build是上一代android ndk开发编译工具,尽管现在官方推荐使用CMake,AS默认的工具也切换成了后者,但是仍有必要对ndk-build有一定了解,以求:

  1. 知道ndk-build如何使用;
  2. 知道如何从ndk-build项目切换到CMake

还是先回顾一下NDK开发的步骤:

  1. 在java类文件中编写native接口;
  2. 借助AS代码提示自动生成或者使用javah命令生成.h头文件;
  3. 新建c/cpp文件引入头文件并实现接口函数;
  4. 配置编译选项编译成so文件(ndk-build或CMake)提供给三方使用;

可以看到ndk-build和CMake是NDK开发编码完成后的编译工具,是整个开发环节的最后一环。下面对ndk-build工具的使用做一个简单的介绍:

Android.mk

Android.mk文件用于指导编译器如何编译程序,文件名后缀为mk表明其是一个Makefile。Android.mk用于配置每个module的C/C++源码,module可以是静态库共享库或者独立的可执行文件。 一个Android.mk文件可以有一个或多个module,modules之间也可以有依赖关系。来看一个最简单的Android.mk配置文件:

# 获取脚本当前文件路径
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 生成的so文件名
LOCAL_MODULE    := hello    
# 目标文件
LOCAL_SRC_FILES := hello.c  
# 指定生成库为动态链接库,即so文件
include $(BUILD_SHARED_LIBRARY)

NDK提供了宏、变量以及模块描述变量,这些宏、变量以及变量的赋值共同组成了Android.mk文件。

  • 宏:包括my-dirall-subdir-makefiles等,通过$(call )来调用,返回文本信息。

  • 变量:包括CLEAR_VARSBUILD_SHARED_LIBRARYTARGET_ARCH等,由NDK编译系统提供,并且在Android.mk文件被解析前就已经存在。Android.mk文件有可能被多次解析,因此每次解析时这些变量的值都有可能不同。

  • 模块描述变量:Module-description,包括LOCAL_PATHLOCAL_MODULELOCAL_SRC_FILES等LOCAL_前缀变量,这些变量除LOCAL_PATH外,均填写在语句include $(CLEAR_VARS)include $(BUILD_XXX)之间。

下面介绍一下常用变量:

变量 说明 可选项 示例
LOCAL_PATH 当前目录,为模块描述变量,一个Android.mk必须定义LOCAL_PATH,用于定位源文件,在本例中,使用的是编译系统提供的宏“my-dir”(“my-dir”返回最近一次包括Makefile文件路径,通常为当前Android.mk所在目录),用于返回当前目录。此变量不会被CLEAR_VARS清除,所以每个Android.mk文件只需要定义一次就可以了。 调用宏 -
CLEAR_VARS 变量清除,由编译系统提供,作用是清除模块变量(在include $(CLEAR_VARS)和include $(BUILD_XXX)之间的LOCAL_XXX模块变量),当然LOCAL_PATH除外。由于所有的编译控制文件都是单一的GNU Make可执行上下文环境中解析,而这个上下文环境中所有的变量都是全局的,所以编译module前需要清理相应变量。 -
LOCAL_MODULE module名称,module的唯一标识,这个名字必须是唯一的,且中间不能有空格。在默认情况下,它决定了生成的文件名,如“hello-jni”对应的动态库名称为libhello-jni.so,然而要索引它时,需要“hello-jni”即可,也可以通过变量LOCAL_MODULE_FILENAME来覆盖这个默认名称。 模块名称 -
LOCAL_SRC_FILES 源码文件,包括C/C++源文件列表,这些源文件会被编译到一个module中,不过也不必列出头文件和包括文件,编译系统会自动为你找打所有需要的依赖关系。值得注意的是linux下路径使用顺斜杠(/)。 C/C++源文件列表 -
BUILD_SHARED_LIBRARY 动态库编译,编译器提供的变量,表示编译成动态库,它指向一个GNU Makefile脚本,这个脚本收集从include $(CLEAR_VARS)后所有的LOCAL_XXX变量中定义的所有信息,决定编译什么以及怎么编译。还有BUILD_STATIC_LIBRARY表示编译成静态库(.a),静态库不会被拷贝到APK中。 -
PREBUILT_SHARED_LIBRARY 预编译,指向一个编译脚本,用来指定一个预编译动态库。使用此变量时,不像BUILD_SHARED_LIBRARYBUILD_STATIC_LIBRARY那样,LOCAL_SRC_FILES的值必须是只能有一个指向预编译动态库的路径,如foo/libfoo.so,而不是源文件。PREBUILD_STATIC_LIBRARYPREBUILD_SHARED_LIBRARY一样,只不过是用于引用静态库。 include $ ( C L E A R V A R S ) < b r > L O C A L M O D U L E : = t e s t < b r > L O C A L S R C F I L E S : = l i b / (CLEAR_VARS) <br> LOCAL_MODULE := test <br> LOCAL_SRC_FILES := lib/ (CLEARVARS)<br>LOCALMODULE:=test<br>LOCALSRCFILES:=lib/(TARGET_ARCH_ABI)/libtest.so
include $(PREBUILT_SHARED_LIBRARY)
TARGET_ARCH_ABI 目标ABI名称,若定义了多个ABI,则每次解析Android.mk时值都不一样,主要使用场景为根本不同的ABI定义不同的文件等。 -
LOCAL_LDLIBS 链接库,用于额外链接选项,所有的库都有“-l”前缀。可同时列出多个库,用空格隔开。Android NDK默认链接了多个库,不需要显式地添加到LOCAL_LDLIBS中,包括 the standard C libraries,the standard C++ libraries,real-time extensions和 pthread库。同时也提供了一些需要显式添加的库。 Android-3:-llog (Android Log)
-lzZlib(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)
-landroidNatice (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_LDLIBS := -llog -ldl
LOCAL_CFLAGS 编译、链接标志,在编译C/C++时,传递给编译器的标志集合。 -fexceptions:由于NDK编译从R5开始才支持C++异常控制,为了通用性,异常处理默认是禁用的(-fno-exceptions),因此需要在指定module中添加LOCAL_CPPFLAGS += -fexceptions编译选项方可编译带异常处理的C++代码。也可以直接在Application.mk中配置APP_CPPFLAGS += -fexceptions。
-frtti:从NDK R5开始,NDK也开始支持C++ RTTI了,但为了通用性,所有的C++源文件被构建的时候默认是不支持RTTI的(-fno-rtti),可以通过在Android.mk中添加:LOCAL_CPPFLAGS += -frtti或者在Application.mk添加APP_CPPFLAGS += -frtti来开启RTTI。
-fvisibility=hidden:在NDK开发中,源文件的函数都有一个默认的visibility属性为public,编译生成的so文件中几乎所有的函数名、全局变量名均被导出,其实只需要导出java_com开头的jni函数即可,其他函数不需要暴露出来,在Android.mk中设置LOCAL_CFLAGS += -fvisibility=hidden,就可以隐藏不需要导出的函数,若某个函数需要导出,则添加JNIEXPORT或者__attribute__ ((visibility (“default”)))即可。
-ffunction-sections:不添加此参数时,编译文件.o中代码部分只有.text段,使用此参数,会使每个函数单独有一个段,举个栗子,函数func1()会编译成.text.func1段,虽然段多了,但对链接后代码大小并没有影响。
-fdata-sections:同上,每个data都有一个单独的段。
-Wl --gc-sections:-Wl,选项是告诉编译器,将后面选项传递给连接器,-Wl,–gc-sections的意思是使用连接器ld链接时删除不用的段。若使用LOCAL_CFLAGS += -ffunction-sections -fdata-sections,则代码和数据均被分割成不同的段,若某个函数或数据未被任何函数调用,则ld不会链接未被调用的函数,从而减小so文件体积,达到优化so的目的。
-fPIC:PIC(position independent code)用于编译位置无关代码,生成可用于共享库的位置独立代码。若不添加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段内容,这样就导致没使用这个.so,代码段的进程在内核中就会生成这个文件的拷贝。
-Wall: wring all 意思在编译和链接过程中显示所有警告信息。
LOCAL_CPPFLAGS += -fexceptions
LOCAL_CPPFLAGS 编译、链接标志,在编译C/C++时,传递给编译器的标志集合,只支持C++。 见上 -
LOCAL_LDFLAGS 编译、链接标志,在编译C/C++时传递给连接器一些额外的参数。 见上 -

更多资料:GCC Command Options 文档

Appliction.mk

Appliction.mk则是用于配置编译生成物相关属性,是轻量级的Makefile,通常位于项目的jni目录下。一个典型的示例如下:

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

配置项含义表:

配置项 功能 可选项 示例
APP_ABI 目标平台ABI类型,默认选择armeabi ABI,可通过设置APP_ABI设置一个或者多个ABI。对于ABI的选择需要考虑到效率和APK大小。由于armeabi-v7a指令集兼容armeabi;市面上的x86手机为了兼容性,基本都使用libhoudini模块,兼容arm指令集;64位机型默认支持32位abi的so,因此在对大小要求比较高的情况下,可以只选择市面上设备基本兼容的armeabi ABI,如果对性能有些许要求,可以再添加x86 ABI。 - armeabi
- arm64-v8a
- x86_64
- x86
- armeabi-v7a
- mips
- mips64
- all(全部)
-
NDK_TOOLCHAIN_VERSION 编译器类型、版本。默认采用的是GCC编译器,对于GCC版本的选择与NDK版本有关系,如NDK R12,在64位ABI默认是GCC 4.9,32位ABI默认是GCC 4.8,当然也可以像上面例子中给出的设置一样,设置clang编译器。 - gcc4.9
-clang3.5
-
APP_STL 运行库类型。Android NDK 默认使用的是最小支持的C++运行库,如果你需要你的NDK程序中使用STL,则可以设置APP_STL := stlport_static,若APK中有多个SO文件用到STL,建议都使用动态方式链接STL,这样可以减小整个APK文件大小。另外需要注意的是官方提供的NDK运行库除了默认的以外都支持RTTI和异常,然而默认是禁用的,将在下面的Android.mk中说明如何开启。 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++
APP_OPTIM 编译模式。“release”模式为默认的,生成的是优化后的二进制;也可以设置为“debug”模式,“debug”模式生成的是未优化二进制,提供很多BUG信息,便于调试和分析。 - release
- debug

参考资料:Application.mk官方文档

命令行

在AS项目根目录下执行ndk-build开始生成so文件。

参考资料:ndk-build 脚本

参考资料

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/csfchh/article/details/120918072

智能推荐

Java编程菜鸟初学_从键盘输入年份t如果年份t能被400整除或者能被四整除但不能被100整除则输入7-程序员宅基地

文章浏览阅读488次。1,编写程序,判断给定的某个年份是否是闰年。 闰年的判断规则如下: (1)若某个年份能被4整除但不能被100整除,则是闰年。 (2)若某个年份能被400整除,则也是闰年。package text;import java.util.Scanner;public class Year { public static void ma_从键盘输入年份t如果年份t能被400整除或者能被四整除但不能被100整除则输入7

BASE64、MD5、SHA、HMAC几种加密算法-程序员宅基地

文章浏览阅读106次。BASE64编码算法不算是真正的加密算法。 MD5、SHA、HMAC这三种加密算法,可谓是非可逆加密,就是不可解密的加密方法,我们称之为单向加密算法。我们通常只把他们作为加密的基础。单纯的以上三种的加密并不可靠。 BASE64 按照RFC2045的定义,Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The ..._base 64编码 和mad5 和雪花算法

住宅IP、家庭宽带IP以及原生IP,它们有什么区别?谷歌开发者账号应选择哪种IP?-程序员宅基地

文章浏览阅读1.1k次。IP地址(Internet Protocol Address)是互联网协议地址的简称,是互联网通信的基础,互联网上每一个网络设备的唯一标识符每个在线的设备都需要一个IP地址,这样才能在网络中找到它们并进行数据交换。IP地址有很多种类型,今天跟大家简单分享一下住宅IP、家庭宽带IP以及原生IP的区别。住宅IP通常是指由互联网服务提供商(ISP)分配给家庭的或小型办公室使用的互联网连接IP地址,并可能随着网络连接的变化而变化。此类IP地址主要用于日常网络活动,如浏览网页、发送接收电子邮件、上网冲浪等。

如何更改layui form表单位置,宽度,颜色等_layui-form-item 宽度-程序员宅基地

文章浏览阅读2.6w次,点赞14次,收藏30次。如何更改layui form表单位置,宽度,颜色等_layui-form-item 宽度

【翻译】Efficient Data Loader for Fast Sampling-Based GNN Training on Large Graphs_pagraph: scaling gnn training on large graphs via -程序员宅基地

文章浏览阅读612次。写的非常好_pagraph: scaling gnn training on large graphs via computation-aware caching

炫酷的HTML代码-程序员宅基地

文章浏览阅读2.7w次,点赞61次,收藏285次。很炫酷的html代码:<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><title>star</title><script type="text/javascript">window.onload = function () {C = Math.cos; // cache Math objectsS = Math.si.._炫酷的html

随便推点

【免费题库】华为OD机试C卷 - 数字字符串组合倒序(Java 代码+解析)-程序员宅基地

文章浏览阅读2.3k次。题目描述对数字,字符,数字串,字符串,以及数字与字符串组合进行倒序排列。字符范围:由 a 到 z, A 到 Z,数字范围:由 0 到 9符号的定义:“-”作为连接符使用时作为字符串的一部分,例如“20-years”作为一个整体字符串呈现;连续出现 2 个 “-” 及以上时视为字符串间隔符,如“out--standing”中的”–“视为间隔符,是 2 个独立整体字符串”out”和”standing”;除了 1,2 里面定义的字符以外其他的所有字符,都是非法字符,作为字符串的间隔符处理,倒序后

Android(14) ArrayAdapter(数组适配器)的三种方法-程序员宅基地

文章浏览阅读5w次,点赞36次,收藏138次。ArrayAdapter数组适配器用于绑定格式单一的数据,数据源可以是集合或者数组列表视图(ListView)以垂直的形式列出需要显示的列表项。实现过程:新建适配器->添加数据源到适配器->视图加载适配器第一种:直接用ListView组件创建列表每一行只有一行文字效果如图:activity_list布局:<?xml version="1.0" e..._arrayadapter

助力商家健康经营 创业者为水滴直播点赞-程序员宅基地

文章浏览阅读43次。近日,水滴直播平台登上了舆论的风口浪尖。有人认为水滴直播涉嫌侵犯隐私,但也有人表示这种互联网新生事物可以有效规避很多风险,值得鼓励,不应一棒子打死。记者采访时发现,很多商家、创业者对于水滴直播纷纷表示支持,并直言水滴直播为他们的经营带来了很大帮助。 邹志泉在北京丰台区经营着一家批发厂家直销男女内衣裤的店铺,平时就打开水滴直播,分享他在店铺的经营画面。面对水滴直播涉及隐私的提问,邹志泉明确表...

java毕业设计宠物收养管理系统Mybatis+系统+数据库+调试部署-程序员宅基地

文章浏览阅读67次。springboot基于SpringBoot的电影社区网站。springboot基于springboot食品销售网站。ssm基于微信平台的校园汉服租赁系统的设计与实现。ssm基于SSM高校教师个人主页网站的设计与实现。ssm基于SSM框架的在线健康系统设计与实现。ssm基于HTML的武昌理工学院二手交易网站。ssm基于JavaEE的网上图书分享系统。ssm基于Javaee的项目任务跟踪系统。

Nginx使用之反向代理、负载均衡、动静分离教程。_php动静分离-程序员宅基地

文章浏览阅读61次。负载均衡是指将客户端的请求分发到多个后端服务器,以平衡服务器的负载。反向代理是指将客户端的请求转发到后端服务器,并将响应返回给客户端。通过配置反向代理,Nginx将转发所有来自客户端的请求到后端服务器,并将响应返回给客户端。通过这样的配置,Nginx将根据请求的URL路径选择是将请求转发到后端服务器还是直接返回静态资源文件。通过配置负载均衡,Nginx将按照指定的策略将客户端的请求分发到后端服务器上,从而实现负载均衡。配置反向代理:编辑Nginx配置文件(通常是nginx.conf),在。_php动静分离

HTML5有哪些新特性_谈谈html5的一些新特性-程序员宅基地

文章浏览阅读9.5k次,点赞3次,收藏18次。(一) 语义标签(二)增强型表单(三)视频和音频(四)Canvas绘图(五)SVG绘图(六)地理定位(七)拖放API(八) WebWorker(九) WebStorage(十)Web..._谈谈html5的一些新特性