包体积优化实践记录

背景

前段时间我们项目开发工作完成后对项目进行一些优化工作,众所周知 , 性能优化是个持久长期的过程,纬度很多,包括启动、卡顿、内存、网络、电量等等,个人认为包体积优化投入产出比是最高的,流程简单,时间花销很少,但能带来体积大幅度缩减直观收益,而且具有通用型,能给每个项目优化提供参考。上周应用市场对接支付宝小程序和智能场景卡片,需要车机预装引擎包,邢捕头透露目前A1上提供的DC系统分区空间已经不足了,所以包体积优化应当引起重视。

优化前:11.9M

APK组成

文件描述

libjar,aar,so文件,不同的cpu架构

res编译后的资源文件,drawable、layout等

assets应用程序的资源、字体、音频文件等

classes(n).dexdx编译后的java文件

META-INF签名信息相关

resources.arsc二进制资源的映射信息,根据R文件找到对应资源

kotlin编译后的kotlin文件

AndroidManifest.xml清单文件

APK构建流程

优化思路

APK本质是一个压缩文件,是打包后的产物,那可以作为切入点的阶段就是打包前、打包中。

打包前,即减少打包的文件,比如无用的资源、代码;

打包中,对打包中的产物进行混淆压缩,比如资源文件、So文件;

基本操作

1、Lint检测无用资源文件

Analyze > Run Inspection by Name > Unused resources

//由于项目没有多余资源,提示如下:(如果有的话,确定无用删除即可)

注意: 因为lint是本地静态扫描,所以动态引用的资源文件并不会识别出来,也会出现在检测列表里。

2、Lint检测无用代码

因为本人电脑安装了alibaba java coding guideline,检测结果包含Lint检测代码。

注意: 因为lint是本地静态扫描,所以反射、动态引用的class并不会识别出来,也会出现在检测列表里。

lint优化后大小:11.9M →11.8M

3、图片压缩

推荐使用tinypng在线压缩。

原理: TinyPNG的压缩是通过减少颜色数量,将24-bit的图像文件转换成8-bit,来大幅度缩小图片体积。(有损,肉眼不可见)

4、TinyPngPlugin

手动压缩毕竟不高效,可以使用TinyPngPlugin一键压缩。plugins搜索TinyPng安装即可。(新版AS安装完plugin已经不需要重启了)

压缩结果:

    3张图片,可以看到效果还是非常可观的。如果图片多,效果更加明显。

5、图片转WebP

那这3张图还能继续优化吗?可以,WebP格式的体积更小,而且AS也提供了一键转换支持,但经过TinyPNG压缩后的体积转为WebP格式不一定每次都会更小。

以load_empty.png为例:

load_empty.png优化后原始大小9KB经过TinyPng压缩再经WebP格式转换缩小为3.82K

可以看到,较原始大小减少了68%

注:尝试对task-lib的png图片进行压缩,提示9-Patch图无法converted webP

优化后大小为:11.9M →11.8M→ 8.8M

6、R8编译优化

R8采用D8 + ProGuard的形式构建,将Proguard(混淆、压缩、优化)和D8(Java字节码转化成dex代码,编译优化体积优化)工具进行整合,目的是加速构建时间和减少输出apk的大小。

                                      -D8编译过程-

                                              -R8编译过程-

desugaring脱糖、shrinking压缩、obfuscating混淆、optimizing优化、 dexing编译一步到位

开启R8的好处:

代码缩减(摇树优化):使用静态代码分析来查找和删除无法访问的代码和未实例化的类型,对规避65535 引用限制非常有用;

资源缩减:移除不使用的资源,包括应用库依赖项中不使用的资源。

混淆代码:缩短类和成员的名称,从而减小 DEX 文件的大小

优化代码:检查并重写代码,选择性内联,移除未使用的参数和类合并来优化代码大小

减少调试信息 : 规范化调试信息并压缩行号信息。

//AS版本升级到3.4以上

minifyEnabled true //启用R8代码缩减功能。

shrinkResource true //启用R8资源缩减功能

注:R8包含混淆,如果不想混淆的代码和资源需要配合自定义混淆规则使用

优化后体积缩减:11.9M →11.8M→ 8.8M→6.7M

7、zipalign启动优化

zipalign 是 Android 提供的一个整理优化 apk 文件的工具,原理大概是格式化Zip文件夹的二进制文件的序列进行优化重排,达到提升系统解析速度。提高系统和应用的运行效率,更快地读写 apk 中的资源,降低内存的使用。所以对于要发布的 APP,在发布之前一般要使用 zipalign 进行优化。

zipAlignEnabled true //启动优化

8、so文件缩减

android studio默认会打包成4种架构,ZEEKR车机端不管是A1还是BX1E都是arm64架构的,所以保留arm64-v8a一种即可,其他直接删除。

由于之前应用市场已经优化过此问题,如果还原发现,lib文件从166KB增大为667KB

只保留一种架构需要设置:

9、移除未使用的备用资源

很多出海的应用会做国际化,但也适配不了这么多的语言。除了自己app的之外,还有一些官方的、三方的,可以统一配置支持的语言。

应用市场没有做海外语音适配 & 从一开始就只有xhdpi一种分辨率

defaultConfig {

//三种语言   

resConfigs("en","zh","zh-rCN")

}

资源文件同理,如果不进行唯一标识,默认会打包生成mdpi、xhdpi、xxhdpi3种

defaultConfig { "zh",resConfigs("xhdpi") }

10、小结

针对上面的基本操作做个小结,看看目前效果如何。

