云音乐 Android so 体积治理实践

背景

软件应用除了功能外,还有许多非功能质量属性需要我们关注,常见有性能、安全性、可用性、可扩展性等。除此之外,软件的体积也是我们应该关注的重要质量属性。体积对启动速度、下载安装时长、安装成功率、磁盘空间占用、OOM 异常等都有深刻影响。

最近负责治理云音乐 Android 端 so 的体积,通过研究摸索总结了一些方法,主要从三个方面着手治理,分别是

  • 优化代码
  • 优化编译链接
  • 优化依赖。

用这些方法进行了一次大面积 so 治理后,so 整体从 30M+ 降低为 20M+,减少了 30%+ 的体积。本文对这些治理方法和背景知识进行了介绍,以供大家参考。

优化代码

针对代码,主要关注在去重复代码和禁用昂贵的 C++ 语言特性。Andorid NDK 下昂贵的语言特性包括

  • 异常
  • RTTI
  • iostream 库

去除重复代码

重复的代码,不仅带来体积问题,更是一种代码坏味道。移除重复代码无论在质量上,还是减小 so 体积上都有益处。我们可采用代码静态检测工具检测重复代码,然后以提炼类或函数的重构手法进行处理。

  • 提炼函数:如果一个类的多个函数有重复代码,提炼独立函数,放入类中供其他函数使用。如果多个兄弟子类有重复代码,提炼独立函数,放入父类之中供子类使用。
  • 提炼类:如果不相关类有重复代码,提炼独立类放置重复代码,供这些类使用。

禁用昂贵的 C++ 语言特性

在 Android NDK 下,有许多 C++ 特性是比较昂贵的,在 Android NDK 官方文档亦有提及,要尽量避免使用。主要包括禁用 C++ 异常、禁用 C++ RTTI、避免使用 iostream。

C++ 异常会有一个误导,以为可以捕获让人头疼的空指针、内存越界等意料之外的错误,其实并不能。异常机制实际上是一种错误处理框架,捕获预先定义的错误,其目的是将正常逻辑和异常逻辑的处理分开,提高代码整洁度。而我们每定义一处异常,在编译链接后都会插入 C++ 库代码进行扩展,占用比编写的代码更多的空间。因为其性能和体积等问题,在实践中可考虑改用返回错误码来代替。

C++ RTTI 机制,在语意层面和多态是矛盾的。C++ 的多态,是通过基类指针指向派生类对象,在 Compile Time 时无须知道实际类型,在 Run Time 时方根据指向的类型,执行对应的虚函数实现,从而让我们得以从依赖实现改为依赖接口。而 C++ RTTI,则是在 Compie Time 期间得知基类指针指向的实际类型,也即让我们从依赖接口改为了依赖实现。此外,编译器实现 RTTI 机制往往会增加 class 的大小,比如为每个 class 产生额外的 RTTI 数据,包含类名和基类信息。当我们使用到 RTTI 时应该仔细考量,是否设计上出现了问题。如果特殊情况需要使用,也要清楚背后的体积成本和设计成本。

对于 iostream 库,通过我们在实际场景中的检查,发现大部分仅仅是使用了 std::cout 输出日志。Android 本身提供有 log 方法,用 <android/log.h> 中的 log 进行日志输出,可移除 iostream 的依赖,从而减少体积。

优化代码实践方法

禁用上述语言特性,在 NDK Build 下无需特别指定,其默认禁用 C++ 异常和 RTTI。在 CMake 下禁用异常和 RTTI 的编译选项如下:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")

针对 iostream 库的引入,可搜索整体代码,或用 objdump 反汇编 so 库,确定是否含有 iostream 信息,然后定位修改代码。

objdump -D demo.so | grep iostream

优化编译链接

