APK 瘦身的主要原因是考虑应用的下载转化率和留存率,应用太大了,用户可能就不下载了。再者,因为手机空间问题,用户有可能会卸载一些占用空间比较大的应用,所以,应用的大小也会影响留存率。
包含以下目录:
包含以下文件:
主要是针对 lib/ 、res/ 和 classes.dex 进行瘦身:
图片优化主要是针对多套图片资源的问题。
Android 常见分辨率
因为 APP 在加载图片时会优先加载对应分辨率文件夹下的图片,如果在对应的分辨率文件夹下没有所要的图片,就会找高分辨率文件夹下的图片。那么,如果我们把所有的图片都放在高分辨率的文件夹下是不是就可以了呢?并不是,这样做会导致低分辨率手机加载图片时会消耗更多的内存。
2017年后,Android 手机一般大小在 5 寸以上,分辨率至少是 720p,1080p,所以对应的 dpi(Dots Per Inch 每英寸点) 分别为:
对于绝大多数 APP 来说,只需要取一套设计图就足够了。鉴于现在分辨率的趋势,建议取 720p 的资源,放在 xhdpi 目录。相对于多套资源,只使用 720P 的一套资源,在视觉上差别不大,很多大公司的产品也是如此,但却能显著的减少资源占用大小,身边也能减轻设计师的出图工作量了。
注意,这里不是说把不是 xhdpi 的目录都删除,而是强调保留一套设计资源就够了。
TinyPNG 工具只支持上传 PNG 图片到官网上压缩,然后下载保存,在保持 alpha 通道的情况下对 PNG 的压缩可以达到 1/3 之内,而且用肉眼基本上分辨不出压缩的损失。
TinyPNG 的官方网站:http://tinypng.com/
如果对于非透明的大图,jpg 将会比 png 的大小有显著的优势,虽然不是绝对的,但是通常会减小一般都不止。
在启动页,活动页等之类的大图展示区采用 jpg 将是非常明智的选择。
webp 支持透明度,压缩比 jpg 更高且显示效果却不输 jpg,官方评测 quality 参数等于 75 均衡最佳。
相对于 jpg、png,webp 作为一种新的图片格式,限于 android 的支持情况暂时还没用在手机端广泛引用起来。从 Android 4.0 开始原生支持,但不支持包含透明度,直到 Android 4.2.1+ 才支持显示含透明度的 webp,使用的时候要特别注意。
官方介绍:https://developers.google.com/speed/webp/docs/precompiled
如果经过上述步骤之后,你的工程里面还有一些大图,考虑是否有必要维持这样的大尺寸,是否能适当的缩小。事实上,由于设计师出图的原因,我们拿到的很多图片完全可以适当的缩小而对视觉影响是极小的。
有些第三方库里引用了一些大图但是实际上并不会被我们用到,就可以考虑用 1x1 的透明图片覆盖。这样会导致 drawable 文件下包含了一些莫名其妙的名称的 1x1 图片…
对于位图图像来说,其的大小是固定的,分辨率低,占的内存空间小,分辨率高,占的内存空间大。
SVG,SVG 意为可缩放矢量图形(Scalable Vectors Graphics),使用 XML 格式定义图像,适用简单的小图标。
首先在 res 包目录下点击 New —> Vector Asset:
进入 Configure Vector Asset 窗口:
选择 Local file (SVG, PSD),选择要导入的 svg 图片:
点击 Next —> Finish:
导入成功:
使用:
在 apk 包的 drawable 目录下是 .xml 文件而不是 .png:
使用 svg 可以不用考虑图片的分辨率、大小、颜色等信息。.xml 的性能比 .png 更好,占用内存更少,转换成机器码的效率更高。
如果 SVG 文件包含不受支持的功能,将在 Vector Asset Studio 的底部显示一个错误提示。不支持的功能:
Tint 着色器:可以直接在 xml 文件中修改矢量图的颜色,但是并不建议直接修改,我们一般用 Tint 着色器去修改矢量图的颜色:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#00ff00" android:state_pressed="true" />
<item android:color="@android:color/black" />
selector>
<ImageView
android:id="@+id/iv"
android:layout_width="60dp"
android:layout_height="60dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/fanhui"
app:tint="@color/selector_back_color" />
使用 .svg 格式的图片,转换成 vector 格式的文件,不同大小,不同颜色的图片只需要一个文件就可以了实现了。
矢量图是由点与线组成,和位图不一样,它再放大也能保持清晰度,而且使用矢量图比位图设计方案能节约 30~40% 的空间,现在谷歌一直在强调扁平化方式,矢量图可很好的契合该设计理念。
优势
劣势
在 Android 开发中,so 库是不可或缺的。so 库指的是动态链接库,也就是在运行时载入内存的库文件。在 Android 应用程序中使用 so 库可以大大降低内存的使用,提高系统的性能。so 库文件的产生主要有两种方式:
so 库是由 ndk 编译出来的动态库,是 C/C++ 写的,所以不是跨平台的,即每一个平台需要使用对应的 so 库。
ABI 是应用程序二进制接口简称(Application Binary Interface),定义了二进制文件(尤其是 .so 文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。
在 Android 系统上,每一个 CPU 架构对应一个 ABI:arm64-v8a,armeabi-v7a,armeabi,x86_64,x86,mips64,mips。现在我们一般只需要配置 arm64-v8a。
在 build.gradle 构建脚本中,配置 ndk 编译的动态库 CPU 架构类型:
android {
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
}
}
按照以上配置,打包时会将 4 种 CPU 架构的动态库都配置到 APK 中。事实上,绝大多数的应用都不需要配置全架构的动态库,arm64-v8a 架构的 CPU 可以向下兼容:
android {
defaultConfig {
ndk {
abiFilters "arm64-v8a"
}
}
}
Android Studio 给我们提供了一键移除所有无用的资源,如下所示:
但是这种方式不建议使用,因为如果某资源仅存在动态获取资源 id 的方式,那么这个资源会被认为没有使用过,从而会直接被删除。
动态获取的方式:getResources().getIdentifier(“name”, “defType”, getPackageName())
另外一种方式是通过 Analyze Code 手动移除无用资源:
搜索 unused resources:
选择搜索范围:
无用资源:
buildTypes {
debug {
minifyEnabled false // 不做混淆
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true // 做混淆
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
minifyEnabled 用来开启删除无用代码,比如没有引用到的代码。
shrinkResources 用来开启删除无用资源,也就是没有被引用的文件(drawable、layout),实际上并没有删除,只是保留文件名,但是没有内容。但是需要知道资源是否被引用,需要配合 minifyEnabled 来使用,只有当两者都为 true 的时候才能真正的删除无效代码和无用资源。
资源压缩只与代码压缩协同工作。
默认情况未启用严格模式(严格模式是指清除掉资源本身,非严格模式指的是只清除资源内容),如需启动则设置 shrinkMode,创建 keep.xml,如下:
<resources xmlns:android="http://schemas.android.com/tools"
tools:shrinkMode="strict" >
如果开发者想要特定保留或者必须移除的资源,可以进行自定义配置:
<resources xmlns:tools="http://schemas.android.com/tools"
tools:discard="@color/selector"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:shrinkMode="strict">
resources>
Java 加载 so 库的方式有两种:
//加载的是 jni_mix.so,注意的是这边只需要传入 "jni_mix"
static {
System.loadLibrary("jni_mix");
}
//传入的是 so 文件完整的绝对路径
System.load("/data/data/[packagename]/lib/jni_mix.so")
静态加载会导致 apk 包比较大,所以采用动态加载 so 库的形式,也就是从网络上下载,放入本地数据目录下。这样做的好处是不仅减小了 apk 的大小,而且可以随时使用最新的依赖库,这也是动态加载的最多的用途之一。
用动态加载技术,编译前不把 so 文件放入 jniLibs 目录(原因很多,比如想减少安装包的大小),自然打包生成的安装包也不包含该 so 库。接着在手机上安装这个 apk 并启动 APP,如果 APP 的运行不涉及到 jni 方法的调用,那就当 so 不存在;如果 APP 打开某个页面,而该页面又需要调用 jni 方法,则 APP 自动到指定地址下载需要的 so 文件,然后保存到用户目录,并从用户目录加载该 so,最后再调用 jni 方法。
步骤一:下载 so;
步骤二:拷贝 so 至私有(data)目录;
步骤三:通过绝对路径加载 so;
通过插桩式来实现加载插件,AssetManager 加载资源和 java 文件
插件化开发就是将整个 APP 拆分成很多模块,每个模块都是一个 APK,最终打包的时候将宿主 APK 和插件 APK 分开打包,插件 APK 通过动态下发到宿主 APK。
插件可以放到服务器加载。
在 resources.arsc 文件下可以查看支持的语言:
大部分应用其实并不需要支持几十种语言的国际化支持。比如只支持中文、英文:
android {
defaultConfig {
resConfigs "zh", "en" // 支持中文、英文
}
}
去除无用资源之后:
特别是在扁平化盛行的当下,很多纯色的渐变的圆角的图片都可以使用 shape 实现,代码灵活可控,省去了大量的背景图片。
相信你的功能里面也有很多 selector 文件,也有很多相似的图片只是颜色不同,通过着色方案我们能大大减轻这样的工作量,减少这样的文件。
借助 android support 库乐意实现一个全版本兼容的着色方案,参考代码 DrawableLess.java
如果你的 APP 支持素材库(比如聊天表情库)的话,考虑在线加载模式,因为往往素材库都有不小的体积。
这一步需要开发者实现在线加载,一方面增加代码的复杂度,一方面提高了 APP 的流量消耗,建议酌情选择。
避免重复库
避免重复库看上去是理所当然的,但是秘密总是藏得很深,一定要当心你引用的第三方库又引用了哪个第三方库,这就很容易出现重复的库了,比如使用了两个图片加载库:Glide 和 Picasso。
通过查看 exploded-aar 目录和 External Libraries 或者反编译生成的 APK,尽量避免重复的库的大小,减小 APP 大小。
版本迭代过程中,因为删减功能经常有冗余代码和第三方库留下,这或多或少都会增加包体,这种情况下没有捷径,只能每个文件查找,这是苦力活。还有要查看第三方库有没有可能精简,比如谷歌分基础、广告和分析包,网络库、supportv4 等,这个就具体情况具体分析了,不多阐述。
微信资源压缩打包工具通过短资源名称,采用 7zip 对 APP 进行极致压缩实现减小 APP 的目标,效果非常好,强烈推荐。
建议开启 7zip,注意白名单的配置,否则会导致有些资源找不到,官方已经发布 AndResGuard 到 gradle 中了,非常方便。
对于一些库是按照需要动态加载的,可能在某些版本并不需要,但是代码又不方便去除否则编译不过。
对于 provided 可以保证代码编译通过,但是实际打包中并不引用第三方库,实现了控制 APP 大小的目标。
但是也同时就需要开发者自己判断不引用这个第三方库时就不要执行到相关的代码,避免 APP 崩溃。
【Android】浅谈APP的瘦身之路
Android SO库的详细阐述
安卓APK安装包arm64-v8a、armeabi-v7a、x86、x86_64有何区别?如何选择?
【转】android中的armeabi、armeabi-v7a、arm64-v8a及x86等
【Android 安装包优化】动态库打包配置 ( “armeabi-v7a“, “arm64-v8a“, “x86“, “x86_64“ APK 打包 CPU 指令集配置 | NDK 完整配置参考 )