11.9M →11.8M→ 8.8M→6.7M 包体积减少43%,事实上应用市场自身特性也决定了对网络依赖性很强,本地资源很少,很多车机项目包体积减少效果会更大。

高阶操作

1、功能重复的三方库整合

1、比如glide和picasso,都是图片库,保留其一即可。

2、同一个三方库的不同版本。

File-Project Structure 查询依赖关系

3、其他一些特定业务操作。

2、so动态加载

├── armeabi-v7a/

│ ├── libmmkv.so

├── arm64-v8a/

│ ├── libmmkv.so

├── x86/

│ ├── libmmkv.so

└── x86_64/

├── libmmkv.so

很多三方库依赖的so文件占比比较大,而且由于适配不同架构包含多种一样的so文件,可以考虑so文件做按需远程下载&动态下发。也就是插件化的思想。

可以看出正常安装APK时:

PackageManagerService根据当前设备架构拷贝对应So文件到/data/app_libs/

启动APP,framework创建应用的ClassLoader实例,并将所有so文件所在目录注入 ClassLoader 字段中

调用 System#loadLibrary("xxx.so") , Framework从当前上下文classLoader的目录数组里查找so

native通过dlopen函数加载so

调用JNI方法

我们要做的就是把自定义的native库path插入nativeLibraryDirectories最前面,即使安装包libs目录里面有同名的so,也优先加载指定路径的外部so。

/** 例如Tinker源码中sdk25版本动态加载so的方案

https://github.com/Tencent/tinker/blob/ec6f1f5471d40d9e5c2386a25f468eb112c3aa86/tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/library/TinkerLoadLibrary.java

*/

private static void install(ClassLoader classLoader, File folder) throws Throwable {

// step1: 反射获取 pathList

final Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");

final Object dexPathList = pathListField.get(classLoader);

// step2: 拿到nativeLibraryDirectories

final Field nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories");

List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);

// step3: 如果 origLibDirs 内已有我们的路径了,移除掉

...

// step4: 将我们的路径放在集合首位,这样会优先加载,实现so的替换

origLibDirs.add(0, folder);

// step5: 获取 pathList 系统路径集合

final Field systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories");

List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);

// step6: addAll 两者所有路径

final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);

newLibDirs.addAll(origLibDirs);

newLibDirs.addAll(origSystemLibDirs);

// step7: 生成一个新的 natieLibraryPathElements 集合

final Method makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class);

final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);

// step8: 覆盖掉原有的路径集合

final Field nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements");

nativeLibraryPathElements.set(dexPathList, elements);

}

收益很大的同时,风险也很大,有很多case需要考虑到,比如下载时机、网络环境、线程进程,尤其是so库文件之间有依赖关系时有很多问题需解决。

可以参考facebook开源的SoLoader。

3、插件化

宿主APP按需动态下发多个子apk。

腾讯Shadow-零反射全动态Android插件框架

任何软件工程遇到的问题都可以通过增加一个中间层来解决

其他方案

1、原生改用H5或小程序等方案

有些功能可能原生做就显得太重,比如各种促销活动,需要加载各种大图,原生既重又不够动态化,这个时候H5是一种很好的替代方案。但是如果你原本就不支持H5或者小程序的话,接入这种能力可能反而会加大包体积,做好对比。

2、砍功能

有些功能可能想的很美好,但上线之后收益并不大,是否需要重新思考价值点,最好找到数据依托,再跟产品打架。

3、修改三方库的源码,不需要的代码剔除

比如引入了一个功能很齐全的三方库utils,但实际只用到几个,对源码进行抽取也能减少包体积,同时还能减少网络下载的编译时间。弊端就是升级成本较大。

4、图片网络化

即把图片上传到服务器,通过动态下载的方式减少包体积,弊端就是首次加载的时候依赖网络环境,对加载速度、流量需要做一个平衡。图片可以预加载,但是流量消耗是无法避免了,如果比较在意流量指标,需要权衡了。

包体积监控

包体积监控应该作为发布流程的一个环节,最好是做到流程化,否则很难持续,没几个版本包体积又涨上来了。大致思想:当前版本与上一个版本的包大小做对比,尽可能不做依赖、大小超过2M需要审批并给出原因和后续优化方案等等。

Matrix Android ApkChecker · Tencent/matrix Wiki · GitHub(Matrix是微信终端自研和正在使用的一套APM(Application Performance Management)系统。 Matrix-ApkChecker 作为Matrix系统的一部分,是针对android安装包的分析检测工具,根据一系列设定好的规则检测apk是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪。Matrix-ApkChecker以一个jar包的形式提供使用,通过命令行执行 java -jar ApkChecker.jar 即可运行。)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • 前言: 随着业务的快速迭代增长,App里不断引入新的业务逻辑代码、图片资源和第三方SDK,直接导致APK体积不断增...
    wenxiaohua阅读 128评论 0 0
  • 了解APK结构 classes.dex: 代码文件 传统的Java程序,首先先把Java文件编译成class文件,...
    奔跑吧李博阅读 1,592评论 2 12
  • 为什么要优化包体积 下载转化率:安装包越小,转化率越高; 推广成本:渠道推广成本和厂商预装的单价 应用市场:App...
    今阳说阅读 2,742评论 3 32
  • 目录: 一、为什么我们需要做 APK 的体积优化? 二、APK 组成 三、APK分析 四、代码体积优化 五、资源体...
    mumuxi_阅读 5,121评论 0 5
  • 前言 我司某产品产品从立项至今已经有三个年头,三年中的功能迭代导致项目中积累了大量过时的功能,把研发人员带到了业务...
    yi_zhe阅读 1,107评论 0 2