android+so+体积优化,Android ndk之so体积缩减

零.背景

SDK对体积比较敏感,并且除了接口代码其余所有用Native实现,armeabi下的so大概有800k,目标是缩减40%左右。所以有了此次对SO体积进行缩减的过程。体积的优化主要有四个层面,分别是代码,编译参数,项目结构和优化工具,下面对这四个方面进行说明。html

一. 代码层面

代码层面无非是替换部分臃肿的第三方库,精简本身的实现代码,尽可能减小stl库的使用,甚至纯粹用c实现。

就个人这个项目而言,代码可优化的地方有三处:java

json库

以前使用的是JsonCpp这个开源库,功能很是全面,包括简单的从字符串解析,构建成json字符串,对象转json,json转对象,可是有三个2000+代码的实现类给咱们的so体积增长了100k左右。由于项目并对json有这么多的依赖,因此这是一个很大的优化点。这里推荐两个比较好用的开源库。android

一个是C++实现的Json 11,他的代码量只有700行左右,而且只有两个文件,在其中实现了字符串解析,构建json字符串,对象转json,json转对象四个比较基本的功能,彻底能知足项目需求。c++

另一个是纯C实现的JSMN,只有200行左右,可是因为接口太过简单,用起来太多麻烦所以就放弃了,下面会提到,若是想摆脱stl库的依赖,这个库是一个比较好的选择。git

综合考虑到易用性和体积,通过替换Json 11库以后so大小大概减小了70k(10%左右)。github

protobuf库

因为项目使用了pb协议和外部通讯,所以使用了谷歌的protobuf解析库,也附带了有许多的protobuf协议C文件,包括protobuf解析库和protobuf协议文件编译后占用了大概200k左右的空间,web

虽然谷歌只提供了C++的方案,可是仍是有人实现了C的方案protobuf-c虽然使用起来没有C++的方案顺手,可是替换后能省下150k(20%)左右空间。json

stl库依赖

终极方案就是不依赖stl库,使用纯C实现,stl库使用静态连接的方式打入到so中大概会增长300k左右,具体取决于使用了多少stl库。将代码中的map,vector,thread等stl库依赖替换成C实现,或者彻底不用。固然这样对整个代码修改就比较多了,并且由于不少库不能用,等因而丧失了C++的不少优点,对之后的迭代效率会有比较多的影响。所以不得不放弃这个方案,若是之后实在有刁钻的客户须要200k如下的so体积,再作此打算吧。app

这其实也是个经验,若是对动态连接库的大小特别敏感,好比代码是跑在一些iot设备上的,就要从早期避免或者少使用stl库,甚至使用纯C实现。ide

这三个点只是针对个人项目的三个优化点,其余业务逻辑代码的优化也没有总结出什么特别的思路,只能特殊状况特殊分析,各位能够根据本身的项目实际状况选择优化项目代码。

二. 编译层面

编译层面有比较多的点,主要是Android.mk和Application.mk两个文件的参数配置,下面给出两个文件的官方参考文档,下面说的参数均可以在官方文档中查阅到。

rtti参数

LOCAL_CPP_FEATURES := rtti或者LOCAL_CPPFLAGS := -frtti

RTTI(即运行时类型信息),该依赖会增长40k(5%)左右的体积,能不用尽可能不用。

exceptions参数

LOCAL_CPP_FEATURES := exceptions或者LOCAL_CPPFLAGS := -fexceptions即C++异常,因为C++对异常的支持不够好,所以能不用其实能够不用(该依赖也会增长60k(7%)左右的体积)。fexception和rtti的依赖在ndk编译中默认是关闭的,若是代码中没有使用到这两个c++特性,就忽略上面两个参数。

gc-sections参数

LOCAL_CPPFLAGS += -ffunction-sections -fdata-sections

LOCAL_CFLAGS += -ffunction-sections -fdata-sections

LOCAL_LDFLAGS += -Wl,--gc-sections

