一、为什么要做apk包体积优化?
- 1、提高下载转化率;包的大小也是用户考虑是否下载的因素之一。
- 2、渠道合作商的要求;如果我们的app要跟手机厂商合作预装的话,手机厂商会对app有很多详细的要求,如:包体积大小、耗电量、内存占用等等。
二、apk分析
Android studio自带了apk分析的功能,只需要把apk文件拖进studio界面内就可以看到分析数据了,可以查看各个文件资源的大小及占比,借助数据具体分析需要优化的地方,而且右上角有个按钮 Compare with previous APK,可以在一次版本发布前对比两个版本的变化,分析有没有新增的大内存文件。
apk.png
三、优化方式
1、图片压缩
-
SVG图片使用
在适配不同类型分辨率的手机时,UI经常会给这么一组图标:
多分辨率图.png
每个图标都要准备2到3张图片适配不同分辨率,这样无疑会增加包体积大小,对于这种简单的小图标可以使用SVG图来替代。
SVG(Scalable Vector Graphics),可缩放矢量图。SVG不会像位图一样因为缩放而让图片质量下降。优点在于可以减小APK的尺寸。常用于简单小图标(一般在200dp * 200dp以下),太大的话加载会增加耗时。
有的UI工具直接支持了svg(我们用的Zeplin),直接在切图中下载就好了,下面这张关闭的按钮图标体积直接缩小了63%!
svg.png
导入Android studio:
引入.png image.pngsvg是由xml定义的,标准svg根节点为<svg>,但是Android中只支持 <vector>,这里 vector 将svg的根节点 <svg> 转换为 <vector>。我们还可以直接在xml中修改svg的颜色等属性:
修改颜色.png在代码里也可以利用Tint着色器修改,甚至支持selector:
<!--代码修改颜色-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_2_2"
android:tint="@color/red_50"/>
<!--drawable selector-->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_2_2" android:state_pressed="true"/>
<item android:drawable="@drawable/ic_2_2"/>
</selector>
<!--color selector-->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/red_50" android:state_pressed="true"/>
<item android:color="@color/white"/>
</selector>
<!--selector使用-->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/close_selector"
android:tint="@color/close_tint_selector"/>
如果有多个svg需要转换为android的vector,则可以通过第三方工具 svg2vector 进行批量转换。
svg图在使用时也有一些坑
Android 5.0(API 21)之前的版本不支持矢量图,在打包的时候svg的xml文件会在不同分辨率的文件夹各生成一张对应名字的png图片(???反而更占内存了)。可在构建时 针对每种屏幕密度将矢量图转换为不同大小的位图,在 build.gradle 中配置如下,适用于 Gradle 插件1.5 及以上版本:
android{
defaultConfig{
// 5.0(API 21)版本以下,将svg图片生成指定维度的png图片
generatedDensities = ['xhdpi','xxhdpi']
}
}
或者使用支持库来兼容,在 build.gradle 中配置如下,适用于 Gradle 插件2.0及以上版本:
android{
// Gradle Plugin 2.0+
defaultConfig{
// 利用支持库中的 VectorDrawableCompat 类,可实现 2.1 版本及更高版本中支持 VectorDrawable
vectorDrawables.useSupportLibrary = true
}
}
dependencies {
// 支持库版本需要是 23.2 或更高版本
compile 'com.android.support:appcompat-v7:23.2.0'
}
这时使用矢量图 必须使用 app:srcCompat 属性,而不是 android:src,如下:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/close_selector"/>
-
图片压缩工具
1、专门压缩图片的网站https://tinypng.com/
2、Gradle插件TingPngPlugin
图片压缩网站.png
2、代码和资源的压缩
- Remove Unuserd Resource
AS 给我们提供了一键移除所有无用资源的功能,如图:
image.png
但是这种方式不建议使用,因为如果某资源仅存在动态获取资源id 的方式,那么这个资源会被认为没有使用过,从而会直接被删除。
getResources().getIdentifier("activity_main","layout",getPackageName());
-
Lint移除无用资源
Lint工具可以自己选择保留或者移除一些无用资源。
image.png image.png
-
国际化资源配置
默认情况下,应用在打包后string包下资源会生成不同语言版本,相应也会增加包体积大小:
如果我们不需要支持国际化,只要在build.gradle配置指定的语言就可以了,或者多渠道打包的时候配置对应的语言:
android{
defaultConfig{
// 只适配英语,中文
resConfigs 'en','cn'
}
}
-
动态库打包配置
首先我们需要知道 so文件是由ndk编译出来的动态库,是 c/c++ 写的,所以不是跨平台的。即每一个平台需要使用对应的so库。
ABI 是应用程序二进制接口简称(Application Binary Interface),定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。
在Android 系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,arm64- v8a,x86,x86_64,mips,mips64。
这是随便找的一个工程,打包之后对应每一个ABI的目录下都会生成一份so文件,而且有的so文件会比较大,这里是导致apk包很大的一个主要因素。
我们一般没什么特别需求的话,只需要配置armeabi-v7a即可,打包后就只有我们配置的ABI目录,看了下微信的apk就是这么干的。如果有需要适配某种ABI也可以在代码中动态判断当前CPU架构对应的ABI,然后加载so到对应的目录就行。
android{
defaultConfig{
ndk{
abiFilters "armeabi-v7a"
}
}
}
配置之后,我们的apk从原来的23.2M直接缩小到7M,效果非常明显。
配置ABI.png- 混淆压缩代码
混淆压缩代码就是将Java文件名以及类文件中的属性名、方法名都改成 a、b、cd、ef 这种无意义的名字,一方面是为了让反编译的人不容易看懂代码逻辑,另一方面是为了减少代码的字符量从而达到压缩代码体积的目的,同时还会删除没有用到的类文件、注释代码等等。
只需要在工程的build.gradle文件中做如下配置,注意在debug过程中不要打开,会降低编译打包的速度。
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
代码压缩.png
还有一点要注意,在开启代码混淆后,再次编译可能会出错,提示类文件找不到的信息,这时要根据错误提示将报错的类添加到 proguard-rules.pro 文件中,过滤掉不需要混淆的文件,相关的语法规则可以自己去查一下,类似下面这种。
-keep class com.taobao.** {*;}
-keep class com.alibaba.** {*;}
-keep class com.alipay.** {*;}
-dontwarn com.taobao.**
-dontwarn com.alibaba.**
-dontwarn com.alipay.**
报错信息.png
-
资源压缩
资源压缩一般和代码混淆压缩协同工作,将 shrinkResources 设置为 true 即可
buildTypes {
debug {
shrinkResources false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
资源压缩目的是删除无用资源,但是会保留这种方式使用的资源
getResources().getIdentifier("activity_main","layout",getPackageName());
看起来比上面说的 Remove Unuserd Resource 这种方式要靠谱得多,资源压缩有严格和不严格两种模式:
严格模式:意思是精确匹配文件名,只有代码中使用的文件名和资源文件名完全一致才会不删除对应的资源文件。
非严格模式: 只要以代码中使用的文件名开头的资源文件就不会被删除,比如代码中使用了 "activity_main" 文件名,"activity_main123"的资源就不会被删除,即使它没有被使用。
默认情况未启用严格模式,如需启动则需设置 shrinkMode,创建keep.xml,如下
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict">
</resources>
将该文件保存在项目资源中,例如,保存在 res/raw/keep.xml。构建不会将该文件打包到 APK 之中。除此之外,如果你有想要保留或舍弃的特定资源,则可以创建如下的 xml 文件,然后在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/activity_main1,@layout/fragment_*"
tools:discard="@layout/item_*">
</resources>
-
资源混淆
和代码混淆一样,将资源文件名变更为无意义的名字,可以使用AndResGuard插件
网友评论