随着项目的不断迭代,功能越来越多,构建出来的apk文件的大小也会越来越大,这样会导致在移动网络情况下下载时,使用的网络流量会增大,并且apk太大,导致下载的时间也增加,虽然当前每个人的手机的流量都很多,对用户流量影响不大,但是据一些网站统计,安装包越大,用户的转化率是在降低的,所以减少apk的体积,可以让更多的用户愿意去下载和体验产品。所以,对apk体积进行瘦身还是很有必要的。在对apk体积进行瘦身前,最好保证这个apk已经是经过Proguard优化过的。
经过了Proguard对apk进行压缩,优化,混淆后,在去对这个已经进行优化过的apk进行瘦身更有意义。
下面是项目总常用的对apk进行优化的方式:
在app的主module下的gradle文件中做如下配置
buildTypes {
release {
//开启代码混淆
minifyEnabled true
//Zipalign优化
zipAlignEnabled true
//移除无用的resource文件
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
使用 shrinkResources 进行移除,配合 //Zipalign优化
使用 shrinkResources 必须先开启代码混淆 minifyEnabled
关于混淆相关的配置,不是本文重点,下面进行简单介绍
proguard-android.txt文件中的常用的混淆配置信息如下:
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
native <methods>;
}
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
# The support library contains references to newer platform versions.
# Dont warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
-dontusemixedcaseclassnames 表示混淆时不使用大小写混合类名。
-dontskipnonpubliclibraryclasses 表示不跳过library中的非public的类。
-verbose 表示打印混淆的详细信息。
-dontoptimize 表示不进行优化,建议使用此选项,因为根据proguard-android-optimize.txt中的描述,
优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行
-dontpreverify 表示不进行预校验。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,
去掉之后还可以加快混淆速度。
-keepattributes Annotation 表示对注解中的参数进行保留。
更多关于proguard混淆规则的介绍,请参考Android安全攻防战,反编译与混淆技术完全解析(下)
大部分应用其实并不需要支持几十种语言的国际化支持,比如国内应用只支持中文,配置如下:
defaultConfig {
...
//只保留指定和默认的资源
//resConfigs('zh-rCN','ko')
resConfigs "zh"
}
经过上面的配置后,打包出的apk,在进行apk的分析,这样效果会更好。Android studio 从2.2开始就提供了分析apk文件的功能,在Android Studio工具栏里,打开build–>Analyze APK, 选择要分析的APK包,或者直接将apk包拖到AS中。下图是对apk分析的结果:
但是在看上面这个图之前,需要先了解各个部分的含义:
lib
这个目录存放应用程序依赖的用C/C++ 编写的 native 库文件,该目录下可以包含 3 种类型,根据 CPU 类型的不同加载不同目录下的 so 库,这3种类型分别为 ARM 架构、MIPS 架构、 X86 架构。所示,不同的 CPU 架构设备在应用程序运行时,根据 CPU 架构类型加载对应的目录,每个目录可以存放很多对应版本的 so 库,同时这个目录结构固定,用户必须严格按照这个目录存放自己的 so文件。
assets
assets目录可以根据应用需求存放任何文件夹架构,如配置文件、资源文件(如 WebView本地资源、图片资源等),这些文件的内容在程序运行过程中可以通过 AssetManager 类获得。和 res 的不同点在于, res 目录下的文件会在 .R 文件中生成对应的资源 ID assets 不会自动生成对应的 ID ,而是通过 AssetMana ger 类的接口获取。
res
res是 resource 的缩写,这个目录存放资源文件,在这个文件夹下的所有文件 都会生成对应的 ID 映射到 Android 工程的 .R 文件中,访问时可以直接使用资源。
classes.dex
Java可执行程序,需要先把 Java 文件编译成 class 文件,字节码都保存在 class 文件中,Java 虚拟机可以通过解释并执行这些 class 文件。而 Dalvik 虚拟机在 Java 虚拟机进行了优化,执行的是 Dalvik 字节码,这些 Dalvik 字节码由 Java 字节码转换而来,一般情况下, Android应用在打包时通过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。 dx 工具可以对多个 class 文件进行合并重组、优化,达到减小体积、缩短运行时间的目的。
META-INF
保存应用的签名信息,签名信息可以验证APK 文件的完整性。 Android SDK 在打包 APK时会计算 APK 包中所有文件的完整性,并且把这些完整性保存到 META-INF 文件夹下,应用程序在安装时首先根据 META-INF 文件夹校验 APK 的完整性,这样可以保证 APK 中的每一个文件都不能被篡改。以此来确保 APK 应用程序不被恶意修改或者病毒感染,有利于确保Android 应用的完整性和系统的安全性。 META-INF目录下包含的文件有 CERT.RSA 、 CERT.DSA 、CERT.SF 和 MANIFEST.MF ,其中 CERT.RSA 是开发者利用私钥对 APK 进行签名的签名文件,
CERT.SF 、 MANIFEST.MF 记录了文件中文件的 SHA-1 哈希值。
AndroidManifest.xml
Android应用程序的配置文件是用来描述 Android 应用“整体资讯”的设定文件,简单来说,相当于 Android 应用向 Android 系统“自我介绍”的配置文件。 Android 系统可以根据这个“自我介绍”完整地了解 APK 应用程序的资讯,每个 Android 应用程序都必须包含一个AndroidManifest.xml 文件,且它的名字固定的,不能修改。在开发 Android 应用程序时,一般都在 AndroidManifest.xml 中注册代码中的每个 Activity 、 Service 、 Provider 和 Receiver ,只有这样系统才能启动对应的组件,另外这个文件还包含一些权限声明以及使用的 SDK 版本信息等。
rsources.arsc
记录资源文件和资源ID 之间的映射关系,用来根据资源 ID 寻找资源。 Android 的开发是分模块的, res 目录专门用来存放资源文件,在代码中需要调用资源文件时,只需要调用findviewbyId ()就可以得到资源文件,每当在 res 文件夹下放一个文件, aapt 就会自动生成对应的 ID 保存在 .R 文 件中,调用这个 ID 就可以,但是只有这个 ID 还不够, .R 文件只是保证编译程序不报错,实际上在程序运行时,系统要根据 ID 寻找对应的资源路径,而resources.arsc 文件就是用来记录这些 ID 和资源文件位置对应关系的文件。
通过上图中对apk文件中各个部分的文件占比的分析,可以看到占用空间的主要是代码、图片、资源和lib和assert文件,主要方向精简代码、压缩图片、去除无用的库、减少asserts里面文件。
so文件优化
首先对lib文件夹进行瘦身:
lib文件夹中存放的都是so文件,so文件存放到不同的cpu架构的文件夹中,之所以这样处理,就需要了解so的编译类型,Android只支持3种cpu架构分为:arm,mips,x86,目前用的最多的是arm体系cpu,x86和mips体系的很少用到了。
arm体系中,又分32位和64位
armeabi/armeabi-v7a:这个架构是arm类型的,主要用于Android 4.0之后的,cpu是32位的,其中armeabi是相当老旧的一个版本, 缺少对浮点数的硬件支持,基本已经淘汰,可以不用考虑了。
arm64-v8a:这个架构是arm类型的,主要是用于Android 5.0之后,cpu是64位的。
平时项目中引入第三方的so文件时,第三方会根据cpu的架构编译成不同类型的so文件,项目引入这些so文件时,会将这些文件分别放入jniLibs目录下的arm64-v8a,armeabi-v7a等这些目录下,其实对于arm体系的so文件,没这个必要,因为arm体系是向下兼容的,比如32位的so文件是可以在64位的系统上运行的。Android上每启动一个app都会创建一个虚拟机,Android 64位的系统加载32位的so文件时,会创建一个64位的虚拟机的同时,还会创建一个32位的虚拟机,这样就能兼容32位的app应用了。鉴于兼容的原理,在app中,可以只保留armeabi-v7a版本的so文件就足够了。64位的操作系统会在32位的虚拟机上加载这个它。这样就极大的精简了app打包后的体积。
虽然这样可以精简apk的体积,但是,在64位平台上运行32位版本的ART和Android组件,将丢失专为64位优化过的性能(ART,webview,media等等)所以,更好的方法是,为相应的abi打对应的apk包,这样就可以为不同abi版本生成不同的apk包。
所以可以通过如下配置,只保留armeabi-v7a版本的so文件在module的build.gradle文件中defaultConfig节点做下面的配置:
```
//配置so库架构(真机: arm ,模拟器 x86 )
defaultConfig {
...
ndk {
//abiFilters "armeabi", "armeabi-v7a"
abiFilters 'armeabi-v7a'
}
}
```
完成这个步骤后,就可以看到,apk包中的lib中,只剩下armeabi-v7a文件夹中的so文件了。
重新编译so文件,用更小的库代替
很多第三方我们导入进来只用到其中很小一部分功能,大部分功能都是我们用不上的。这时候我们找到源代码,将我们需要的那部分代码提取出来,重新编译成新的so文件,再导入到我们项目中。
通过插件化,动态加载so库文件。
assets目录中的优化
如果存放的资源文件实在太大,可以考虑将部分不是立刻用到的资源文件,从网络上下载到本地。这样也能够减少asstes目录的大小。
res目录优化:
手动lint检查,手动删除无用的资源文件。
版本迭代过程中,不但有废弃代码冗余,肯定会有无用的图片存在。在build.gradle 里面配置shrinkResources true,在打包的时候会自动清除掉无用的资源,但经过实验发现打出的包并不会,而是会把部分无用资源用更小的东西代替掉。注意,这里的“无用”是指调用图片的所有父级函数最终是废弃代码,而shrinkResources true 只能去除没有任何父函数调用的情况,真正起效果只能通过Android Studio自带的 “Remove Unused Resources”小插件来实现了。更人性化是该查找结果可以“一键删除”。当然,可能图片是经过反射或字符拼接等方式获取,所以这个检测列表也不是全对,删除后很大概率编译失败或部分页面挂死、无图等问题,这个无解,工具还没智能到这个地步,你只能一遍又一遍“编译—解决部分问题—再编译再”。
使用tinypng等图片压缩工具对图片进行压缩
TinyPNG工具只支持上传PNG图片到官网上压缩,然后下载保存,在保持alpha通道的情况下对PNG的压缩可以达到1/3之内,而且用肉眼基本上分辨不出压缩的损失.。
Tinypng的官方网站:http://tinypng.com/
大部分的图片使用webp格式替代
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
尽量少使用帧动画,帧动画需要的图片较多,并且很占内存,建议使用属性动画替代。
使用一套资源
对于绝大对数APP来说,只需要取一套设计图就足够了。鉴于现在分辨率的趋势,建议取1080p的资源,放到xxhdpi目录,在加上一些屏幕适配的处理,基本能够适配大部分的手机。相对于多套资源,只使用1080P的一套资源,在视觉上差别不大,很多大公司的产品也是如此,但却能显著的减少资源占用大小,顺便也能减轻设计师的出图工作量了。
使用jpg格式
如果对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。在启动页,活动页等之类的大图展示区采用jpg将是非常明智的选择。
缩小大图
如果经过上述步骤之后,你的工程里面还有一些大图,考虑是否有必要维持这样的大尺寸,是否能适当的缩小。
事实上,由于设计师出图的原因,我们拿到的很多图片完全可以适当的缩小而对视觉影响是极小的。
覆盖第三库里的大图
有些第三库里引用了一些大图但是实际上并不会被我们用到,就可以考虑用1x1的透明图片覆盖。你可能会有点不舒服,因为你的drawable下竟然包含了一些莫名其妙的名称的1x1图片
使用微信资源压缩打包工具(AndResGuard)
矢量图
矢量图是由点与线组成,和位图不一样,它再放大也能保持清晰度,而且使用矢量图比位图设计方案能节约30~40%的空间,现在谷歌一直在强调扁平化方式,矢量图可很好的契合该设计理念。
优势
(1)占用存储空间小
(2) 无极拉伸不会出现锯齿,可以照顾不同尺寸的机型
(3)Android Studio自带很多资源,减小UI工作量
劣势
(1) 只支持5.0及以上系统
(2) 与位图相比多了一层计算,需消耗更多性能
(3) 不支持.9图
(4)不适合表现真实照片和复杂图形,一般使用在简单的icon和动画上
实现:
在app的build.graldle中的defaultConfig 标签下:
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//minSdkVersion 19 (5.0)
vectorDrawables.generatedDensities('xhdpi','xxhdpi','xxxhdpi')
//minSdkVersion > 19
// vectorDrawables.useSupportLibrary = true
}
使用shape背景
特别是在扁平化盛行的当下,很多纯色的渐变的圆角的图片都可以用shape实现,代码灵活可控,省去了大量的背景图片。
使用着色方案
相信你的工程里也有很多selector文件,也有很多相似的图片只是颜色不同,通过着色方案我们能大大减轻这样的工作量,减少这样的文件。借助于android support库可实现一个全版本兼容的着色方案,
加上一行代码 android:tint="@color/colorAccent"
<ImageView
android:layout_marginTop="100dp"
android:layout_gravity="center_horizontal"
android:layout_centerInParent="true"
android:src="@drawable/ic_icon_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/colorAccent"
/>
线化素材库
如果你的APP支持素材库(比如聊天表情库)的话,考虑在线加载模式,因为往往素材库都有不小的体积。这一步需要开发者实现在线加载,一方面增加代码的复杂度,一方面提高了APP的流量消耗,建议酌情选择。
classes.dex文件进行优化:
这个部分的优化,就需要优化.class文件的大小了,可以从以下几个方面入手:
L尽量减少第三方库的引用,有时候,可能为了部分功能,而去引用一个三方库,这样,就会增加.class文件的大小,建议,将要用的部分,从第三方库中抽离出来自己封装使用,这样就会减少class文件的大小,从而减少.dex文件的大小。
进行避免引入重复功能的库,比如(图片加载库,glide,picasso,fresco,image_loader,如果不是你一个人单独开发完成的很容易出现这种情况),尽量做到一个功能点一个库解决。通过查看exploded-aar目录和External Libraries或者反编译生成的APK,尽量避免重复库的大小,减小APP大小。
使用provided编译
对于一些库是按照需要动态的加载,可能在某些版本并不需要,但是代码又不方便去除否则会编译不过。使用provided可以保证代码编译通过,但是实际打包中并不引用此第三方库,实现了控制APP大小的目标。但是也同时就需要开发者自己判断不引用这个第三方库时就不要执行到相关的代码,避免APP崩溃。
支持插件化
插件化技术支持动态的加载代码和动态的加载资源,把APP的一部分分离出来了,对于业务庞大的项目来说非常有用,极大的分解了APP大小。因为插件化技术需要一定的技术保障和服务端系统支持,有一定的风险,如无必要(比如一些小型项目,也没什么扩展业务)就不需要了,建议酌情选择。
Facebook的redex优化字节码
ReDex 是 Facebook 开发的一个 Android 字节码的优化工具。它提供了 .dex 文件的读写和分析框架,并提供一组优化策略来提升字节码。
使用方法很简单: redex path/to/your.apk -o path/to/output.apk
输出后的 output.apk 体积更小,运行速度更快。
ReDex 依赖于 folly, glog, double-conversion, boost 和 zlib, 使用 autoconf/automake 进行构建。
参考:
Android性能优化系列之apk瘦身
https://blog.csdn.net/u012124438/article/details/54958757
Android 性能优化系列:APK极致优化
https://mobile.51cto.com/abased-600232.htm
Android性能优化之APK瘦身详解(瘦身73%)
https://blog.csdn.net/qq_32175491/article/details/80071987
Android 图片着色 Tint 详解
https://www.cnblogs.com/Free-Thinker/p/9105288.html
Android-使用tint一张图制作selector
https://blog.csdn.net/Blizzard_liu/article/details/52689688
AndResGuard集成笔记
//www.greatytc.com/p/fe988af9c663
redex, Android应用的字节码优化器
https://www.helplib.com/GitHub/article_138303
对于本文的技术讨论或者需要相关资料的小伙伴们,欢迎私聊我或者下方评论。