优化-包体积优化

另一篇APK瘦身

做法:利用Android Studio自带的Analyzer进行APK的分析:

  1. 将APK拖到android studio的编辑器窗口
  2. 在project窗口中,双击build/output/apks/目录下的apk
  3. 在菜单栏中选择Build > Analyze APK,然后选择要分析的APK。我们就可以看哪部分占比比较大进行针对性的优化

image-20210218135812094

  • lib:包含了一些区分于处理器的编译代码,主要是SO文件,一般里面包含很多子目录,例如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, and mips。
  • res:包含了一些不会被编译到resources.arsc的资源文件。如drawable文件、layout文件等。
  • assets:包含了一些通过AssetManager能够检索到的资源。如MP3、字体、webp等资源文件。
  • META-INF:包含了CERT.SFCERT.RSA签名文件,还有MANIFEST.MF文件。
  • resources.arsc:包括了所有可以被编译的位于res/values/目录下的XML资源。打包工具在打包过程中会把XML的内容编译成二进制的形式,亦或者把相关资源的引用路径编译成二进制,然后整合到该文件里面。例如string文件、layout的路径、图片的路径等。
  • classes.dex:包含了所有的Java文件编译后的class文件,class文件最终转化成该dex文件。一般文件都比较大,有的App有几个dex文件,这是因为单个DEX文件限制方法数在65536,所以当代码量过大时,就需要通过multiDex进行分包,拆分成多个dex文件,解决这个问题。
  • AndroidManifest.xml:整合了多个module的AndroidMainifest文件的权限、声明等配置到该文件。

lib瘦身

  • so裁剪、删除

对App引入的so文件进行确认哪些是不需要的,哪些是可以进行裁剪压缩的,哪些是可以避免引入的。例如如果引入的so需要下载上传功能而多引入了一个cURL库导致so增大,这时就可以让Java层代码定义接口,让so来调用,从而避免引入cURL库;再如Fresco库,如果不需要webP图,或者不需要webP动图功能,然后减少Fresco库的依赖,同样可以减小so的大小。

  • 只保留armeabi或者armeabi-v7a

Android系统现在支持很多种CPU架构(如mips、arm、x86等),市面上主流机型都是arm架构,x86和mips类型极少。所以可以有选择地保留某些架构的so,从而降低lib文件夹的大小。但是我建议你在开始前先对用户手机的cpu型号进行一次统计,分析自身App对应架构手机的占有率,这样你才能大胆的进行操作,一般只保留armeabi或者armeabi-v7a即可。操作也是比较简单,只需要在根目录的build.gradle下配置:

1
2
3
4
5
6
7
android {
buildTypes {
ndk {
abiFilters "armeabi-v7a"
}
}
}

如果你的App需要支持多种架构,那么就可以在abiFilters里面把多种架构加进去,当然你也可以只保留一种,然后分渠道打包,如Google Play就支持arm和x86等多个渠道打包。

res目录优化

res目录一般也是占APK Size大头的一个目录

  • 只保留一套图

因为Android设备在加载图片时会优先加载对应分辨率文件夹下的图片,如果对应分辨率文件下没有所要的图片,则找高分辨率对应文件夹下的图片。那是不是我们把图片放在最高分辨率的文件夹下就可以了呢?不是的!因为如果这样会导致低分辨率手机加载图片时会消耗更多的内存,而且是指数级别增长的,所以如果盲目地放在一个目录是不合适的。目前不同分辨率对应优先加载的文件夹中图片如下,如果是针对国内用户的App可以只保留xxhdpi目录,而如果是东南亚市场的App则可以只保留xhdpi

1
2
3
4
5
320*240        ldpi
480*320 mdpi
800*480 hdpi
1280*720 xhdpi
1920*1080 xxhdpi
  • 非重要图片动态加载

针对一些非重要的图片,可以选择动态在线加载,严格来说,非首页的图片都可以动态加载,当然,为了提升用户体验,我们会把图片放在本地。但是,一些使用场景非常小或者大小较大的图片,大胆删掉,选择动态加载吧!

  • 保真压缩图片

