当前位置: 首页 > news >正文

安卓动态链接库文件体积优化探索实践

背景介绍

应用安装包的体积影响着用户下载量、安装时长、用户磁盘占用量等多个方面,据Google Play统计,应用体积每增加6MB,安装的转化率将下降1%。

安装包的体积受诸多方面影响,针对dex、资源文件、so文件都有不同的优化策略,在此不做一一展开,本文主要记录了在研发时针对动态链接库的文件体积裁剪优化方案。

我开发的链接库使用rust语言开发,通过安卓jni接口实现java层和native层之间的相互调用。为什么使用rust主要有以下几个方面的考虑:

1.稳。安卓的JNI接口调用复杂,又涉及到native层的内存管理,随着代码量的增加,代码的安全稳定性会受到很大的挑战。使用rust开发,开发者几乎不需要考虑GC的问题,只要开发的时候按照规范老老实实写代码并且通过了编译器的检查,基本上就很难把程序写崩,这一点在代码上线后也确实得到了验证。

2.安全。传统使用C、C++开发的代码编译完成以后,如果不加保护,很容易使用反汇编工具破解,市面上比较成熟的工具如IDA、ghidra等都可以将汇编代码还原到高级语言。使用rust编译的产物,内部函数间的调用规约和传统都不一样,目前市面上还没有相对完善的反编译工具,软件的防破解能力直接上升一个数量级。

但是使用rust有一个非常明显的缺点就是编译产物体积过大。在不修改默认的rust编译选项的情况下,仅开启strip的情况下,我的动态库体积达到了495k

优化方案

参考网上前人的经验,依次进行了以下优化方式。

调整优化等级

默认的编译优化等级是O3,该优化的目的提高代码的运行速度,但是与此同时会对部分循环进行展开,体积造成膨胀。在此我们以缩减体积为目标,将优化选项改为z,表示生成最小二进制体积:

[profile.release]
opt-level = 'z'

优化后前后体积变化

| 编译选项 | 体积 |
| strip | 495k |
| strip + opt-level = ‘z’ | 437k |

开启LTO

LTO(Link Time Optimization)可以在链接时消除冗余代码,减小二进制体积——代价是更长的链接时间。

Cargo.toml
[profile.release]
opt-level = 'z'
lto = true

优化后前后体积变化

| 编译选项 | 体积 |
| strip | 495k |
| strip + opt-level = ‘z’ | 437k |
| strip + opt-level = ‘z’ + lto | 436k |

优化效果非常不明显,聊胜于无。

Panic立刻终止

rust默认的panic会在崩溃时进行栈回溯,方便定位问题。然而会带来额外的体积增加,将这一功能使用abort替代。

[profile.release]
opt-level = 'z'
lto = true
panic = 'abort'

优化后前后体积变化

| 编译选项 | 体积 |
| strip | 495k |
| strip + opt-level = ‘z’ | 437k |
| strip + opt-level = ‘z’ + lto | 436k |
| strip + opt-level = ‘z’ + lto + panic = ‘abort’ | 366K |

到目前为止,常规的优化手段已经用完了,后续优化需要配合一些代码的额外变动。

使用rust分析工具bloat对产物进行分析,结果如下:

File  .text     Size Crate
4.1%  69.0% 192.7KiB std
1.0%  16.8%  46.9KiB jdmp
0.5%   8.1%  22.7KiB [Unknown]
0.2%   3.8%  10.5KiB jni
0.0%   0.5%   1.5KiB cesu8
0.0%   0.4%   1.1KiB adler32
0.0%   0.3%     904B bytes
0.0%   0.2%     640B aho_corasick
0.0%   0.2%     588B regex_syntax
0.0%   0.2%     572B regex_automata
0.0%   0.2%     440B log
0.0%   0.1%     304B memchr
0.0%   0.0%      52B combine
0.0%   0.0%       8B jni_sys

让我感到惊讶的是我的核心代码jdmp模块只占了46.9k,为此要额外引入几百k的额外开销!

