体积全面瘦身"/>
App包体积全面瘦身
汇总:Android小白成长之路_知识体系汇总【持续更新中…】
目录
- 为什么要进行包体积优化?
- Apk的组成
- 优化方案
- dex大小优化
- 删除无效代码
- D8编译
- Proguard混淆
- R8优化
- 资源优化
- 删除无用资源
- 使用shrinkResources压缩资源
- png转webp
- TinyPng压缩png
- So优化
- 及时移除无用so
- 精简so库
- 过滤so库
- 综合优化
- booster
- Android App Bundle
- 极限优化
- 使用 XZ Utils 进行 Dex 压缩
- 避免产生 Java access 方法
- AndResGuard
- AabResGuard
- ByteX
- 资源最小化配置
- 资源在线化
- 使用 XZ Utils 对 Native Library 进行压缩
- So 动态下载
- 插件化
- H5嵌入
- So 动态下载
- 插件化
- H5嵌入
- 总结
为什么要进行包体积优化?
- 包体积越大下载耗费流量越大,使用流量的用户可能会因耗费流量大而放弃下载
- 包体积足够小可以给用户更短的思考时间,用户在短暂的思考是否要取消下载时已经完成了下载,减少用户反悔
- 更小的包体积在同类app竞争中更占优势,用户更倾向于轻量级app
- 倘若app需要预装在渠道合作商的合作手机上,包体积更小更能让合作商接受并且单价成本更低
- 在用户手机存储空间不足时,包体积更小的app更能避免遭受卸载的风险
Apk的组成
想要对包体积大小进行优化,首先需要了解apk的组成,apk实际上是一个压缩包,把apk拖进Android Studio中可以看到如下结构:
- classes.dex: 主要包含能够被
Dalvik/ART
虚拟机理解的DEX
格式的class
文件 - res: 主要包含apk的资源文件
- resources.arsc: 一个二进制文件,存储Values类型的资源和图片、raw、xml资源的路径,R.java文件就是对这些资源进行索引
- AndroidManifest.xml: 该文件主要是 Android 应用清单文件
- META-INF: 主要包含
CERT.SF
和CERT.RSA
签名文件, 以及MANIFEST.MF
清单文- MANIFEST.MF:列出了apk所有文件和它所对应的base64-encoded SHA1 哈希值
- CERT.SF:和MANIFEST.MF类似,但是列出的却是MANIFEST.MF中每一条信息的hash值
- CERT.RSA:包含了对CERT.SF文件的数字签名以及签名时所用的platform.x509.pem这个数字证书
- lib: 如果使用了so库,就会生成这个目录,包含一些平台的 so 库, 可能有
armeabi
,armeabi-v7a
,arm64-v8a
,x86
,x86_64
这些分类等等,通常占着大量的空间,急需优化 - assets: 如果工程中用到了assets文件夹,才会打包进去,主要包含一些app需要内置的资源文件,通常为各种
mp3
文件、lottie
动画的图片资源和json
文件
优化方案
dex大小优化
删除无效代码
无效代码包括:
- 已注释代码
- 未使用方法
- 未使用类
- 重复工具类、重复功能代码块
- 已下架功能代码模块
操作方法:使用Android Studio
的Analyze -> Inspect Code
进行代码检测,可以查找未使用的代码,对于重复工具类和已下架功能代码等需要手动检查
优化效果:无效代码体积本身占比不大,优化很小
注意事项:
- 已注释代码如果是暂时屏蔽某个功能,或者是个公用的工具类方法,可以不清除,后续可能会再次用到
- 已下架功能代码确认之后不再使用,避免删除以后再次上架再次编写功能代码
D8编译
Google
在 Android Studio 3.1
版本之后使用D8
作为版本开发工具默认的Dex
编译器,D8
的 优化效果总的来说可以归结为如下四点:
- Dex的编译时间更短
- Dex文件更小
- D8 编译的 .dex 文件拥有更好的运行时性能。
- 包含 Java 8 语言支持的处理。
操作方法:在Android Studio 3.0
需要主动在gradle.properties
文件中新增:
android.enableD8 = true
Android Studio 3.1
或之后的版本D8
将会被作为默认的Dex
编译器
优化效果:Dex略微变小
参考链接:新一代Dex编译器:D8
Proguard混淆
Proguard
是一个免费的 Java 类文件压缩、优化、混淆、预先校验的工具,主要作用可以概括为 两点:
- 瘦身:它可以检测并移除未使用到的类、方法、字段以及指令、冗余代码,并能够对字节码进行深度优化。最后,它还会将类中的字段、方法、类的名称改成简短无意义的名字
- 安全:增加代码被反编译的难度,一定程度上保证代码的安全
操作方法:
buildTypes {release {// 1、是否进行混淆minifyEnabled true// 2、开启zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗zipAlignEnabled true// 3、移除无用的resource文件:当ProGuard 把部分无用代码移除的时候,// 这些代码所引用的资源也会被标记为无用资源,然后// 系统通过资源压缩功能将它们移除。// 需要注意的是目前资源压缩器目前不会移除values/文件夹中// 定义的资源(例如字符串、尺寸、样式和颜色)// 开启后,Android构建工具会通过ResourceUsageAnalyzer来检查// 哪些资源是无用的,当检查到无用的资源时会把该资源替换// 成预定义的版本。主要是针对.png、.9.png、.xml提供了// TINY_PNG、TINY_9PNG、TINY_XML这3个byte数组的预定义版本。// 资源压缩工具默认是采用安全压缩模式来运行,可以通过开启严格压缩模式来达到更好的瘦身效果。shrinkResources true// 4、混淆文件的位置,其中 proguard-android.txt 为sdk默认的混淆配置,// 它的位置位于android-sdk/tools/proguard/proguard-android.txt,// 此外,proguard-android-optimize.txt 也为sdk默认的混淆配置,// 但是它默认打开了优化开关。并且,我们可在配置混淆文件将android.util.Log置为无效代码,// 以去除apk中打印日志的代码。而 proguard-rules.pro 是该模块下的混淆配置。proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'signingConfig signingConfigs.release}
}
优化效果:如果全部功能开启,优化较好
参考链接:混淆工具:Proguard
R8优化
Google
在Android Studio 3.2
中引入R8
作为 ProGuard
的替代工具,用于代码的压缩(shrinking)和混淆(obfuscation),ProGuard
和R8
都应用了基本名称混淆:它们都使用简短,无意义的名称重命名类,字段和方法。他们还可以删除调试属性。但是,R8
在inline
内联容器类中更有效,并且在删除未使用的类,字段和方法上则更具侵略性。例如,R8
本身集成在ProGuard V6.1.1
版本中,在压缩 apk
的大小方面,与 ProGuard
的 8.5% 相比,使用 R8 apk 尺寸减小了约 10%。并且,随着 Kotlin
现在成为 Android 的第一语言,R8 进行了 ProGuard
尚未提供的一些 Kotlin
的特定的优化。
操作方法:如果我们当前使用的是Android Studio 3.4
或 Android Gradle
插件3.4.0
及其更高版本,R8 会作为默认编译器。否则,我们 必须要在 gradle.properties
中配置如下代码让 App 的混淆去支持 R8:
android.enableR8=true
android.enableR8.libraries=true
优化效果:Proguard
的升级版本,优化较好
资源优化
删除无用资源
APK 的资源主要包括图片、XML,由于版本更新迭代,会遗留很多旧版本使用但新版本不再使用的资源文件,这些资源文件也会占用空间,需要删除
操作方法:选中菜单项Refactor
,然后点击Remove Unused Resource => preview
可以预览找到的无用资源,点击 Do Refactor
可以去除冗余资源
优化效果:无用资源越多优化越好
注意事项:可能需要手动再次确认资源真实未使用,不能使用一键删除,否则会导致某些特殊情况下使用的资源找不到
使用shrinkResources压缩资源
Proguard
混淆自带的资源压缩
操作方法:在Proguard
混淆中开启
shrinkResources true
优化效果:优化效果一般
png转webp
WebP 是 Google 的一种可以同时提供有损压缩(像JPEG
一样)和透明度(像 PNG
一样)的图片文件格式,不过与JPEG
或PNG
相比,这种格式可以提供更好的压缩。Android 4.0
(API 级别 14)及更高版本支持有损 WebP
图片,Android 4.3
(API 级别 18)及更高版本支持无损且透明的 WebP
图片
操作方法:在Android Studio
中右键点击某个图片文件或包含一些图片文件的文件夹,然后点击 Convert to WebP
优化效果:如果png
没压缩过,优化效果很大,如果png
已经压缩过了,也依然有一定的优化作用
注意事项:
- 由于只有
Android 4.3
及更高版本支持无损和透明的webp
图片,因此minSdkVersion
必须为18或者更高 9-patch
文件无法转换为WebP
图片,转换器工具会自动跳过9-patch
图片- 转换过程中需要查看图片是否存在异常
Google play
不允许app
图标使用非png
格式,因此不能对app图标进行转换
TinyPng压缩png
TinyPNG
使用智能有损压缩技术将PNG
文件的文件大小降低。 通过选择性的减少图片中的颜色,只需要很少的字节数就能保存数据。 对视觉的影响几乎不可见,但是在文件大小上有非常大的差别
操作方法:
Android Studio
下载TinyPng Image Optimizer
插件- 选中需要压缩的
png
图片,右键选中Optimize Image Size
- 点击
Optimize
即可
优化效果:较大,但比不上转webp
参考链接:TinyPng
So优化
及时移除无用so
在功能迭代后,会有一些so库不再使用,必须尽快移除,因为so占体积比很大,特别是分多个abi的情况下
优化效果:so越大,优化效果越大
精简so库
提取so库功能,精简so库中无用代码,可以进一步减小so库的大小
优化效果:一般
过滤so库
目前,Android 一共 支持7种不同类型的 CPU 架构,比如常见的 armeabi、armeabi-v7a、X86
等等。理论上来说,对应架构的 CPU 它的执行效率是最高的,但是这样会导致 在lib
目录下会多存放了各个平台架构的 So 文件,所以 App 的体积自然也就更大了,因此需要对lib目录进行精简。一般情况下,应用都不需要用到 neon
指令集,我们只需留下 armeabi
目录就可以了。因为armeabi
目录下的 so 可以兼容别的平台上的 so,相当于是一个万金油,都可以使用。但是,这样 别的平台使用时性能上就会有所损耗,失去了对特定平台的优化,如果不考虑性能,可以直接配置:
defaultConfig {ndk {abiFilters "armeabi"}
}
但这样做会影响性能,所以可以考虑一个折中的方案:对于性能敏感的模块,它使用到的 so,即使是属于其他abi
,也都放在 armeabi
目录当中随着 Apk 发出去,然后在代码中来判断一下当前设备所属的 CPU 类型,根据不同设备 CPU 类型来加载对应架构的 So 文件:
String abi = "";
// 获取当前手机的CPU架构类型
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {abi = Buildl.CPU_ABI;
} else {abi = Build.SUPPORTED_ABIS[0];
}
if (TextUtils.equals(abi, "x86")) {// 加载特定平台的So} else {// 正常加载}
综合优化
booster
滴滴在 Github 上开源了一个 Android App 的质量优化工具Booster
,Booster
是一款专门为移动应用设计的易用、轻量级且可扩展的质量优化框架,其目标主要是为了解决随着 APP 复杂度的提升而带来的性能、稳定性、包体积等一系列质量问题。Booster 提供了性能检测、多线程优化、资源索引内联、资源去冗余、资源压缩、系统 Bug 修复等一系列功能模块,可以使得稳定性能够提升 15% ~ 25%,包体积可以减小 1MB ~ 10MB。只为了优化包体积大小,可以选择性集成部分功能
操作方法:
-
在项目根目录的
build.gradle
中配置:buildscript {ext.booster_version = '3.1.0'repositories {google()mavenCentral()}dependencies {//booster基础插件classpath "com.didiglobal.booster:booster-gradle-plugin:$booster_version" //采用 cwebp 对资源进行压缩classpath "com.didiglobal.booster:booster-task-compression-cwebp:$booster_version"//ap_ 文件压缩classpath "com.didiglobal.booster:booster-task-compression-processed- res:$booster_version"//去冗余资源classpath "com.didiglobal.booster:booster-task-resource-deredundancy:$booster_version"//资源索引内联classpath "com.didiglobal.booster:booster-transform-r-inline:$booster_version"} } }
-
在主工程模块的
build.gradle
中引入插件:apply plugin: 'com.didiglobal.booster'
-
在
gradle.properties
中配置:android.precompileDependenciesResources=false
优化效果:很大,建议使用
注意事项:采用这个插件会增加编译时间,因此在调试开发阶段不应该开启,可以添加以下判断,只在打publish包的时候才使用:
ext.isPublish = gradle.startParameter.taskNames.any { it.contains('publish') || it.contains('Publish') }
classpath "com.didiglobal.booster:booster-gradle-plugin:$boosterVersion"
if (isPublish) {classpath "com.didiglobal.booster:booster-task-compression-cwebp:$boosterVersion"classpath "com.didiglobal.booster:booster-task-compression-processed-res:$boosterVersion"classpath "com.didiglobal.booster:booster-task-resource-deredundancy:$boosterVersion"classpath "com.didiglobal.booster:booster-transform-r-inline:$boosterVersion"
}
参考链接:质量优化框架:booster
Android App Bundle
Android App Bundle
是一种发布格式,其中包含应用的所有经过编译的代码和资源,它会将 APK 生成及签名交由 Google Play 来完成。Google Play 会使用 app bundle 针对每种设备配置生成并提供经过优化的 APK,因此只会下载特定设备所需的代码和资源来运行应用。不必再构建、签署和管理多个 APK 来优化对不同设备的支持,而用户也可以获得更小且更优化的下载文件包,这在Google play上架的应用来说是非常有效的
操作方法:
- 使用
Android Studio 3,2
或以上版本 - 选择
Build
菜单,右键选择Build Bundles(s)/Apk(s)
,然后点击Build Bundle(s)
- build成功后可以选择
Generate Signed Bundle or Apk
对生成的aab
文件进行签名,需要进行秘钥的配置 - 如果需要测试aab,需要下载
buildtool
进行安装测试,或者把aab文件上传到google play
控制台的测试通道进行测试
优化效果:非常大,Google play上架的app极力推荐
注意事项:
- 在 2021 年下半年,新应用将需要使用
Android App Bundle
才能在 Google Play 中发布
参考链接:Android App Bundle
极限优化
以下优化方式存在各种各样的问题,看自己情况使用
使用 XZ Utils 进行 Dex 压缩
XZ Utils
是具有高压缩率的免费通用数据压缩软件。XZ Utils
是为类似POSIX
的系统编写的,但也可以在某些非POSIX
系统上使用。XZ Utils
是LZMA Utils
的后继产品。XZ Utils
压缩代码的核心基于LZMA SDK
,但是已经对其进行了大量修改以适合XZ Utils
。当前,主要压缩算法是LZMA2
,它在.xz
容器格式内使用。对于典型文件,XZ Utils
的输出比gzip
小30%,比bzip2
小15%
存在的问题
- 当文件较大时,在低端机可能存在3-5s的解压时间
- 当 Dex 非常多的时候会增加应用的安装时间,如果还使用了压缩 Dex 的方式,那么首次生成 ODEX 的时间可能就会超过1分钟
参考链接:XZ Utils
避免产生 Java access 方法
为了能提供内部类和其外部类直接访问对方的私有成员的能力,又不违反封装性要求,Java 编译器在编译过程中自动生成 package 可见性的静态 access$xxx
方法,并且在需要访问对方私有成员的地方改为调用对应的 access 方法。在开发过程中需要注意在可能产生 access 方法的情况下适当调整,比如去掉 private,改为 package 可见性,也可以使用ASM
或者ByteX
在编译时删除生成的 access 方法。
存在的问题:效果很小但使用相对麻烦
AndResGuard
AndResGuard
是微信团队开源出来的一个注重减小Res包大小的工具,可以直接对Apk进行处理得到处理后的Apk文件。原理类似Java Proguard
,但是只针对资源。他会将原本冗长的资源路径变短,例如将res/drawable/wechat
变为r/d/a
。AndResGuard
不涉及编译过程,只需输入一个apk(无论签名与否,debug版,release版均可,在处理过程中会直接将原签名删除),可得到一个实现资源混淆后的apk(若在配置文件中输入签名信息,可自动重签名并对齐,得到可直接发布的apk)以及对应资源ID的mapping文件
apply plugin: 'AndResGuard'
buildscript {repositories {jcenter()google()}dependencies {classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.20'}
}andResGuard {// mappingFile = file("./resource_mapping.txt")mappingFile = nulluse7zip = trueuseSign = true// 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字keepRoot = false// 设置这个值,会把arsc name列混淆成相同的名字,减少string常量池的大小fixedResName = "arg"// 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源mergeDuplicatedRes = truewhiteList = [// for your icon"R.drawable.icon",// for fabric"R.string.crashlytics.*",// for google-services"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"]compressFilePattern = ["*.png","*.jpg","*.jpeg","*.gif",]sevenzip {artifact = 'com.tencent.mm:SevenZip:1.2.20'//path = "/usr/local/bin/7za"}/*** 可选: 如果不设置则会默认覆盖assemble输出的apk**/// finalApkBackupPath = "${project.rootDir}/final.apk"/*** 可选: 指定v1签名时生成jar文件的摘要算法* 默认值为“SHA-1”**/// digestalg = "SHA-256"
}
参考链接:Apk混淆工具:AndResGuard
AabResGuard
节跳动抖音技术团队开源的一款针对 .aab
文件的资源混淆工具,也就是使用app bundle
打包的应用,由于 .aab
和 .apk
文件结构的差异,AndResGuard 的资源混淆方案是不适用于 AAB 的,需要使用它的另一个版本,也就是AabResGuard,使用方式:
在 build.gradle(root project)
中进行配置:
buildscript {repositories {mavenCentral()jcenter()google()}dependencies {classpath "com.bytedance.android:aabresguard-plugin:0.1.0"}
}
在 build.gradle(application)
中配置
apply plugin: "com.bytedance.android.aabResGuard"
aabResGuard {mappingFile = file("mapping.txt").toPath() // 用于增量混淆的 mapping 文件whiteList = [ // 白名单规则"*.R.raw.*","*.R.drawable.icon"]obfuscatedBundleFileName = "duplicated-app.aab" // 混淆后的文件名称,必须以 `.aab` 结尾mergeDuplicatedRes = true // 是否允许去除重复资源enableFilterFiles = true // 是否允许过滤文件filterList = [ // 文件过滤规则"*/arm64-v8a/*","META-INF/*"]enableFilterStrings = false // 过滤文案unusedStringPath = file("unused.txt").toPath() // 过滤文案列表路径 默认在mapping同目录查找languageWhiteList = ["en", "zh"] // 保留en,en-xx,zh,zh-xx等语言,其余均删除
参考链接:Aab混淆工具:AabResGuard
ByteX
ByteX
是字节跳动抖音团队开发的一个基于gradle transform api
和ASM
的字节码插件平台。目前集成了若干个字节码插件,每个插件完全独立,既可以脱离ByteX
这个宿主而独立存在,又可以自动集成到宿主和其它插件一起整合为一个单独的Transform
。插件和插件之间,宿主和插件之间的代码是完全解耦的(有点像组件化),这使得ByteX
在代码上拥有很好的可拓展性,新插件的开发将会变得更加简单高效
buildscript {ext.plugin_version="0.2.6"repositories {google()jcenter()}dependencies {classpath "com.bytedance.android.byteX:base-plugin:${plugin_version}"// Add bytex plugins' dependencies on demand. 按需添加插件依赖classpath "com.bytedance.android.byteX:refer-check-plugin:${plugin_version}"// ...}
}
apply plugin: 'com.android.application'
// apply ByteX宿主
apply plugin: 'bytex'
ByteX {enable trueenableInDebug falselogLevel "DEBUG"
}
// 按需apply bytex 插件
apply plugin: 'bytex.refer_check'
// ...
已集成的插件
- access-inline-plugin(access方法内联)
- shrink-r-plugin(R文件瘦身和无用资源检查)
- closeable-check-plugin(文件流的close检查)
- const-inline-plugin(常量内联)
- field-assign-opt-plugin(优化多余赋值指令)
- getter-setter-inline-plugin (getter和setter方法内联)
- method-call-opt-plugin(干净地删除某些方法调用,如
Log.d
) - coverage-plugin(线上代码覆盖率)
- refer-check-plugin(检查是否有调用不存在的方法和引用不存在的字段)
- serialization-check-plugin(序列化检查)
- SourceFileKiller(删除SourceFile和行号属性,作为示例)
- ButterKnifeChecker(检测跨module使用ButterKnife可能导致的问题)
- RFileKnife(修复R.java太大编译报code too large的问题)
存在的问题:
- 本身是为了插件扩展而做的插件平台,如果只用作瘦身,有点大材小用,并且对瘦身的优化不是很大
- 可以扩展插件功能,但是有一定的使用难度
参考链接:ByteX
资源最小化配置
根据 App 目前所支持的语言版本去选用合适的语言资源和图片大小
android {...defaultConfig {...resConfigs "zh", "zh-rCN"resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}...
}
存在问题:优化不大,并且可能会导致适配兼容出现问题
资源在线化
将一些图片资源放在服务器,然后 结合图片预加载 的技术手段,这些 既可以满足产品的需要,同时可以减小包大小
存在问题:用户网络不好的时候体验不佳
使用 XZ Utils 对 Native Library 进行压缩
Native Library
同 Dex
一样,也可以使用XZ Utils
进行压缩,对于 Native Library
的压缩,我们 只需要去加载启动过程相关的Library
,而其它的都可以在应用首次启动时进行解压,并且,压缩效果与 Dex
压缩的效果是相似的
存在问题:和压缩Dex一样,对应用启动时间影响较大
So 动态下载
将部分 So 文件使用动态下发的形式进行加载,也就是在业务代码操作之前,可以先从服务器下载下来 So,接下来再使用
存在问题:用户网络不好时体验不佳
插件化
H5嵌入
So 动态下载
将部分 So 文件使用动态下发的形式进行加载,也就是在业务代码操作之前,可以先从服务器下载下来 So,接下来再使用
存在问题:用户网络不好时体验不佳
插件化
H5嵌入
总结
根据对各种方法的分析,筛选出较为实用和常用的方法如下:
- 删除无用代码和无用资源文件
- 开启
proguard
混淆 - 开启
D8
和R8
- 使用
webp
格式或TinyPng
压缩png
图片 - 使用
booster
插件优化 - 使用
Android App Bundle
方式打包
备选方法:
- 过滤so库
- AndResGuard
- AabResGuard
- ByteX
更多推荐
App包体积全面瘦身
发布评论