APK瘦身——更全面的方案

来源:互联网 发布:windows longhorn开机 编辑:程序博客网 时间:2024/05/29 18:09

前阵子做了一次减包体的工作,过后觉得这项工作还是套路性挺强的,于是综合当时的经验以及广泛浏览了网上的各种大牛小牛的博客,实践后特总结了以下的一些思路和做法,不求内容最丰富,只求方案更全面。

APK瘦身的目的

瘦身的目的最明显的一个就是:提高下载转化率。怎么理解呢?举个例子来说,假如你的应用包12MB,有100个潜在用户想要去下载尝试使用你的应用,结果有20个用户嫌弃安装包太大而直接放弃,有20个用户在等待下载的过程中取消下载,最终只有60个用户下载安装了应用。这时你的应用的实际的下载转化率其实是 60/100 = 60%。
简单的总结:安装包越小,用户下载等待的时间越短,对手机配置要求的也越小,设备的体验愈佳,应用的下载转化率也就越高。

APK包体的组成

开始瘦身前,需要先了解一下APK都主要由哪些成分组成

  1. classes.dex
    编写的所有的Java代码(包括各种引入的sdk代码)最终转化成在Android虚拟机上运行需要的字节码(和java的字节码有一定的区别)

  2. res文件夹
    存放所有资源的文件夹(除了里面raw文件夹的文件不会被编译,其他都会被编译)

  3. resources.arsc
    编译后的二进制资源文件

  4. assets文件夹
    用于保存需要保持原始文件的资源文件(这部分资源不会被编译)

  5. lib文件夹
    用于存放应用需要的native库文件

  6. AndroidManifest.xml
    程序全局配置文件

  7. META-INF文件夹
    存放几个签名校验相关的文件,用于保证APK的完整性和安全性

  8. 其他
    其他一些配置生成的文件

分析现APK各成分的比例

了解清楚APK的各个组成成分后,就需要有针对性地对自己的APK各个成分做一个比例分析。
工具1:Android Studio 2.2及其以上的版本
Android Studio自2.2版本以来就引入了分析APK各成分的比例的功能,用法也挺简单的,主要有2种操作方法,如下:

  1. 导航栏的Build →Analyze APK…→选择APK文件的路径→选择OK打开即可
    导航栏的Build

    选择APK文件的路径

  2. 可以直接把APK包拖进IDE,也可以得到如下的比例分析构成图(比例会自动按从大到小排好序呈现)
    比例会自动按从大到小排好序

    可以看出,在其中一般占比比较大的一般都是dex文件,res文件夹,assets文件夹,lib文件夹以及resource.arsc文件。所以接下来的工作就是有针对性地让这些文件和文件夹尽量地变小。在开始下一步之前,要简要介绍另外一个工具。

工具2:NimbleDroid
NimbleDroid是美国哥伦比亚大学的博士创业团队研发出来的分析Android app性能指标的系统,分析的方式有静态和动态两种方式,其中静态分析可以分析出APK安装包中大文件排行榜,各种知名SDK的大小以及占代码整体的比例,各种类型文件的大小以及占排行,各种知名SDK的方法数以及占所有dex中方法数的比例。这个外国网站的工具目前比Android Studio自带的功能更加全面和强大。这里仅作简要介绍,读者若有兴趣可自行去研究。

有针对性地对各部分做缩减工作

一、减classes.dex文件——压缩代码

代码压缩通过 ProGuard 提供,ProGuard 会检测和移除封装应用中未使用的类、字段、方法和属性,包括自带代码库中的未使用项(这使其成为以变通方式解决 64k 引用限制的有用工具)。ProGuard 还可优化字节码,移除未使用的代码指令,以及用短名称混淆其余的类、字段和方法。混淆过的代码可令您的 APK 难以被逆向工程。
要启用通过 ProGuard 实现的代码压缩,要在对应moudle(一般情况下是主moudle,build.gradle文件第一行为apply plugin: ‘com.android.application’)的build.gradle 文件相应的构建类型中添加 minifyEnabled true
注意,ProGuard会拖慢构建速度,因此应该尽可能避免在调试版本构建中使用它。另:Android Studio 会在使用 Instant Run 时停用 ProGuard。