在编译链接方面,重点是治理 so 的导出符号,增加相应的编译链接优化选项,提供足够的信息给编译器,由它在编译链接期间进行体积优化。 要了解 so 的导出符号,需要了解 ELF 文件格式。ELF 文件有多个分段(section),可以通过 readelf 命令查看细节。我们关注的分段为:

  • .text:存放编译后的机器代码
  • .date:存放已初始化的全局/静态变量
  • .dynsym:动态符号表,包含导出符号和导入符号
  • .dynstr:动态符号字符串表
  • .symtab:全体符号表
  • .debug:调试信息表

其中动态符号表(.dynsym)是我们关注的重点,它记录了动态库的导入导出符号,我们需要确保导出的是必要且完整的符号集合,去除不必要的导出符号。

对于代码段(.text)和数据段(.data),在默认编译选项下,产出的目标文件会将多个函数汇集到一个代码段,多个变量放到一个数据段,最后合入到so中。我们需要通过编译链接选项,帮助编译器只合入用到的函数和变量。

全体符号表(.symtab)和调试信息(.debug),则包含了丰富完整的符号信息,在分析 Crash 堆栈时可还原符号。我们保留一份带有 .symtab 和 .debug 的 so,并在发布时执行 strip 移除这些符号调试信息。就即可以发布小体积的 so,也可以在出现 Crash 时用大体积 so 还原堆栈符号。

限定动态符号表

ELF 中的动态符号表(.dynsym),记录了动态库的导入导出符号。在 Linux/Android NDK下,编译器默认将函数和全局变量,及引入使用的静态库的函数和全局变量,作为自己的动态符号全部导出,使用者在使用时也无需任何特殊操作。虽然方便,但也容易导致 so 包含许多不应该导出的函数符号,甚至将内部使用的其他静态库的函数也进行导出。我们需要对导出的动态符号做出限制,确保只暴露外部依赖的符号,可以有效缩减动态符号表以及相关表项。对于第三方或无法明确导出符号的 so,则强制不导出其它的静态库符号。我们也强制要求不导出 C++ 库的符号。见下图示意:

移除未使用函数和变量

默认编译选项下的目标文件在编译后,会将多个函数汇集到一个代码段,多个变量放到一个数据段。以代码段来说,其含有多个函数,哪怕我们只用到其中一个函数,这个代码段就要整个保留,在链接阶段会整体合入 so,从而合入了并未使用的函数,增大了体积。数据段也是如此。我们可以通过选项告知编译器用更细粒度分段,让一个函数占一个代码段,一个变量占一个数据段,并告知编译器回收未使用的代码段和数据段,从而移除并未使用的函数和变量。见下图示意:

精简 JNI 原生接口符号

在 Android NDK 下的 JNI 原生接口注册方式有两种,分别是静态注册和动态注册。静态注册是以“Java+包名+类名+方法名”定义 native 方法,由 runtime 自己扫描注册。动态注册则是在 cpp 文件中定义 JNI_OnLoad 方法,我们在此方法中调用 RegsiterNative 注册 JNI 接口。采用动态注册,对于支持 JNI 的 so 只需要导出 JNI_OnLoadJNI_OnUnloadJava_* 可有效降低体积(规模更小、速度更快的共享库)。 RegsiterNatives 动态注册方法,可参考 Google 官网动态注册代码。

优化编译链接实践方法

融合上述的限制动态符号表,细化代码段和数据段,并回收未使用分段,可以让编译器移除没有被“导出函数”直接或间接依赖的函数和变量,从而大幅减少 so 的体积。

限制导出符号方法

可采用 version script 的方法,这也是 NDK 官网示例的方法。具体来说我们编写一个类似 json 的文件,指明要导出的函数,并在链接选项中加入此脚本文件即可。version script 文件示例如下(注意导出类需要 extern “C++”,避免名称修饰问题):

{global: gValue;*someFuncs*;extern "C++" {CSemaphore::*;CCritical::*;};local: *;
};

CMake编译链接选项如下:

# 以 version script 指定导出函数
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,versionscript=${CMAKE_CURRENT_SOURCE_DIR}/funcs.map")            
# 不导出所有引入的静态库的符号
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--exclude-libs,ALL")

NDK build编译链接选项如下:

