前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android C++ 系列:NDK 减少 so 库体积方法总结

Android C++ 系列:NDK 减少 so 库体积方法总结

作者头像
IT大咖说
发布2022-04-18 12:10:18
1.4K0
发布2022-04-18 12:10:18
举报
文章被收录于专栏:IT大咖说IT大咖说

背景

基于亚马逊 AVS Device SDK 改造的全链路语音 SDK 最终编译的动态库有几十个,单架构动态库大小有几十兆,之前在 Iot 设备中勉强跑着,但是这个体积对于手机应用来说是致命的,各个模块费事费力能优化个几 K 的体积就不错了,我这直接给上个几十兆的,APP 平台方肯定无法接受。但是一是有业务需求,二是自己又想把 SDK 推到手机 APP,提高用户量,验证 SDK 的稳定性和交互体验,所以开始了漫长的瘦身过程,最后单架构压缩到了五兆一下,虽然还是有点大,但是比起之前有了很大的提升。

删除无用模块

AVS Device SDK 是主要应用在音响的控制台程序,而且代码是跨平台的,所以一是有很多为了跨平台做的冗余,二是有很多我们根本用不到的模块。比如为了做本地存储引入了一个 Sqlite 的动态库,我们本身也用不到本地存储,像闹钟设置之类的放到 APP 层即可,而且就算是需要存储也完全可以使用 Android 和 iOS 平台提供的 Sqlite。删除用不到的模块是包体积优化空间最大最快的。

第三方库替换为 Android/iOS 平台提供能力

AVS Device SDK 在 Android 平台基于 ffmpeg 做解码实现了音频播放器,对于我们的场景主要使用用播放器来播放 TTS,而 TTS 是和服务协商好固定的 mp3 格式,完全没有必要为了一个 mp3 解码引入一个庞大的 ffmpeg 库。这里我们使用 Android 平台提供的 Jni 层的媒体库来做音频解码。而且即使是 Android 平台 JNI 层不支持,也可以单独依赖一个 mp3 解码库,而不是庞大的 ffmpeg。对于整个包体积来说,第三方模块往往相对来说是比较大的。

使用 strip

使用 NDK toolchain 可以把调试的 C++ 符号表(Symbol Table)中数据删除,我们一般我们打成 APK 会自动帮我们做这个工作,当然也可以手动设置:

手动的在链接选项中加入 strip 参数,配置如下所示:

代码语言:javascript
复制
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,-s")

也可以手动执行 ndk 提供的aarch64-linux-android-strip命令移除动态库中的调试信息,这种方式除了前面方法外优化体积最高的方式,比如 libLibSampleApp.so 从 48M 直接优化到了 992k。

设置编译器的优化 flag

编译器有个优化 flag 可以设置,分别是-Os(体积最小),-O3(性能最优)等。这里将编译器的优化 flag 设置为-Os,以便减少体积。

CMake:

代码语言:javascript
复制
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os")set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

Android.mk

代码语言:javascript
复制
LOCAL_CPPFLAGS += -OsLOCAL_CFLAGS += -Os

除了直接删除占用体积较大的模块外,编译器优化是排下来优化空间最大的方法。设置完-Os后占用提交较大的前几个库体积对比:

使用 gc-sections 去除没有用到的函数

有些时候代码量比较大的时候我们没办法手动发现无用的函数,这个时候可以可以开启编译器的 gc-sections 选项,让编译器自动的帮你做到这一点。

编译器可以配置自动去除未使用的函数和变量,以下是配置方式:

CMake:

代码语言:javascript
复制
# 去除未使用函数与变量set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")# 设置去除未使用代码的链接flagSET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections")

Android.mk:

代码语言:javascript
复制
OCAL_CPPFLAGS += -ffunction-sections -fdata-sectionsLOCAL_CFLAGS += -ffunction-sections -fdata-sections LOCAL_LDFLAGS += -Wl,--gc-sections

设置编译器的 Visibility Feature

