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

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

image.png


1. 背景


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


2. 删除无用模块


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


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


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


4. 使用strip


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


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


SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,-s")


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


4. 设置编译器的优化flag


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


CMake:


set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")


Android.mk


LOCAL_CPPFLAGS += -Os
LOCAL_CFLAGS += -Os


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


库名 优化前体积 优化后体积
libLibSampleApp.so 48M 33M
libAVSCommon.so 28M 22M
libDefaultClient.so 14M 9.9M


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


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


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


CMake:


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


Android.mk:


OCAL_CPPFLAGS += -ffunction-sections -fdata-sections
LOCAL_CFLAGS += -ffunction-sections -fdata-sections 
LOCAL_LDFLAGS += -Wl,--gc-sections


6. 设置编译器的 Visibility Feature


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


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


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


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


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


8. STL的使用方式


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


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


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


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


9. 不使用Exception和RTTI


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


RTTI


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


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


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


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


Exception


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


10 总结


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

目录
相关文章
|
2月前
|
算法 C++ 容器
C++标准库(速查)总结
C++标准库(速查)总结
79 6
|
2月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
112 10
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
80 15
Android 系统缓存扫描与清理方法分析
|
2月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
79 5
|
2月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
72 1
|
2月前
|
编译器 Android开发
配置环境变量,使CMakeLists.txt可直接使用Android NDK工具链编译项目
配置环境变量,使CMakeLists.txt可直接使用Android NDK工具链编译项目
|
3月前
|
编译器 API C语言
超级好用的C++实用库之跨平台实用方法
超级好用的C++实用库之跨平台实用方法
45 6
|
3月前
|
安全 C++
超级好用的C++实用库之环形内存池
超级好用的C++实用库之环形内存池
65 5
|
3月前
|
缓存 网络协议 Linux
超级好用的C++实用库之套接字
超级好用的C++实用库之套接字
35 1
|
3月前
|
存储 算法 安全
超级好用的C++实用库之国密sm4算法
超级好用的C++实用库之国密sm4算法
92 0