# 以 version script 指定导出函数
LOCAL_LDFLAGS += -Wl,--version-script=${LOCAL_PATH}/funcs.map 
# 不导出所有引入的静态库的符号
LOCAL_LDFLAGS += -Wl,--exclude-libs,ALL

协助编译器移除未使用函数和变量

我们通过增加编译选项,可让编译器回收未使用的代码段和数据段,如下:

  1. 指定分段选项,此举会让编译器在编译目标文件或静态库时,将单函数和单变量放入单个独立的段。
    • -ffunction-sections
    • -fdata-sections
  2. 指定回收选项,此举会让编译器在链接阶段执行 DeadCode 检测,识别出未使用的函数和变量,进而移除未使用的段。
    • –gc-sections
# CMake 编译选项
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto -fdata-sections -ffunction-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -fdata-sections -ffunction-sections")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -O3 -flto -Wl,--gc-sections")
# NDK Build 编译选项:
LOCAL_CFLAGS += -Oz -flto -fdata-sections -ffunction-sections
LOCAL_LDFLAGS += -O3 -flto -Wl,--gc-sections

优化依赖

当一个静态库被多个 so 重复依赖时,会引入多份静态库代码,可以提取这个重复依赖库为独立 so,供其他 so 库共用。当一个 so 库仅仅被另一个 so 库依赖时,会产生相关导入/导出符号表项,可通过合并两个 so 去除导入导出符号。这两个方法是依赖的治理思路。

在实践中,我们重点推进了 libc++ 的依赖治理。一个应用不应使用多个 c++ 运行时,在 Android 下 libc++ 版本与 NDK 版本是相对应的,统一 NDK 版本及 libc++ 依赖方式非常重要,涉及的不仅仅是体积问题,还可能导致 App Crash 或者其他奇怪问题。通过检测我们发现大部分自研 so 库都是采用静态依赖 libc++_static 且版本不一,所以重点推进了 NDK 版本的统一,并统一动态依赖 libc++_shared 。对于因为历史原因无法升级 NDK 版本的则保持静态链接 libc++_static,但要确保不导出其符号。

统一 libc++ 的依赖方式

  • 确定统一的 NDK 版本以及相应的 libc++_shared.so,在 module 级约束 ndkVersion 为统一版本。
  • 发布基础 aar 包,内含 libc++_shared.so。
  • 在功能性 so 工程中,动态链接 libc++_shared.so。
# Module 级的 build.gradle,此举会自动将 libc++_shared.so 打入 aar 包
DANDROID_STL=c++_shared
# ndk-build 下,在 Application.mk中加入
APP_STL := c++_shared
  • 发布功能性 so aar 包时,排除自身的 c++_shared.so,以避免冲突
packagingOptions {exclude '**/libc++_shared.so'
}
  • 如果不能对齐统一版本的 ndk,则采用静态链接 C++ 库的方式。因为 so 默认会将自己引入的静态库作为自己的导出符号全部导出,所以需要排除 C++ 库的符号。
# 不导出 C++ 库的符号
LOCAL_LDFLAGS += -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a

总结

通过上述方法,能够有效的治理和控制so的体积。除此之外,还需要不断挖掘重复依赖的功能;同时为了防止劣化,需要在CI/CD机制中加上相关符号和编译选项的检测。这也需要我们持续进行关注和完善。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集

在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/51424.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

日本美术学校有哪些,日本美术学校排名榜

日本美术类院校&#xff0c;不管是在教育质量上还是在艺术造诣上在国际上都享有极高的声誉。是故&#xff0c;每年都有大量学生赴日学习美术。那么日本有哪些排名靠前的美术类大学呢&#xff1f;下面跟着花水木君一起来了解下吧。 一、东京艺术大学 东京艺术大学是一所校本部位…

是计算机数控系统的核心,监管目标是监管者追求的最好效果或最好状态。()

摘要&#xff1a; 监管中国知网的高级检索不包含初级检索的功能。( )林黛玉敏感自尊&#xff0c;管者果或虽与贾宝玉耳厮鬓摩长大&#xff0c;仍严守男女大防&#xff0c;杜绝任何身体接触。《红楼梦》第二十五回《魇魔法姊弟逢五鬼&#xff0c;追求最好状态红楼梦通灵遇双真》…

