背景:
1.为什么要做包体积优化
在2018年的Google I/O,Google透露了Google Play上安装包体积与下载转化率的关系图。
下载转化率:一个 100MB 的应用,用户即使点了下载,也可能因为网络速度慢、突然反悔下载失败。对于一个 10MB 的应用,用户点了下载之后,在犹豫要不要下的时候已经下载完了。但是正如上图的数据,安装包大小与转化率的关系是非常微妙的。(10MB 跟 15MB 可能差距不大,但是 10MB 跟 40MB 的差距还是非常明显的)
推广成本:一般来说,包体积对渠道推广和厂商预装的单价会有非常大的影响。特别是厂商预装,这主要是因为厂商留给预装应用的总空间是有限的。如果你的包体积非常大,那就会影响厂商预装其他应用。还有一些街边推广需要靠数据流量来下载的场景,更是直接影响用户的下载意向。
2. 包体积与应用性能的关系
安装时长:文件拷贝、Library 解压、编译 ODEX、签名校验等,甚至包括一些混编的工程来说,初步解压转化就要占据很长的时间
运行内存RAM:资源越多,加载到内存里的东西越多,占据内存越多,就容易越卡顿
物理内存ROM:对于一些32G,64G用户来说,可能装不了几个大型的app
二、我们大致能做些什么?
简单来说可以分为三部分:
1:业务梳理优化,随着时间的堆积,尤其一些大型应用,业务线不断增多,功能不断增多,代码量就上去了,所以要定期清理一些旧的功能,不用的,效益不好的,等等,各个维度来降低业务堆积。
2: 转变开发模式,配合一些混编模式,h5,小程序分担一些不必要的需求等方式,来减少原生代码堆积。
3: 技术方式瘦身,结合实际情况,打包特性等操作,来做瘦身。我们重点说一下这一部分。
三、瘦身之前先认识apk
1、apk是由哪几项组成的
大致是由
dex:代码相关
res,assets:资源相关
lib:so相关
meta-inf:应用的签名校验信息相关
2、分析apk的几种方式
大致有几种方式:
apk说到底是一个压缩包,如果实在不想使用任何工具,可以把后缀改为.zip,然后直接解压
一个比较官方的方式apktoolapktool
使用AS,在菜单栏Build>Analyze apk ,比较清晰的看到结构和每个文件的大小
nibledroid 这个是一个比较完善的工具,(我没有实践,感兴趣的小伙伴可以去康康)
android-classyshark 是一个二进制分析工具,可以清晰看到每个包名下代码占比
四、开始优化
从apk结构可以看出,dex和lib是apk大小的“大头”
先说so的优化方式:
so是Android上的动态链接库,在我们Android应用开发过程中,有时候Java代码不能满足需求,比如一些 加解密算法或者音视频编解码功能,这个时候就必须要通过C或者是C++来实现,之后生成So文件提供给Java层来调用,在生成So文件的时候就需要考虑生成市面上不同手机CPU架构的文件。目前,Android一共 支持7种不同类型的 CPU 架构,比如常见的armeabi、armeabi-v7a、arm64-v8a、X86等等。理论上来说,对应架构的 CPU 它的执行效率是最高的,但是这样会导致 在 lib 目录下会多存放了各个平台架构的 So 文件,所以App的体积自然也就更大了。
现在市场支持64位,32位两种系统的apk包,打包的时候会拆包进行打包。也是为了合理的包体积优化考虑。之前是只打入64位的,64位可以兼容32位,只是效率会差点,现在应用市场支持分包了,很不错。
1: 常见的处理方式,拆包,根据手机架构和位数来动态适配
2: so动态下发,该方案比较彻底,对包体积优化比较友好,但是兜底方案不好制定,会存在因为下载失败导致的某些功能暂时不可用
dex主要优化方式:
1: 安卓ProGuard(混淆)
提到混淆,大家都想到的是打包时混淆,其实远不止此,混淆大概有四个作用:
1-1:压缩(Shrink): 侦测并移除代码中无用的类、字段、方法和特性 1-2:优化(Optimize): 对字节码进行优化,移除无用的指令 1-3:混淆(Obfuscate): 使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名 1-4:预检(Preverify): 在Java平台上对处理后的代码进行预检
代码混淆形式一般有三种:
1:将代码中的各个元素,比如类、函数、变量的名字改变成无意义的名字。例如将 hasValue 转换成单 个的字母 a。这样,反编译阅读的人就无法通过名字来猜测用途。
2:重写代码中的部分逻辑,将它变成功能上等价,但是又难以理解的形式。比如它会改变循环的指令、结构体。
3:打乱代码的格式,比如多加一些空格或删除空格,或者将一行代码写成多行,将多行代码改成一行。
混淆规则:
https://www.jianshu.com/p/d5ee2f1f977f
ps:混淆本来就是为了节约字节码站位和反编译风险,所以尽量不要keep *,这样就等于无效混淆了。
优化(dontoptimize)-dontoptimize关闭优化
压缩(dontshrink)- dontshrink关闭压缩
混淆(dontobfuscate)-dontobfuscate 关闭混淆
这里多说一下:
混淆非常重要大家都已经知道了,那么细看一下gradle中的配置,其中有一句是:
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'mpProguard.cfg'
x0; 大家有没有想过proguard-rules.pro这个是自己的文件,那么getDefaultProguardFile是什么,它在哪里?它的作用是什么?
其实该getDefaultProguardFile文件所在目录是在android/sdk/tools/proguard/,是Android sdk 为我们提供的一些Android内置的一些混淆规则,可以理解为是通用配置。当你没有额外定义混淆规则时,就按通用混淆规则进行混淆。
其次说一下mapping文件,这个文件也非常重要,这个文件是混淆后的输出文件,如果遇到崩溃情况资源找不到情况,可以由该文件找到资源和混淆后字母的对应关系:
这里有个小tips:如果一个类没有被引用,是不会参与混淆的。mapping里找不到映射关系。
像我们这种大体量的app,光mapping文件就一百多M了
2:andResGuard 资源混淆
原生安卓的混淆主要是针对于代码,而工具AndResGuard主要是针对资源
x0; 3:第三方库
第三方库,随着项目逐渐发展,引入的第三方库越来越多,这时候需要排查是否有功能相同的第三方库,比如图片的第三方库,比较流行的有glide,Fresco等,还有处理Json的也容易重复,所以我们需要排查第三方库,避免引入相同功能的第三方库。其次,对于一些比较大的第三方,功能比较齐全,有时候我们可能就只用它的一部分功能,可以进行源码依赖,二次代码剥离,但是同时也要面对版本管理问题。
4:移除无用代码,无用类,无用资源
对于删除代码通常有两个问题:
1: 业务代码越来越多
2: 代码越多越不敢删
其实如果可以确定无用了,也就敢删除了,所以如何确定无用就非常重要:
1:业务层面,产品直接说,确定是功能下线,关联代码直接删除
2:技术手段筛选无用代码:
2-1: 统计可以打点的所有page,捞取apk里所有page,取差值,就是真正的无用页面
2-2: Lint扫描, ,扫描无用资源, 扫描无用代码,Lint的弊端是不完全准确,因为lint的无用资源和无用代码是割裂开进行扫描的,尤其针对无用代码中引用的无用资源,一般情况下扫描不到,所以需要反复扫描多次。我个人很少遇到误删的情况,但是还是需要反复确认一下比较安全。
2-3: 有一些算法,加载文件处理成String,扫描文件名是否在其他文件中引用,算法也是同样有弊端。
3: 低pv页面转web,低pv,参考业务埋点数据统计。
ps: 工具simian扫描重复代码的,感兴趣的小伙伴可以看一下。
额外:对于代码编码方向,尽量少用枚举 ,不要拿google源码中也有大量枚举来杠,每减少一个 enum 可以减少大约 1.0 到 1.4 KB 的大小,这个是已经被论证了的。所以尽量少用。
5: 资源文件少配置
语言包只配置英文
图片只载入一套drawable-xxhdpi
6: 打包再次压缩(滴滴插件)
滴滴的出的bosster 是针对打包过程中二次资源整理,重复资源去重,压缩,整理resources.arsc文件等处理的比较彻底,添了google对于无用资源处理方式(不移除用一个空白文件替代)
像这个体量的app,用booster保守估计可以再压缩5-10M左右,还是比较可观的。
7:图片优化
在一些大型app中,图片资源也是重中之重,所以控制好图片资源也对包体积优化有不可或缺的贡献。
图片优化大概分几个方向:
UI层面:尽量使用原生自带的样式,或者尽可能统一UI样式,一些图片close,back等常用图片能统一使用
图片压缩,导入包内前用压缩工具处理压缩
安卓原生尽量转webp图片使用,这里需要注意不是每个png转webp后都会体积下降,注意下即可
一些纯色icon尽量用矢量图VectorDrawable,VectorDrawable》webp》png》jpg
8:避免产生Java access方法
java access方法是,为了提供内部类和外部类直接调用掉对方私有成员变量,又不违反java的封装性规范,Java在编译时期自动会生成 package 可见性的静态方法。因为我们没有引入ASM,加上该优化方式收益并不是很大,所以大家了解一下就好了。
https://mp.weixin.qq.com/s/ZHisCVjO_ZrtvvEWBYUQFQ
9:插件化
插件化开发能有效的大幅度降低包体积,一些独立的模块变为插件化开发,从服务器拉取下来家加载,能有效的降低包体积。
build目录下,这里每个目录可以结合上一篇gradle里面task来看,这里就是每个task后输出的对应目录对应的内容,最后根据这些内容进行打包。
参考文档:
https://time.geekbang.org/column/article/81483
http://t.zoukankan.com/sihaixuan-p-10978222.html
此篇文章由于脱敏问题,缺失配图。看个大概吧。
网友评论