Visibility Feature 就是用来控制在哪些函数可以在符号表中被输入,由于 C++并不是完全面向对象的,非类的方法并没有 public 这种修饰符,因此,要用 Visibility Feature 来控制哪些函数可以被外部调用。而 JNI 提供了一个宏-JNIEXPORT 来控制这点。所以只要对函数加上这个宏,像这样:

代码语言:javascript
复制
// JNIEXPORT就是控制可见的宏// JNICALL在NDK这里没有什么意义,只是个标识宏JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)

然后在编译器的 FLAGS 选项开启 -fvisibility = hidden 就可以。这样,不仅可以控制函数的可见性,并且可以减少包体的大小。

去除 C++代码中的 iostream 等直接 IO 相关代码

使用 STL 中的 iostream 相关库会明显的增加包的体积,而 Android 本身是有预编译库(android/log.h)可以代替输入到控制台的工具的。在我们的 SDK 中由于之前是控制台程序所以用到了输入输出,编译的时候没有把这块排除出去,造成了一定的体积冗余。

STL 的使用方式

对于 C++的 library,引用方式有 2 种:

  • 静态方式(static)
  • 动态方式(shared)

其中,静态方式在编译时会将用到的相关代码直接复制到目的文件中;而动态方式则会将相关的代码打成 so 文件,以便多次引用。由于编译器在编译时并不能知道所有被引用的地方,所以同时会打入了很多不相关的代码。

所以,如果项目中引用 library 的函数较多时,用动态方式可以避免多次拷贝,节省空间。相反,则直接使用静态方式会更节省空间。由于我们 SDK 的模块特别多,再加上整体 APK 里面已经有其他业务引入了动态库,所以我们用动态库的方式。

不使用 Exception 和 RTTI

关于这两点在网上看到的没有实践过,不过拿过来可以作为包体积持续优化的参考。

RTTI

通过 RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型,即运行时获取对象的实际类型。C++通过下面两个操作符提供 RTTI。

(1)typeid:返回指针或引用所指对象的实际类型。

(2)dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用。

RTTI 的选项是默认关闭的的,而代码中其实并没有用到相关的功能,这里可以直接关闭。

Exception

使用 C++的 exception 会增加包的大小,而目前 JNI 对 C++的 exception 的支持是有 bug 的,比如下面这段代码就会引起程序的 crash(对于低版本的 android NDK)。因此要在程序中引入 exception 要自己实现相关逻辑,但是这样又会增加包体大小。对于开发者来说,exception 可以帮助快速定位问题,而对于使用者并不是那么重要,这里可以去掉。

总结

本文介绍了删除无用模块,平台能力替代第三方库,使用 strip,设置编译器优化的 flag,使用 gc-sections 去除没有用到的函数,设置可见性,去除 iostream 等有助于动态库体积优化的方法。

来源:

https://xie.infoq.cn/article/e670ae4bd73cf424ea6b4940f

“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com

来都来了,走啥走,留个言呗~

 IT大咖说  |  关于版权

由“IT大咖说(ID:itdakashuo)”原创的文章,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!

感谢您对IT大咖说的热心支持!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT大咖说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ◆ 背景
  • ◆ 删除无用模块
  • ◆ 第三方库替换为 Android/iOS 平台提供能力
  • ◆ 使用 strip
  • ◆ 设置编译器的优化 flag
  • ◆ 使用 gc-sections 去除没有用到的函数
  • ◆ 设置编译器的 Visibility Feature
  • ◆ 去除 C++代码中的 iostream 等直接 IO 相关代码
  • ◆ STL 的使用方式
  • ◆ 不使用 Exception 和 RTTI
  • ◆ 总结
相关产品与服务
灰盒安全测试
腾讯知识图谱(Tencent Knowledge Graph,TKG)是一个集成图数据库、图计算引擎和图可视化分析的一站式平台。支持抽取和融合异构数据,支持千亿级节点关系的存储和计算,支持规则匹配、机器学习、图嵌入等图数据挖掘算法,拥有丰富的图数据渲染和展现的可视化方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档