移除一些无用字符串

在引入的第三方依赖里,开发者自己添加了很多字符串信息,大部分是用来完善提供运行时报错信息。通过修改、精简这些依赖库,删除无用代码,又可以省出一部分空间来。

同时,上面的优化尽管使用abort替代了panic,rust编译器仍然会生出一些格式化的字符串,使用panic_immediate_abort这个编译选项禁用这个行为。

.cargo/config.toml
[unstable]
build-std-features = ["panic_immediate_abort"]
build-std = ["std","panic_abort"]

优化后前后体积变化

| 编译选项 | 体积 |
| strip | 495k |
| strip + opt-level = ‘z’ | 437k |
| strip + opt-level = ‘z’ + lto | 436k |
| strip + opt-level = ‘z’ + lto + panic = ‘abort’ + 代码裁减 + panic_immediate_abort | 135k |

再次分析,整个文件的体积已经降到了135k,自己开发的核心代码占总代码量的52%,基本符合预期。

 File  .text    Size Crate
14.2%  52.0% 41.3KiB jdmp3.2%  11.7%  9.3KiB core3.1%  11.4%  9.1KiB jni3.0%  11.0%  8.8KiB [Unknown]1.9%   6.8%  5.4KiB std0.9%   3.3%  2.6KiB alloc0.3%   1.1%    936B cesu80.3%   1.0%    792B adler320.1%   0.5%    372B aho_corasick0.1%   0.4%    316B regex_automata0.1%   0.3%    220B log0.1%   0.3%    216B hashbrown0.0%   0.1%    108B bytes0.0%   0.1%     44B combine0.0%   0.1%     44B rustc_demangle0.0%   0.0%      8B compiler_builtins0.0%   0.0%      8B jni_sys

优化linker script

尽管目前文件体积已经相比一开始优化了不少,但是还没有达到接入要求。通过readelf进一步分析ELF文件的各个section,我找到了一些额外的优化空间。

$ aarch64-linux-gnu-readelf -S target/aarch64-linux-android/release/libjdmp.so
There are 24 section headers, starting at offset 0x21738:Section Headers:[Nr] Name              Type             Address           OffsetSize              EntSize          Flags  Link  Info  Align[ 0]                   NULL             0000000000000000  000000000000000000000000  0000000000000000           0     0     0[ 1] .note.android.ide NOTE             0000000000000270  000002700000000000000098  0000000000000000   A       0     0     4[ 2] .dynsym           DYNSYM           0000000000000308  0000030800000000000002e8  0000000000000018   A       7     1     8[ 3] .gnu.version      VERSYM           00000000000005f0  000005f0000000000000003e  0000000000000002   A       2     0     2[ 4] .gnu.version_r    VERNEED          0000000000000630  000006300000000000000040  0000000000000000   A       7     2     4[ 5] .gnu.hash         GNU_HASH         0000000000000670  000006700000000000000024  0000000000000000   A       2     0     8[ 6] .hash             HASH             0000000000000694  000006940000000000000100  0000000000000004   A       2     0     4[ 7] .dynstr           STRTAB           0000000000000794  00000794000000000000014d  0000000000000000   A       0     0     1[ 8] .rela.dyn         RELA             00000000000008e8  000008e800000000000007f8  0000000000000018   A       2     0     8[ 9] .rela.plt         RELA             00000000000010e0  000010e000000000000002a0  0000000000000018  AI       2    19     8[10] .rodata           PROGBITS         0000000000001380  000013800000000000001d83  0000000000000000  AM       0     0     8[11] .eh_frame_hdr     PROGBITS         0000000000003104  000031040000000000002494  0000000000000000   A       0     0     4[12] .eh_frame         PROGBITS         0000000000005598  0000559800000000000078cc  0000000000000000   A       0     0     8[13] .text             PROGBITS         000000000000de64  0000ce640000000000013e0c  0000000000000000  AX       0     0     4[14] .plt              PROGBITS         0000000000021c70  00020c7000000000000001e0  0000000000000000  AX       0     0     16[15] .data.rel.ro      PROGBITS         0000000000022e50  00020e500000000000000430  0000000000000000  WA       0     0     8[16] .fini_array       FINI_ARRAY       0000000000023280  000212800000000000000010  0000000000000008  WA       0     0     8[17] .dynamic          DYNAMIC          0000000000023290  000212900000000000000180  0000000000000010  WA       7     0     8[18] .got              PROGBITS         0000000000023410  000214100000000000000048  0000000000000000  WA       0     0     8[19] .got.plt          PROGBITS         0000000000023458  0002145800000000000000f8  0000000000000000  WA       0     0     8[20] .data             PROGBITS         0000000000024550  000215500000000000000060  0000000000000000  WA       0     0     8[21] .bss              NOBITS           00000000000245b0  000215b00000000000000101  0000000000000000  WA       0     0     8[22] .comment          PROGBITS         0000000000000000  000215b000000000000000b2  0000000000000001  MS       0     0     1[23] .shstrtab         STRTAB           0000000000000000  0002166200000000000000d3  0000000000000000           0     0     1