如下为构建release版本的build.gradle的示例片段:

android {    buildTypes {        release {            minifyEnabled true            proguardFiles getDefaultProguardFile(‘proguard-android.txt'),                    'proguard-rules.pro'        }    }    ...}

其中的proguardFiles 属性:用于定义 ProGuard 规则

getDefaultProguardFile(‘proguard-android.txt’) 为获取默认的 ProGuard规则文件,它位于Android SDK tools/proguard/ 文件夹。(提示:要想做进一步的代码压缩,可尝试使用位于同一位置的 proguard-android-optimize.txt 文件。它包括相同的 ProGuard 规则,除此之外还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小 APK 大小和帮助提高其运行速度。)

proguard-rules.pro 文件用于添加自定义的 ProGuard 规则。默认情况下,该文件位于moudle根目录(build.gradle 文件旁)

每次构建时 ProGuard 都会输出下列文件,这些文件保存在 /build/outputs/mapping/release/

  • dump.txt
    说明 APK 中所有类文件的内部结构。

  • mapping.txt
    列出了原始的类,方法和字段名与混淆后代码间的映射。(这个文件很重要,当你从release版本中收到一个bug报告时,可以用它来翻译被混淆的代码)

  • seeds.txt
    列出未进行混淆的类和成员

  • usage.txt
    列出从 APK 移除的代码

  • resources.txt
    列出resource被保留的资源

自定义混淆规则
对于某些情况,默认 ProGuard 配置文件 (proguard-android.txt) 足以满足需要,ProGuard 会移除所有(并且只会移除)未使用的代码。不过,ProGuard 难以对许多情况进行正确分析,可能会移除应用真正需要的代码。举例来说,它可能错误移除代码的情况包括:

  • 当应用引用的类只来自 AndroidManifest.xml 文件时
  • 当应用调用的方法来自 Java 原生接口 (JNI) 时
  • 当应用在运行时(例如使用反射或自检)操作代码时

要修正错误并强制 ProGuard 保留特定代码,可以在 ProGuard 配置文件中添加一行 keep 代码。例如:
-keep public class MyClass

还可以在想保留的代码添加 @Keep 注解。在类上添加 @Keep 可原样保留整个类。在方法或字段上添加它可完整保留方法/字段(及其名称)以及类名称。(请注意,只有在使用注解支持库时,才能使用此注解。)
有关自定义proguard-rules.pro文件的更多信息,可以参考 ProGuar手册.这里问题排查列出了一些常见的问题。本文不讲解这部分知识。

解码混淆过的堆叠追踪
本来本章主要讲解压缩代码的,一般都不会涉及到如何解码混淆过的堆叠追踪的。考虑到网上很多参考资料只是讲了如何压缩混淆代码却没有告诉我们如何去解码混淆过的堆叠追踪以便快速定位出现崩溃的代码的。所以当你发现刚好差某样东西的时候,这恰恰就是你的机会了。
在 ProGuard 压缩代码后,读取堆叠追踪变得困难(即使并非不可行),因为方法名称经过了混淆处理。幸运的是,ProGuard 每次运行都会创建一个 mapping.txt 文件,其中显示了与混淆过的名称对应的原始类名称、方法名称和字段名称。ProGuard 将该文件保存在应用的 /build/outputs/mapping/release/ 目录中。
请注意,每次使用 ProGuard 创建发布构建时都会覆盖 mapping.txt 文件,因此每次发布新版本时都要必须小心地保存一个副本。通过为每个发布构建保留一个 mapping.txt 文件副本,就可以在用户提交的已混淆堆叠追踪来自旧版本应用时对问题进行调试。
如果是在Google Play 上发布应用,可以上传每个 APK 版本的 mapping.txt 文件。Google Play 将根据用户报告的问题对收到的堆叠追踪进行去混淆处理,以便你在 Google Play Developer Console 中进行检查。
如果要自行将混淆过的堆叠追踪转换成可读的堆叠追踪,请使用 retrace 脚本(在 Windows 上为 retrace.bat;在 Mac/Linux 上为 retrace.sh)。它位于 /tools/proguard/ 目录中。该脚本利用 mapping.txt 文件和您的堆叠追踪生成新的可读堆叠追踪。使用 retrace 工具的语法如下:
retrace.bat|retrace.sh [-verbose] mapping.txt []
例如:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
如果不指定堆叠追踪文件,retrace 工具会从标准输入读取
其实,最简单的方法还是自己先查看混淆后的堆叠追踪文件,根据自己的经验找出最后导致崩溃的追踪信息,然后拿这个追踪信息去mapping.txt文件中直接按查找键查找。例如,假如我最后出错的代码定位的信息为com.go.cj.b.c(b.java 1234),那我们可以拿com.go.cj.b去mapping.txt文件中直接按查找键查找。

对于压缩代码,以上是工具能为我们做的,但是我们在平时的开发中养成一些习惯,也会有利于我们的代码缩减,一些良好的习惯建议如下:

  • 移除废弃功能的代码
    不要只是注释,担心可能以后会用到。因为一般的开发都会使用版本控制工具VCS,所以这种担心是多余的。

  • 定期review项目代码
    发现有重复功能实现的代码或者框架要及时重构,删除重复部分的代码和框架。出现重复功能代码的情景大多如下:已经有了的功能代码或框架,团队成员不知道自己又写了一套或者引入另外的相同功能的框架。这种情况的发生也反映出你们团队之间的沟通存在一定的问题。有可能是个人经验问题,也有可能是整个团队的规范,共识问题,导致大家都不重视。

  • 谨慎引入第三方框架
    这里仅从包体大方面来考虑,如果你仅仅是只需要解析几个json字段的值,那么就没有必要引入json解析框架,android和java本身封装的api就可以完全应付需求了。杀鸡不用牛刀,其他方面的需求也是一样的。

  • 选择小而精的库,而不是大而全的
    比如减小对 Support 兼容包的依赖,Support-V4 包非常大,项目引入无疑会增大 dex 文件的大小,Google 已经意识到这个问题,所以 Support-V7 一开始就做了拆分,并且开始对 Support-V4 做拆分,虽然目前成果还不明显,不过还是挺值得期待的,特别是发现你少了 Support-V4 包后,可能就从 2 个 dex 变成 1 个 dex 了。
    又比如,如果只用到了谷歌统计,那么就不要把整个google play services都集成进来,只集成需要的那部分。现在优秀的库都是小而精的,不追求什么大而全。

  • 业务模块采用插件化框架,代码动态从云端拉取
    插件化,一种懒加载思想的体现,先让用户能够安装宿主包,对于一些功能模块做插件化,在特定的时机再下载安装。

二、减res文件夹

res文件夹里面主要就是包括各种布局文件,value文件,图片文件,原生文件。这一块的处理方案主要采用2种方式,一是压缩资源,二是删除未使用的资源。

1. 压缩资源

压缩资源又分2种思路,一是对资源进行压缩,二是使用更小的资源来替换当前的资源。

  • 压缩图片
    一般而言图片压缩对减小Apk大小所产生的效果占到你所有减小Apk努力的效果50%以上。下面推荐一款目前所知图片压缩效果最好的网站TinyPing。目前除了要求美工在提供图片的时候就把图片放上网站去压缩,我们也可以使用别人利用tinypng提供的jar包做的批量处理本地图片的tinyPIC gradle plugin。它在build 中插入一个新的tinyPicPlugin task.遍历寻找项目res中以drawable开头的文件夹中的图片资源,调用tiny API进行压缩工作并替换原来的文件。它的接入方法可以参考这里TinyPIC Gradle Plugin。

  • 有损编码格式的音频文件代替无损格式的音频文件
    从下面这篇官方文档Supported Media Formats可以看到 Android 平台支持的音视频格式,下面列出有损和无损常用的格式(不要认为有损编码就是音质很差):
    无损格式:WAV,PCM,ALS,ALAC,TAK,FLAC,APE,WavPack ( WV )
    有损格式:MP3,AAC,WMA,Ogg Vorbis
    实际开发中需要使用音频文件尽量采用 MP3、Ogg 这种有损格式,尽量不要用 WAV、PCM 这种无损音频。

  • 尽量只保存一份图片资源
    开发目录下会有个drawable或者mipmap目录用于适配不同dpi的屏幕,目前市面上绝大部分机型都处于xxhdpi的适配范围,所以可以考虑只保留xxhdpi目录下一份图片资源,具体保留哪个目录下的资源和保留几份资源还得依照应用自身的实际机型分布决定。至于为什么可以考虑一份图片资源,可以去了解一下以下2篇文章:
    Android drawable微技巧,你所不知道的drawable的那些细节
    那些值得你去细细研究的Drawable适配

  • 使用 Drawable XML、Color代替PNG图片
    一些情况下,我们可以考虑使用 Drawable XML 来代替 PNG,如:渐变的背景图,用几行 XML 就可以描绘出来,何必使用几十到上百K的 PNG 文件。
    用 Color 代替 PNG,如:纯色的背景。
    从性能上看,比起使用图片资源需要先将其生成 Bitmap 再传到底层交由 GPU 渲染,用Drawable XML和Color则更加高效,它是直接将 Shape 信息传到底层由 GPU 进行渲染,CPU和内存的占用会更少。

  • 不需要透明度时使用JPG代替PNG
    当不需要透明度的图片时,可以考虑用JPG代替PNG,由于JPG没有Alpha 通道,所以文件更小。

  • 考虑使用WEBP图片资源格式
    WebP是谷歌研发出来的一种图片数据格式,它是一种支持有损压缩和无损压缩的图片文件格式,如果应用支持到Android 4.0+,那么我们可以使用WebP格式代替PNG,我们的资源大小能降低50%多。
    不过就目前来说,对于4.0+ 到 4.2.1 ,原生只支持完全不透明的webp图,4.2.1+ 对于webp的是完全支持的(包含半透明的webp图)。所以说对于4.2.2(API17)以下的版本,还是需要引入兼容库来解决。但是另外一点,引入兼容库又会导致包体变大。不过这种增量或许和把所有png图换成webp所带来的减量比较或许不值得一提,特别是图片特别多的应用,这种增量几乎可以不计。另外也要注意的是,某些国产rom会代理类Resource为自己定义的,例如小米2刷成4.xx的手机上,小米机器代理了类Resource为MIUIResource,但是这个MIUIResource未能正确识别webp资源,会导致加载资源文件失败而出现崩溃。所以应该考虑自己用户的机型分布,考虑使用WEBP图片资源格式替换png格式。
    谷歌也在致力于推动WEBP图片资源的使用,目前Android studio 2.3中加入了对图片压缩的工具,可以直接将PNG,BMP,JPG和静态的Gif图片文件转成Webp格式。以下为详细转换方法:
    Android studio 2.3 Webp使用
    详细的使用方法:
    Android Webp 完全解析 快来缩小apk的大小吧

  • 考虑使用SVG格式图片去替换一些icon
    SVG的全称是Scalable Vector Graphics,叫可缩放矢量图形。它和位图(Bitmap)相对,SVG不会像位图一样因为缩放而让图片质量下降。它的优点在于体积小,不用考虑屏幕适配问题。Android 5.0中引入了 VectorDrawable 来支持矢量图(SVG),同时还引入了AnimatedVectorDrawable 来支持矢量图动画。但是5.0以前的版本还是需要引入支持库,也一定程度上增加了包体。所以说可以考虑使用。

2. 移除无用的资源

这里的移除无用的资源,主要是指2个方面,一是在工程里面直接删除没有使用的资源,二是不打包没有使用的资源。

2.1 在工程里面直接删除没有使用的资源

这一点主要是使用lint检查并清除冗余资源。如果你的资源是通过资源名称使用Resources的getIdentifier(String name, String defType, String defPackage)方法去获取到资源的id来使用资源(这种方式是通过反射的方法根据资源名称去获取资源的id),而不是直接通过R文件自动生成的id来使用资源的,lint会检测判定你这个文件并没有被使用,而作为未使用的文件列出来。这时候就不能使用一键删除的功能,需要确认后自己手动删除。例如我要使用doodle.png这个资源,一般情况下我们是通过R.drawable.doodle去使用的,如下代码

Resources res = context.getResources();Drawable doodleDrawable = res.getDrawable(R.drawable.doodle);

但是,有些时候我们需要在代码中动态地根据资源名称去使用资源,这时候就要用到getIdentifier()去获取到资源的id,然后再使用这个id去获取资源,示例代码如下:

Resources res = context.getResources();int doodleDrawableId = res.getIdentifier("doodle", "drawable", context.getPackageName());Drawable doodleDrawable = res.getDrawable(doodleDrawableId);

这种情况就不能使用一键删除的功能,需要确认后自己手动删除。

具体使用步骤
Android Studio 选中项目右键 => Analyze => Run Inspection by Name => 输入 unused resuroces

2.2 不打包不需要使用的资源

这个方面也有2个可行的思想,一是利用Android Plugin开启gradle 的Resource shrinking进行构建打包,这时候没有被使用的资源将不会打进包里。二是不打包未使用(不需要)的替代资源

(1) 利用Android Plugin开启gradle 的Resource shrinking
Resource shrinking的使用
Resource shrinking 需要和Code shrinking 一起使用。在代码中删除所有未使用的代码后,Resource shrinking才可以知道哪些资源APK程序仍然使用,你必须先删除未使用的代码,Resource才会成为无用的,从而被清除掉。Code shrinking部分在上面的减classes.dex文件——压缩代码 一节中已经讲过了。下面为Resource shrinking的使用具体步骤:
添加shrinkResources true属性在你的 build.gradle文件中,相应代码块如下:

android {    buildTypes {        release {            shrinkResources true            minifyEnabled true            proguardFiles getDefaultProguardFile('proguard-android.txt'),                    'proguard-rules.pro'        }    }}

resource shrinker 目前还不支持移除定义在values/目录下的资源文件(strings,dimensions,styles,colors),因为Android Asset Packaging Tool(AAPT)不允许Gradle Plugin指定预定义的版本资源[issue 70869]

指定要忽略的资源文件或者一定要删除的资源文件
如果我们希望保留或丢弃特定的资源,需要在项目中创建一个XML文件,并使用resources标签,并使用tools:keep属性明确指定每个资源保留,或者使用tools:discard属性明确指定这个资源将要被舍弃移除。两个属性都可以使用逗号(,)分隔符声明资源名称列表。也可以使用* 作为匹配符,匹配名称。
例如,XML文件,命名为keep.xml,这个文件需要保存在:res/raw/keep.xml,这样build的时候该文件才不会被打包到APK里面,示例代码块如下:

<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"           tools:discard="@drawable/home"           tools:keep="@drawable/doodle, @layout/doodle, @drawable/doodle_*"></resources>

启用严格的检测
resource shrinker 也通过搜索代码中是否包含资源名或者具有匹配符相同的名称来判断是否在build的时候删除资源。所以,通常情况下,resource shrinker可以准确地确定资源使用。但是如果你在代码中使用Resources.getIdentifier()显式通过资源名称动态获取指定资源的Id,在默认情况下,这样资源具有匹配名称的格式为潜在的使用,无法去除。
例如,下面的代码将导致所有img_前缀的资源都无法去除。

String name = String.format("img_%1d", angle + 1);res = getResources().getIdentifier(name, "drawable", getPackageName());

这是因为,Gradle 在处理该资源文件时候的方式,遇到被判断为潜在使用的情况下,默认的值为 safe。这时候就需要在上面的keep.xml文件中指定 shrinkMode为strict(只会保留有明确引用的资源,以及处理被 tools:keep 和 tools:discard 标注的资源)。示例代码块如下:

<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"          tools:shrinkMode="strict"></resources>

但是这里面还有另外一种特殊情况就是,当上面代码中的文件名name没有显式地出现“img_”这个字符的时候,不需要指定 shrinkMode为strict模式构建的时候Gradle也能删除具有前缀为“img_”字符名称的资源。也就是说,当我把name写在其他非代码类的文件中,然后在代码中读取的时候,这时候resource shrinker 通过搜索代码是扫描不出相关的资源名称“img_”字符来的,所以这个时候具有前缀为“img_”字符名称的资源都会被删除了。所以要特别注意这种特殊情况。

(2) 不打包未使用(不需要)的替代资源
Gradle resource shrinker 只删除你在代码中未使用资源,这意味着它不会删除不同的设备配置的可替代资源。如果有必要,可以使用Android Gradle plugin 的resconfigs属性删除替代资源文件。
例如:我们项目中适配10种国家语言,而项目依赖了v7、v4等其他support包里面包含20种国家语言,那么我们可以通过resconfigs 删除剩余的可替代资源文件,这对于我们APK大小可减少不少。
以下代码说明了如何限制语言资源:

android {    defaultConfig {        ...        resConfigs "en", "fr"    }}

以上resconfigs 属性只指定了英语和法语这2个语言的资源会被打进包里。未指定的语言的任何资源都被删除。当然如果不设置resconfigs属性,默认会把所有的语言资源都会打进包里。
同样的思想我们可以运用在图片资源上,但是我们不能做到几种屏幕密度的资源打在同一个包里面,另外的几种不要了。我们只能做到打包一个只有1种屏幕密度图片资源的包。就是我们下面要讲的使用APK Splits构建不同替代资源的APK。

(3)使用APK Splits构建不同替代资源的多个APK
APK Splits比起使用 flavors,能让应用程序更有效地构建一些形式的多个apk。注意:这里的多个APK和使用flavors构建出来的多个APK是不同的。使用Splits构建出来的APK是只含有不同的单套资源但功能用途一样的APK,而flavors构建出来的APK是含有同样的资源但却是功能用途不一样的APK。
APK Splits多APK只支持以下类型:

  • 屏幕密度

  • ABI

正是由于Splits构建出来的APK只含有单套可替换资源,所以它的适用情景就是我们要根据用户的手机去提供基于不同屏幕分辨率(xxhdpi,mhdpi等),so库版本的单个APK,并且应用市场支持发布这种多个APK的功能(即要求应用市场能根据用户的手机的屏幕分辨率,CPU的架构而为用户选择对应的版本的APK提供下载)。而目前只有GooglePlay支持这种Multiple APK Support发布功能,所以你的应用如果不是在GooglePlay上面发布的话,这种拆分多个APK发布的做法,你就了解一下就行了。下面继续介绍

按屏幕密度拆分,配置代码如下:

android {  ...  splits {    density {      enable true      exclude "ldpi", "tvdpi", "xxxhdpi"      compatibleScreens 'small', 'normal', 'large', 'xlarge'    }  }

enable: 启用屏幕密度拆分机制
exclude: 默认情况下,不设置这个属性所有屏幕密度都包括在内,如果设置,则显式声明移除一些密度。
include: 表示要包括哪些屏幕密度
reset( ): 重置屏幕密度列表为只包含一个空字符串 (这能够实现,在与include一起使用时可以表示使用哪一个屏幕密度,而不是要忽略哪一些屏幕密度)
compatibleScreens:表示兼容屏幕的列表。这将会注入到manifest中匹配的 节点。这个设置是可选的。

构建完成后可以在out/apk/目录下看到多个版本的APK。

按 ABI 拆分,配置代码如下:

android {  ...  splits {    abi {      enable true      reset()      include 'x86', 'armeabi-v7a', 'mips'      universalApk true    }  }}

enable: 启用ABI拆分机制
exclude: 不使用这个属性默认情况下所有ABI都包括在内,可以指明移除一些ABI。
include:指明要包含哪些ABI
reset():重置ABI列表为只包含一个空字符串(这可以实现,在与include一起使用来可以表示要使用哪一个ABI,而不是要忽略哪一些ABI)
universalApk:指示是否打包一个通用版本(包含所有的ABI)。默认值为 false。

Splits只支持这2种类型的分类,更多的多版本支持的知识是下一节的内容。

(5)使用多版本的APK发布
Multiple APK Support是一个在Google Play,可以发布不同的应用程序,分别针对不同的设备配置特征。每个APK是一个完整的、独立的应用程序版本,但他们分享在Google Play相同的应用程序清单,必须共享相同的包名和与签名。Google Play 会自动给你匹配相应的APK,这样我们的APK 就可以是分不同版本构建需要资源文件,从而减小APK的大小。
Multiple APK Support支持以下:

  • 支持不同OpenGL的APK
  • 支持不同的屏幕尺寸和密度的APK
  • 支持不同的设备功能的APK
  • 支持不同的平台版本的APK
  • 支持不同的CPU架构,每个apk(如ARM、x86,MIPS等)的APK
  • 支持不同的平台版本的APK

更多相关信息请参考Multiple APK Support,这里不详细叙述。

三、减resources.arsc文件

简单介绍下resources.arsc文件来源与作用:除了assets和res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理。除了assets资源之外,其它的资源都会被赋予一个资源ID。打包工具负责编译和打包资源,编译完成之后,会生成一个resources.arsc文件和一个R.java,前者保存的是一个资源索引表,后者定义了各个资源ID常量,供在代码中索引资源。
所有的png文件是以STORE的方式存储到apk里的,关于zip里的STORE和DEFLATE,详见:Zip (file format)
通俗的说,当文件是STORED的方式存储到zip,表示这个文件并没有经过压缩,如果是Defl:N的方式,表示通过DEFLATED normal的方式压缩存储到zip。
现在业内有一个开源的插件针对以上原理进行了一定的压缩,就是下面要讲的

微信资源压缩插件:AndResGuard
其原理就是:

  • (1)对资源(png, xml, jpg等)名称混淆,资源路径名称混淆以及名称长度压缩

  • (2)将原来以STORED形式存储到zip中的png文件改成DEFLATED(普通压缩存储)方式。

其Gitghub地址为(里面有详细的接入流程):Android资源混淆工具使用说明

四、减assets文件夹

assets目录可以存放各种文件,正常情况下,一般只存放以下几种文件:字体文件、WEB页面、配置文件、图片文件。
上述几种文件除了配置文件之外,我们都可以进行适当的压缩处理:
字体文件:可以使用字体资源文件编辑神器Glyphs进行压缩,其压缩方式其实就是通过删除不需要的字符从而减少APK的大小。
WEB页面:可以考虑使用7zip压缩工具对该文件进行压缩,在正式使用的时候解压
图片文件:可以使用tinypng进行图片压缩

五、减lib文件夹

lib目录用于存放通过C或C++编写编译生成的so文件(native库/JNI开发)。
因为目前市场上主流的架构还主要是arm架构,所以如果不是必要的话,可以考虑不支持x86和mips架构,但这并不意味着CPU是x86或mips架构的手机就不能正常安装使用APK了,因为放在arm目录下的so库是可以兼容到其他架构的;
另外arm架构中的eabi-v7a相比于eabi只是在图形渲染方面有了很大的改进,所以如果so库对图形渲染没有很高的要求的话,完全可以把so库只存放在arm eabi目录中,这样可以大大减小APK的体积。
lib瘦身主要是减小对 CPU 架构的支持,配置起来很简单,在 build.gradle 使用 abiFilters 配置需要用到的 CPU 架构,并将不需要兼容的 so 文件从项目中移除即可。
示例代码块如下:

defaultConfig {        ndk{            // 设置支持的so库            abiFilters 'armeabi', 'x86'        }}

总结

以上所述的方法,并不需要全部都应用,而是应该根据实际情况来选用其中适合实际情况的最有效的方法。根据笔者使用过的实际情况来说,以下几种方式对于减少包体是效果最明显的且施行起来也不难的:

  • 1.对代码进行ProGuard混淆

  • 2.对图片资源进行压缩

  • 3.使用Gradle resource shrinker 在打包时删除不需要的资源

  • 4.使用微信资源压缩插件对资源进行混淆

  • 5.在可行的情况下使用WEBP图片资源替换现有的PNG图片

欢迎大家斧正、交流、补充!

原创粉丝点击