书香荟萃,数字赋能,重庆佳兆业珑樾壹号项目顺利完成数字化落地

项目名称&#xff1a;重庆佳兆业珑樾壹号 建设单位&#xff1a;重庆渝祥兆实业有限公司 施工单位&#xff1a;重庆华硕建设有限公司 一、项目基本情况 重庆佳兆业珑樾壹号坐落于大学城西路&#xff0c;以1.5低容积率规划打造洋房、美学院墅两大标杆作品&#xff0c;和鸣纯粹…

《程序员情商》自我修养必备《论语》

导读&#xff1a;《论语》是中国古代春秋时期一部语录体散文集&#xff0c;由孔子弟子及再传弟子编纂而成。主要记录孔子及其弟子的言行&#xff0c;较为集中地反映了孔子的思想&#xff0c;是儒家学派的经典著作之一&#xff0c;中国现传扬并学习的古代著作之一。主要由仲弓、…

使用echarts中国地图上绘制散点图(自适应宽高)

先上最终效果图&#xff08;显示公司在中国各个城市药店分布图&#xff09; 我这边使用的技术栈是react&#xff0c;不熟悉的可走&#xff1a;https://zh-hans.reactjs.org/ 首先使用npm安装echarts cnpm install echarts --save 除此之外&#xff0c;我还自己引用了两个jso…

AirBuddy让你在Mac上像iPhone一样使用AirPods

创作立场声明&#xff1a;爱好Mac软件&#xff0c;感谢张大妈提供平台与大家分享&#xff0c;自费购入软件进行测试&#xff0c;希望大家客观指正&#xff0c;期待大家的交流讨论&#xff0c;欢迎补充&#xff01; AirPods2 AirPods值得入手吗&#xff1f; AirPods的问世&…

《红楼梦》诗词大全

前言&#xff1a; 博主最近二读红楼&#xff0c;幼时只觉此书开篇便人物繁杂、莺莺燕燕似多混乱&#xff0c;开篇只看黛玉哭闹了几次&#xff0c;便弃书不读&#xff0c;只觉困惑&#xff0c;其何敢称六大奇书或四大名著&#xff1f; 今日书荒&#xff0c;偶然间再次拾起红楼…

追剧还能得红包 《欢乐颂2》五美邀你来“抢”搜狗搜索现金大礼

《欢乐颂2》播出过半&#xff0c;“22楼五美”的感情生活一直是剧情走向的一大主线&#xff0c;与此相关的各种话题也在剧外持续发酵&#xff0c;成为观众热议的焦点。大家在看剧紧追“五美”生活、爱情故事的同时&#xff0c;在剧外还可获得五位女神送上的真现金福利。日前&am…

网易云信欢乐颂(送),领取“五美”送麻麻

神圣的母亲节快到了 如何让母后度过一个快乐的母亲节 云小信做了一番SWOT分析 最终得出的结论是 麻麻需要一个儿媳妇儿 而你需要一个女朋友&#xff01; 为了捍卫麻麻的尊严 为了维护猿类的和平 云小信为广大开发者们带来了 母亲节最佳大礼包 当当当当~~~ 五美荟萃 总有一款是你…

欢乐颂之鸿蒙系统,《欢乐颂3》立项开拍,五美主演大换血,看清阵容后:熬夜都追...

《欢乐颂》作为一部经典的都市女性剧&#xff0c;连续推出的两部都收获了不错的口碑和收视率&#xff0c;近期这部剧作第三部已经备案拍摄&#xff0c;引发了观众的期待。 从设定上来看&#xff0c;人物形象与之前有很大的差异&#xff0c;不过还是五位性格&#xff0c;家庭迥异…

思维导图带你走进欢乐颂五美的世界