在对这些section进行优化时,有必要搞清楚每个section在程序运行的作用。

| section | 作用 |
| .text | 代码段 |
| .data .rodata .bss | 数据段 |
| .plt .got .dynamic .dynsym .rela.dyn .rela.plt .shstrtab | 运行时被动态链接库解析,用于动态链接。 |
| .eh_frame .eh_frame_hdr | 用于保存函数的栈帧偏移,方便栈回溯 |
| .gnu.hash .gnu.version .gnu.version_r .hash | 保存编译文件元信息 |

程序在正常运行时,代码段、数据段必不可少,同时需要保留动态链接需要的section。剩余的section可以移除,可以进一步优化文件体积。值得注意到是,删除.eh_frame .eh_frame_hdr后,在程序崩溃时只能得到一个崩溃地址,无法进行栈回溯。

创建一个linker script,只保留程序运行最小依赖的section。

PHDRS
{headers PT_PHDR PHDRS ;text PT_LOAD FILEHDR PHDRS ;data PT_LOAD ;dynamic PT_DYNAMIC ;
}
ENTRY(Reset);
EXTERN(RESET_VECTOR); 
SECTIONS
{. = SIZEOF_HEADERS;.text : { *(.text .text.*) } :text.rodata : { *(.rodata .rodata.*) } :text. = . + 0x1000;.data : { *(.data .data.*) *(.fini_array .fini_array.*) *(.got .got.*) *(.got.plt .got.plt.*) } : data.bss : {*(.bss .bss.*)} : data.dynamic : { *(.dynamic .dynamic.*)  } :data :dynamic/DISCARD/ :{*(.ARM.exidx .ARM.exidx.*);*(.gnu.version .gnu.version.*);*(.gnu.version_r .gnu.version_r.*);*(.eh_frame_hdr .eh_frame .eh_frame_hdr.* .eh_frame.* );*(.note.android.ident .note.android.ident.*);*(.comment .comment.*);}
}

修改编译参数,替换默认的linker script

.cargo/config.toml[build]
target = ["aarch64-linux-android","armv7-linux-androideabi"][unstable]
build-std-features = ["panic_immediate_abort"]
build-std = ["std","panic_abort"][target.aarch64-linux-android]
rustflags = ["-C", "link-arg=-Tlinker.lds"][target.armv7-linux-androideabi]
rustflags = ["-C", "link-arg=-Tlinker.lds"]

经过一番操作,程序的体积最终裁减到了95k!完美符合要求。

总结