可以使用一些图片压缩网站或者工具压缩你的资源文件吧,例如TinyPng、ImageOptim、Zopfli、智图等。

  • 使用webp替换png

如果你的App只支持Android4.0以上的话,可以把png格式的图片转为webp,相同画质下体积更小。

  • 使用lint删除无用资源

在多人开发过程中,通常都会有漏删无用资源的问题,图片资源也不例外,例如需要删除一个模块的代码时,很容易就会漏删资源文件,所以可以定期使用lint检测出无用的资源文件,原理这里不作介绍,使用方法非常简单,可以直接在AS里面使用,如下图所示。注意:lint检查出来的资源都是无直接引用的,所以如果我们通过getIdentifier()方法引用文件时,lint也会标记为无引用,所以删除时注意不要删除通过getIdentifier()引用的资源。

1
Analyze -> Run Inspection by Name -> 输入:Unused resources -> 跳出弹框选择范围即可
  • 打开shrinkResources

shrinkResources是在编译过程中用来检测并删除无用资源文件,也就是没有引用的资源,minifyEnabled 这个是用来开启删除无用代码,比如没有引用到的代码,所以如果需要知道资源是否被引用就要配合minifyEnabled使用,只有两者都为true时才会起到真正的删除无效代码和无引用资源的目的。打开方式也是非常简单,在build.gralde文件里面打开即可:

1
2
3
4
5
6
android {
buildTypes{
minifyEnabled true //删除无用代码
shrinkResources true //检测并删除无用资源文件
}
}

assets目录优化

assests目录存放的通常是一些通过AssetManager能够检索到的资源,包括MP3、视频、字体、webp的资源,各个App存放内容都很大不相同,列举一些常用的优化方案。

  • 删除无用字体

中文字体一般都比较大,因为字体文件包含了中文好几千个汉字,但是我们实际上在App中并不会全部都使用,甚至我们只用到其中的几个字,这时候我们就可以把字体文件进行删减,在Github上面有一个字体提取工具FontZip,使用方法也是非常简单,有兴趣可以去star一下。

image-20210218142916524

  • 动态下载

一些MP3、视频、Webp等资源可以在使用到时再进行下载,不需要放在本地。

  • 对资源进行压缩

一些MP3、视频、Webp等资源如果必须放在本地,可以压缩成zip文件或者使用7zip进行压缩,在使用到时再进行解压,减小空间的占用。

META-INF目录

该目录下的MANIFEST.MF、CERT.SF、INDEX.LIST、CERT.RSA等文件主要是存放一些APK文件加密后的信息,用以校验APK的完整性和安全性,这个目录没有太好的优化方式,而且文件一般也比较小,不会超过1M。

resources.arsc文件压缩

这个文件包含所有可以被编译的位于res/values/目录下的XML资源,如下图所示是淘宝APK的resources.arsc文件,像图片的引用名字、layout文件的引用名字、string资源等都被编译到了这个文件里面。

image-20210218143102333

所以如果我们需要对resources.arsc文件进行优化,无非就是对路径名字进行混淆,删除无用的资源映射,前者可以使用AndResGuard,后者可以使用lint等进行检测。

  • 删除无用的语言

大部分应用都不需要支持几十种上百种语言,所以在我们引用一些第三方库时(如Google、Facebook的库),它们往往带有上百种多语言资源,而大部分多语言对于我们自己的应用是没有用处的,我们只需要在build.gralde里面进行如下配置即可完成无用语言资源的删除,这样在打包的时候就会排除私有项目、android系统库和第三方库中非中文的资源文件了,效果还是比较显著的。

1
2
3
4
5
6
7
android {
//...
defaultConfig {
// 只保留中文
resConfigs "zh"
}
}
  • 使用AndResGuard压缩

AndResGuard是一个帮助你缩小APK大小的工具,他的原理类似Java Proguard,但是只针对资源。他会将原本冗长的资源路径变短,例如将res/drawable/wechat变为r/d/a。详细使用方法参照Github,很简单有效地减小resources.arsc文件大小。如果是资源比较多的App,压缩效果也是立竿见影。