这个参数能够说是意外之喜了,竟然能缩减大约200k(20%)左右的提示,所以简单研究了一下这个参数设置的原理。

GCC连接操做是以section做为最小的处理单元的,只要一个section中有某个符号被引用,该section就会被加入。若是没有加入-ffunctipn-sections和-fdata-sections全部的function和data都会放到同一个section中生成.o文件。使用了两个参数后,编译生成的.o文件将会拆分红许多个section,每一个section只包含一个function,而且该section的名字为function的名字。由于插入了不少个section声明,因此天然加入两个参数后,生成的.o文件都会比较大,可是在后面的连接阶段,这样的拆分就会颇有很大的做用。

在连接阶段,因为上述的拆分操做,连接过程当中能够很方便的标记出哪些function被使用到,哪些function没有被使用到,这样就能够将没有使用到的function剔除出最后生成的so库中,因为生成的so不会将section的拆分保留,因此上面由于section产生的体积增长就会消失,而且无用的function会被删除,这样整个so的体积就会大大减少了。其实整个过程有点相似于java proguard的optimize过程,可是Java会有反射,动态加载等过程,因此optimize用得会比较少。C++貌似没有相似机制(因为不是很精通C/C++因此不太确定),不然也会有坑。

icf=safe删除多余代码

LOCAL_LDFLAGS +=--icf=safe

加入--icf=safe连接参数能够删除多余的代码,可是有必定风险,可能删除掉内联声明,形成性能损失。

Os参数

关于这个参数详细的说明能够参考官方说明

这里作一下简单的说明O是在执行效率上进行优化,从1~3开启不一样的优化等级,这样会增长编译时间和增长编译后的库的大小,具体能够参考上面给出网站查看1~3的优化等级分别开启了什么优化方式。

O0则是调试模式,最大限度的减小编译时间和让调试产生预期的结果。

Os的意思就是针对size进行优化,这个选项能够最大限度的减小生成代码的体积,也就是最后能让动态库的体积最小。

arm默认的就是Os,而x86默认是-O2,这里能够经过设置

TARGET_x86_release_CFLAGS := -Os

这样能够有效的减少so体积,可是效率可能会慢一点。

三. 项目结构层面

若是项目拥有两个动态库,能够采用共享stl库的方式进行编译,主要是修改Applocation.mk文件参数改成xxx_shared,这样就会在编译后产生本身的动态库以及libxx_shared.sostl动态库,这个就是引用的动态连接库,若是须要编译两个动态库,那只须要引用一个stl动态库便可,可是要注意System.load时必需要优先加载stl动态库。

其次就是根据不一样的厂商提供不一样的so了,通常来讲armeabi-v7a针对多浮点运算会有比较大的优化,若是native代码涉及到很少的浮点数据运算,能够直接去掉armeabi-v7a的支持,只用armeabi。在接下来就是x86的动态库,其实如今就算在模拟器下也会兼容armeabi的运行,所以x86并非必要提供的。因此其实通常的应用而言,armeabi的so足够支持全部的机器运行,而更少的so能大大的减小应用的体积。

四. 优化工具 - UPX

最后提供比较另类的思路,使用UPX

UPX是一款的可执行程序文件压缩器,压缩过的可执行文件体积缩小50%-70%。之前PC时代常常会做为一款加壳器,固然是作过修改的。咱们这有专人负责研究这块,我使用后发现能将体积减小20~40%取决于设置多少的压缩比。能够说效果很是的好,可是直接用官网提供的工具在Android上会有一些兼容问题,可是都能解决。这块不方便透露更多,有兴趣的同窗本身去踩坑填坑吧。

五. 总结

最后经过上述的各类方式,so体积减小到只有300k左右,打包进apk里面大概只增长了150k左右的体积(由于打包成apk后会在进行一层zip压缩),目的达到了。由于对运行效率不太敏感,所以也没有去测试减小体积后带来的多少的性能损耗,后续有需求在进行这方面的测试吧。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值