前言
本文旨在介绍常见的优化APK体积大小的常用方法,截图介绍多转至别人博客,我只稍微整理。
APK构成
在讨论如何减小apk大小之前,理解apk的结构很有必要。一个APK文件包括一个ZIP 文件,该ZIP包含app的所有文件。包括java 字节码文件,资源文件和一个包含了编译后的资源文件。APK包含以下目录:
-
META-INF/
:包含了CERT.SF
和CERT.RSA
签名文件, 以及MANIFEST.MF
manifest 文件. -
assets/
: 包含了app的assets,app可以通过AssetManager
对象获取到这些资源 -
res/
: 包含了那些没有被编译到resources.arsc
的资源 -
lib/
: 包含了用于软件处理器的编译代码,该目录包含一个子目录,针对不同平台:armeabi
,armeabi-v7a
,arm64-v8a
,x86
,x86_64
, andmips
.
一个APK也包含了下面的文件,但只有 AndroidManifest.xml
是强制性的
resources.arsc
:
包含了编译后的资源。该文件包含了res/values/
文件夹下的所有XML内容。打包工具抽取了XML内容,将它编译成二进制格式,并且进行了压缩。该内容包括language strings和styles,以及未直接包含在resources.arsc
文件中的内容路径。比如layout文件和图片。classes.dex
:
包含可以被Dalvik/ART 识别,以dex文件格式编译后的代码AndroidManifest.xml
:
包含了Android核心mainfest文件。该文件罗列了app名字,版本,访问权限,和引用的library文件。该文件采用二进制XML格式。
使用APK Analyser 分解你的 APK
Android Studio 提供了一个有用的工具:APK Analyser。APK Analyser 将会拆解你的应用并让你知道 .apk 文件中的那个部分占据了大量空间。进入Android Studio的菜单中选择Analyze->Inspecting Code即可,让我们看一下在没有经过优化之前的截图:
从 Apk Analyser 的输出来看,应用的原大小是 24.4MB。经过 Play 商店的压缩,大致是 23.3MB。
从截图中可以看出主要有 3 个文件夹占据了应用的大多数空间。
- lib — 用来存放一些用到第三方的jar包和so文件
- classes.dex — 这是 dex 文件,包含了所有会运行在你的 DVM 或 ART 里的字节码文件。
- res — 这个文件夹包含了所有在 res 文件夹下的文件。大部分情况下它包含所有图片,图标和源文件,菜单文件和布局。
减小 classes.dex
classes.dex 包含了所有 Java 代码。当你编译你的应用时,gradle 会将你的所有模块里的 .class 文件转换成 .dex 文件并将这些文件合成一个 classes.dex 文件。
单个的 classes.dex 文件可以容纳大约 64K 方法。如果你达到了这个限制,你必须要在你的工程中启用 multidexing。这将会创建另一个 classes1.dex 文件去存储剩下的方法。所以 classes.dex 文件数目由你的方法数而定。
你可以看到现在的我的应用包含 7548 个类和 52763 个方法。这个结果是没有经过混淆的。你有两个默认的混淆文件。
- proguard-android.txt
- proguard-rules.pro
release {
//Enable the proguard
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard-rules.pro"
//...
}
就像文件名写的那样,“proguard-android.txt”是更积极的混淆选项。我们将这个作为默认的混淆配置。你可以在 /app 目录下的 proguard-rules.pro 里添加自定义的混淆配置。
proguard-android.txt这个默认混淆配置位置是在:sdk目录下 ~sdk\tools\proguard\proguard-android.txt
通过设置 minifyEnabled 为 true,混淆将会移除所有未使用的方法、指令以减小 classes.dex 文件。
这是启用了 minify 之后的 APK:
减小 res
下一大块就是 res 文件夹,它包括了所有的图片,raw 文件和 XML。你不能添加/删除/修改你的 XML,因为它们包含了你的布局。但是我们可以减小图片文件。
- “shrinkResources” 属性将会移除所有在工程中没有用到的资源。在 build.gradle 中像下面这样启用它:
release{
//...
//...
shrinkResources true
//...
}
- “resConfigs” 属性将会在构建过程中移除所有本地化资源。添加下面的这些代码让应用只支持中文。
defaultConfig {
//...
//...
//...
//strip other than english resources
resConfigs "zh"
}
- 你可以使用 webp 替代 png
如果你在用 Android Studio 2.3,并且你的应用的最低支持版本大于 18,你可以使用 webp 替代 png。webp 图片比 png 体积更小但质量一样。而且 Android 支持 webp。所以你可以在 ImageView 中像加载其它光栅图片一样加载 webp 图片。这不需要改变你的布局。
你可以在工程选择 drawable 和 mipmap 文件夹,右击并选择 convert to webp。这将会打开下面这样的配置弹框。
点击 ok,将会将所有 png 图片转成 webp。如果 webp 图片比 png 更大,Android Studio 将会自动跳过这个文件。
只支持特定的分辨率
Android支持非常大的设备集,包括各种屏幕密度。 在Android 4.4(API级别19)及更高版本中,框架支持各种分辨率:ldpi,mdpi,tvdpi,hdpi,xhdpi,xxhdpi和xxxhdpi。 虽然Android支持所有这些分辨率,但你不需要导出光栅化资源到每种分辨率。
如果你知道只有一小部分用户使用特定分辨率的设备,请考虑是否需要支持这些分辨率。 如果你不包括特定屏幕密度的资源,Android会自动缩放最初为其他屏幕密度设计的现有资源。
如果您的应用只需要缩放的图片,您可以通过在drawable-nodpi /中使用图片的单个版本来节省更多空间。 我们建议每个应用至少包含一个xxhdpi图片版本。
so的优化
我们在接入百度地图的时候,发现需要引入很多很多so
这些so文件占了很多大体积,如果你不加控制,所有的so都会打包到你的apk了,最后发现这些so文件尽然占了我们apk的近乎三分之一的体积。然而考虑下我们的用户,基本都是跑在手机上的(没有人跑在模拟器上),所以明显x86和x86_64的so是不需要支持,那么我们可以通过配置gradle来制定只打包某些so,依然是在defualtConfig中:
defaultConfig {
... ...
ndk {
//设置支持的SO库架构
abiFilters 'arm64-v8a', 'armeabi' //, 'x86', , 'x86_64', 'arm64-v8a'
}
}
最后打包出来的apk真的是减少了好几个MB,这是太好了
当然如果有一些so是你们自己开发的,那么可以参考这个文章来参考如果用ndk开发的时候减少so本身的体积,这里就不过多介绍了
https://blog.algolia.com/android-ndk-how-to-reduce-libs-size/
对第三方库进行重新定制(重新打jar包)
开发中引入大量的第三方开发库也是一个增加apk体积的重要原因,因为你把人家的代码和资源全给包含进来了。但是想想人家的代码,并不一定全要的,是否可以只引入人家的一部分代码,而不是在build.gradle中仅仅添加一行“compile”来全部依赖呢?答案是可以得!这里举一个例子
我们开发中有一个需求是将数据通过图标的方式显示出来,这里我们站在巨人的肩膀上,使用了MPAndroidChart这个优秀的开源项目(https://github.com/PhilJay/MPAndroidChart),但是发现他们的东西太多了,我们仅仅需要使用其中一种chart。如果在build.gradle里面加一句:
dependencies {
compile 'com.github.PhilJay:MPAndroidChart:v2.2.3'
}
这要把他们的库全给引用过来了。想到他们是开源的,代码有,所以我们仅仅把他们的我们所用到的代码给剥离出来,单独打包了一个jar包引入到我们的项目里面,就OK了,减少了大量的无用依赖代码!