最近火热的《欢乐颂》带了了一阵风潮&#xff0c;每个人看的角度都不同。一千个读者就有一千个哈姆雷特&#xff0c;有人看到的是阶层&#xff0c;有人看到的是钱钱钱&#xff0c;有人看到的是豪车包包化妆品&#xff0c;而小编就用思维导图从专业的角度去分析其中的职业技能。…

VideoFusion,damo文本生成视频

文本生成视频大模型_哔哩哔哩_bilibiliModelScope 旨在打造下一代开源的模型即 服务共享平台&#xff0c;为泛 AI 开发者提供灵活、易用、低成本的一站式模型服务产品&#xff0c;让模型应用更简单&#xff01;欢迎使用魔搭社区&#xff1a;ModelScope.cn, 视频播放量 278、弹幕…

TCP/IP协议族-应用层协议http|DNS|smtp|ftp简介

在庞大的TCP/IP协议族5层模型中&#xff0c;网络应用层是位于最上层的&#xff0c;其应用模型主要分为两种&#xff1a;客户/服务器模型(client/server&#xff0c;C/S)&#xff0c;P2P模型。 在客户/服务器模型中&#xff0c;有一个总是打开的主机称为服务器&#xff0c;服务…

ChatGPT进军网络安全,安全从业者是否会被取代?

在ChatGPT爆火之后&#xff0c;微软宣布推出基于ChatGPT的“安全副驾驶”产品&#xff0c;宣称将“帮助防御者以机器的速度和规模进行端到端防御。 网络安全是人工智能最大的细分市场&#xff0c;而ChatGPT又是引领当下人工智能技术革命的“核弹级产品”&#xff0c;在网络安全…

产品经理为什么比程序员工资要高?百度员工:因为他是产品灵魂

程序员和产品经理关系堪称是渊源颇深了&#xff0c;网络上关于他俩关系的段子更是层穷不出&#xff0c;这不&#xff0c;有程序员抱怨道&#xff1a;产品经理为什么比程序员工资普遍要高&#xff1f; 在某平台上&#xff0c;有实名的百度员工回应道&#xff1a;因为产品经理才是…

程序员吐槽:凭什么产品经理工资比我高?网友评论炸了!

在我们 IT 界&#xff0c;程序员和产品经理简直就是一对冤家。在工作上天天互怼&#xff0c;互相不服气。其实导致这种现象出现的原因就是&#xff1a;产品经理把程序员当民工&#xff0c;程序员把产品经理当 SB&#xff0c;其实是互相不了解罢了。小编c学习群825414254获取c一…

在北京做产品经理3年可以拿到多少薪资

在目前来看&#xff0c;人才市场什么岗位比较热呢?毫无疑问&#xff0c;那就是产品经理这个岗位。这就要说说&#xff0c;产品经理跟其他的岗位相比有什么优势呢?今天周老师和大家说道说道。 产品经理的优势   第一点&#xff0c;最突出的优势就表现在薪资方面&#xff0c;…

什么是:产品专员、产品经理、产品总监、产品副总?

在互联网产品管理职位结构中大致分为&#xff1a;产品专员-----》产品经理-----》产品总监-----》产品副总&#xff08;由低到高&#xff09;。 在目前很多的小公司里基本将众多产品职能角色汇集成一个&#xff1a;产品经理&#xff08;当然&#xff0c;甚至在有些公司老板就是…

程序员和产品经理的关系

目录 和谐团队必备要素我所期待的PM做一个合格的程序总结 正文 上周&#xff0c;又看见有程序和PM&#xff08;产品经理&#xff09;吵了起来&#xff0c;大致是因为晚上就要上线了&#xff0c;下午的时候PM来说要改点需求&#xff0c;但程序不愿意。兴许是天气热了&#xff…

产品经理与研发经理的分工

最近在翻看《程序员》杂志的时候看到的一篇文章&#xff1a;被《偷走的童话结局-对营销和研发分工的考核》。中间反应的问题感觉和现在的工作息息相关&#xff0c;整理下来供思考。 一、如何进行职责的划分&#xff1f; 产品经理和研发经理是一个研发团队的重要组成部分&…