使用方法也是非常简单,在build.gradle文件中进行如下配置即可(在根build.gradle中与buildscript{}同级):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
apply plugin: 'AndResGuard'
buildscript {
repositories {
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.1.16'
}
}

andResGuard {
// mappingFile = file("./resource_mapping.txt")
mappingFile = null
use7zip = true
useSign = true
// it will keep the origin path of your resources when it's true
keepRoot = false
whiteList = [
// your icon
"R.drawable.icon",
// for fabric
"R.string.com.crashlytics.*",
// for umeng update
"R.string.umeng*",
"R.string.UM*",
"R.string.tb_*",
"R.string.rc_*",
"R.layout.umeng*",
"R.layout.tb_*",
"R.layout.rc_*",
"R.drawable.umeng*",
"R.drawable.tb_*",
"R.drawable.rc_*",
"R.drawable.u1*",
"R.drawable.u2*",
"R.anim.umeng*",
"R.color.umeng*",
"R.color.tb_*",
"R.color.rc_*",
"R.style.*UM*",
"R.style.umeng*",
"R.style.rc_*",
"R.id.umeng*",
"R.id.rc_*",
// umeng share for sina
"R.drawable.sina*",
// for google-services.json
"R.string.google_app_id",
"R.string.gcm_defaultSenderId",
"R.string.default_web_client_id",
"R.string.ga_trackingId",
"R.string.firebase_database_url",
"R.string.google_api_key",
"R.string.google_crash_reporting_api_key",
"R.dimen.rc_*"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
"resources.arsc"
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.1.16'
}
}

dex文件压缩

Dalvik是Android平台运行时的环境,但是Dalvik虚拟不支持直接执行Java的字节码,所以会对编译生成的 .class 文件进行翻译、重构、解释、压缩等处理,这个处理过程是由 dx 进行处理,处理完成后生成的产物会以 .dex 结尾,称为Dex文件。

像淘宝、微信这些App,如果我们分析它们的APK可以发现,它们有多个Dex文件,这是因为单个Dalvik Excutable(DEX)字节码文件内的方法数不可以超过65536个,所以需要DEX分包配置来避免这个限制,使应用能够构建并读取DEX文件。

  • Proguard代码混淆

Proguard是一款免费的Java类文件压缩器、优化器和混淆器,Android Studio已经集成了这个工具,只要经过简单的配置,即可完成,如下代码所示,在build.gradle里面设置minifyEnabled为ture,同时在proguardFiles指向proguard的规则文件即可。

1
2
3
4
5
6
android {
buildTypes{
minifyEnabled true
proguardFiles 'proguard.cfg'
}
}

利用lint清理无用的Android项目资源

运行Lint

Android Studio右侧菜单栏中找到 Gradle,展开找到并双击verification > lint

image-20210218172408117

点击运行之后会生成两个文件:

  • lint-results.xml
  • lint-results.html

这两个文件所在的目录为:项目名称/app/build/reports

也可以使用命令行生成:

也可以使用命令行生成:

lint –check “UnusedResources” ./ > result.txt

会导出一个txt的文件,如果想导出*.html*的文件可以使用命令:

lint –check “UnusedResources” ./ –html result.html

这样就会生成一个html格式的文件。
要查看未使用的资源,可以在生成的文件里面可以找到UnusedResources的选项:

找到了不再使用的资源,如何清理呢?手动删除太麻烦了,推荐使用另一个工具android-resource-remover

android-resource-remover 清理无用资源

android-resource-remover是一个开源的python库,可以根据Android Lint生成的结果,从项目中删除掉不用的资源。

使用环境要求:

  • Python >=2.7
  • ADT >= 16
  • Pip

通过pip安装android-resource-remover:

pip install android-resource-remover

使用Lint生成的lint-results.xml文件清理不用资源,运行命令:

android-resource-remover –xml build/outputs/lint-results.xml

这样就可以清除无用的Android资源了。