| 编译选项 | 体积 |
| strip | 495k |
| strip + opt-level = ‘z’ | 437k |
| strip + opt-level = ‘z’ + lto | 436k |
| strip + opt-level = ‘z’ + lto + panic = ‘abort’ + 代码裁减 + panic_immediate_abort | 135k |
| strip + opt-level = ‘z’ + lto + panic = ‘abort’ + 代码裁减 + panic_immediate_abort + 移除section | 95k |

本文记录了我进行编译体积优化的各种操作,其中的一些策略在使用C、C++语言开发中仍具有一定的通用性。

作者:尚红泽

来源:京东云开发者社区 转载请注明来源

相关文章:

安卓动态链接库文件体积优化探索实践

背景介绍 应用安装包的体积影响着用户下载量、安装时长、用户磁盘占用量等多个方面,据Google Play统计,应用体积每增加6MB,安装的转化率将下降1%。 安装包的体积受诸多方面影响,针对dex、资源文件、so文件都有不同的优化策略&…...

[Java][算法 哈希]Day 01---LeetCode 热题 100---01~03

LeetCode 热题 100---01~03 ------->哈希 第一题 两数之和 思路 最直接的理解就是 找出两个数的和等于目标数 这两个数可以相同 但是不能是同一个数字(从数组上理解就是内存上不是同一位置) 解法一:暴力法 暴力解万物 按照需求 …...

【每日一题】LeetCode——链表的中间结点

📚博客主页:爱敲代码的小杨. ✨专栏:《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️ 🙏小杨水平有…...

k8s 部署java应用 基于ingress+jar包

k8 集群ingress的访问模式 先部署一个namespace 命名空间 vim namespace.yaml kind: Namespace apiVersion: v1 metadata:name: ingress-testlabels:env: ingress-test 在部署deployment deployment是pod层一层封装。可以实现多节点部署 资源分配 回滚部署等方式。 部署的…...

深度学习技巧应用36-深度学习模型训练中的超参数调优指南大全,总结相关问题与答案

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用36-深度学习模型训练中的超参数调优指南大全,总结相关问题与答案。深度学习模型训练中的调优指南大全概括了数据预处理、模型架构设计、超参数优化、正则化策略和训练技巧等多个关键方面,以提升模型性能和泛化能力。 …...

“探索AJAX:前端与后端数据交互的利器“

前言 在现代Web开发中,前端与后端之间的数据交互是一个至关重要的环节。为了实现无需刷新页面的动态更新,AJAX(Asynchronous JavaScript and XML)作为一种强大的技术被广泛应用。 AJAX的原理 AJAX通过JavaScript和XMLHttpReque…...

【5G NR】移动通讯中使用的信道编解码技术

目录 一、引言 二、信道编解码技术概述 三、移动通讯中常用的信道编解码技术 四、优缺点分析与比较 五、未来发展趋势 六、结论 本文主要介绍了移动通讯中采用的信道编解码技术,由于在5G NR终端中,通常要兼容4G LTE通讯技术,所以4G LTE…...

用Python Tkinter打造的精彩连连看小游戏【附源码】

文章目录 连连看小游戏:用Python Tkinter打造的精彩游戏体验游戏简介技术背景MainWindow类:职责:方法:Point类: 主执行部分:完整代码:总结: 连连看小游戏:用Python Tkinter打造的精彩游戏体验 在丰富多彩的游戏世界中&#xff0c…...

nvm安装node后,npm无效

类似报这种问题,是因为去github下载npm时下载失败, Please visit https://github.com/npm/cli/releases/tag/v6.14.17 to download npm. 第一种方法:需要复制这里面的地址爬梯子去下载(github有时不用梯子能直接下载,有…...

spring boot(2.4.x 开始)和spring cloud项目中配置文件application和bootstrap加载顺序

在前面的文章基础上 https://blog.csdn.net/zlpzlpzyd/article/details/136060312 spring boot 2.4.x 版本之前通过 ConfigFileApplicationListener 加载配置 https://github.com/spring-projects/spring-boot/blob/v2.3.12.RELEASE/spring-boot-project/spring-boot/src/mai…...

