APK包体积优化

一、为什么要做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.png

svg是由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工具可以自己选择保留或者移除一些无用资源。
lint.png
unused.png

image.png
image.png
  • 国际化资源配置
    默认情况下,应用在打包后string包下资源会生成不同语言版本,相应也会增加包体积大小:
image.png

如果我们不需要支持国际化,只要在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目录.png

这是随便找的一个工程,打包之后对应每一个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插件
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容

  • 前言: Android 开发中,一个成熟的App没有了前期快速迭代的试错,并且业务需求逐渐稳定,功能稳定,必然会面...
    王朋6阅读 1,080评论 0 1
  • 最近几周一直在研究如何为APK瘦身,折腾了很久,是时候写篇博客总结一下了,虽然已经准备了下周一要在客户端周会分享用...
    风清袖一阅读 1,058评论 1 10
  • 1、 前言 如果你对App优化比较敏感,那么Apk安装包的大小就一定不会忽视。关于瘦身的原因,大概有以下几个方面:...
    未来的理想阅读 11,260评论 4 39
  • 1.介绍 如果你正在查阅build.gradle文件的所有可选项,请点击这里进行查阅:DSL参考 1.1新构建系统...
    Chuckiefan阅读 12,142评论 8 72
  • 现在你已经知道了Gradle是如何工作的,如何创建你自己的任务和插件,如何运行测试,以及如何设置持续集成,你差不多...
    sollian阅读 1,099评论 0 5