5-2、S曲线计算【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】,查看本系列全部文章 摘要:本节介绍S曲线的基本变换,将基本形式的S曲线变换成为任意过两点的S曲线,为后续步进电机S曲线运动提供理论支撑 一.计算目标 ①计算经过任意不同两点的S曲线方程 ②可调节曲线平…...

SQL 注入 - http头注入之UA头注入探测

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 一、http头注入介绍 HTTP头注入是一种网络安全攻击手段,它利用了Web应用程序对HTTP头的处理不当或缺乏充分的验证和过滤。在这种攻击中,攻击者通过修改HTTP请求头中的某些字段,…...

学习数据结构和算法的第5天

空间复杂度及其常见案例 空间复杂度 空间复杂度也是一个数学函数表达式,是对一个算法在运行过程中临时占用存储空间大小的量度。 空间复杂度不是程序占用了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度…...

Android 11 访问 Android/data/或者getExternalCacheDir() root方式

前言: 需求要求安装三方应用ExternalCacheDir()下载下来的apk文件。 getExternalCacheDir() : /storage/emulated/0/Android/data/com../cache/ 获取访问权限 如果手机安卓版本为Android10的时候,可以在AndroidManifest.xml中添加下列代码 android:requestLegacyExt…...

Linux探秘之旅:透彻理解路径、命令与系统概念

目录 如何远程连接 远程登录简明指南 linux区别 1.严格区分大小写 2.linux的命令返回结果判断 3.如何查看网络信息 4.关于后缀名(Linux不关心文件后缀) 4.1 需要记忆的后缀 5.echo命令 6.linux一切皆文件 6.1比如磁盘的文件 6.2可执行文件 …...

哈希算法 c语言

#include <stdio.h> #include <stdlib.h> #include <string.h> // 哈希函数 unsigned int hash_function(const char *str) { unsigned int hash 0; while (*str) { hash (hash * 31 *str) % 1000; str; } return hash;…...

新版MQL语言程序设计:组合模式的原理、应用及代码实现

文章目录 一、什么组合模式二、为什么需要组合模式三、组合模式的实现原理四、组合模式的应用场景五、组合模式的代码实现 一、什么组合模式 组合模式是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和…...

代码随想录算法训练营第25天 | 216.组合总和III ,17.电话号码的字母组合

回溯章节理论基础&#xff1a; https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 216.组合总和III 题目链接&#xff1a;https://leetcode.cn/problems/combination-sum-iii/ 思路: 本题就是在[1,2,3,4,5,6,7,…...

Rust 第一个rust程序Hello Rust️

文章目录 前言一、vscode 安装rust相关插件二、Cargo New三、vscode调试rustLLDB 前言 Rust学习系列。今天就让我们掌握第一个rust程序。Hello Rust &#x1f980;️。 在上一篇文章我们在macOS成功安装了rust。 一、vscode 安装rust相关插件 以下是一些常用的 Rust 开发插件…...

高斯消去法 | LU分解 | PA=LU分解(MatLab)

一、问题描述 利用高斯消去法&#xff0c;LU 分解及PALU 分解求解非线性方程组。 二、实验目的 掌握高斯消去法、LU 分解、PALU 分解的算法原理&#xff1b;编写代码实现利用高斯消去法、LU 分解、PALU 分解来求解线性方程组。 三、实验内容及要求 1. 利用顺序高斯消去法求…...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决

问题&#xff1a; pgsql数据库通过备份数据库文件进行还原时&#xff0c;如果表中有自增序列&#xff0c;还原后可能会出现重复的序列&#xff0c;此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”&#xff0c;…...

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?

系列回顾&#xff1a; 在上一篇《React核心概念&#xff1a;State是什么&#xff1f;》中&#xff0c;我们学习了如何使用useState让一个组件拥有自己的内部数据&#xff08;State&#xff09;&#xff0c;并通过一个计数器案例&#xff0c;实现了组件的自我更新。这很棒&#…...