在开始讲瘦身技巧之前,先来讲一下APK的构成。

APK的构成

可以用Zip工具打开APK查看。
比如,美团App 7.8.6的线上版本的格式是这样的:

可以看到APK由以下紧张部分组成:

php打包apkAndroid App包瘦身优化实践 AJAX

文件/目录描述lib/存放so文件,可能会有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips,大多数情形下只须要支持armabi与x86的架构即可,如果非必需,可以考虑拿掉x86的部分res/存放编译后的资源文件,例如:drawable、layout等等assets/运用程序的资源,运用程序可以利用AssetManager来检索该资源META-INF/该文件夹一样平常存放于已经署名的APK中,它包含了APK中所有文件的署名择要等信息classes(n).dexclasses文件是Java Class,被DEX编译后可供Dalvik/ART虚拟机所理解的文件格式resources.arsc编译后的二进制资源文件AndroidManifest.xmlAndroid的清单文件,格式为AXML,用于描述运用程序的名称、版本、所需权限、注册的四大组件

当然还会有一些其它的文件,例如上图中的org/src/push_version等文件或文件夹。
这些资源是Java Resources,感兴趣的可以结合编译事情流中的流程图以及MergeJavaResourcesTransform的源码看看被打入APK包中的资源都有哪些,这里不做过多先容。

在充分理解了APK各个组成部分以及它们的浸染后,我们针对自身特点进行了剖析和优化。
下面将从Zip文件格式、classes.dex、资源文件、resources.arsc等方面来先容下我们创造的部分优化技巧。

Zip格式优化

前面先容了APK的文件格式以及紧张组成部分,通过aapt l -v xxx.apkunzip -l xxx.apk来查看APK文件时会得到以下信息,见下面截图:

通过上图可以看到APK中很多资源因此Stored来存储的,根据Zip的文件格式中对压缩办法的描述Compression_methods可以看出这些文件是没有压缩的,那为什么它们没有被压缩呢?从AAPT的源码中找到以下描述:

/ these formats are already compressed, or don't compress well /static const char kNoCompressExt[] = { \公众.jpg\公众, \公众.jpeg\公众, \公众.png\"大众, \"大众.gif\公众, \公众.wav\公众, \"大众.mp2\"大众, \公众.mp3\"大众, \公众.ogg\"大众, \"大众.aac\公众, \"大众.mpg\"大众, \"大众.mpeg\"大众, \公众.mid\"大众, \"大众.midi\公众, \公众.smf\"大众, \公众.jet\"大众, \"大众.rtttl\"大众, \"大众.imy\"大众, \"大众.xmf\"大众, \"大众.mp4\"大众, \"大众.m4a\"大众, \"大众.m4v\"大众, \"大众.3gp\公众, \公众.3gpp\公众, \"大众.3g2\公众, \"大众.3gpp2\公众, \"大众.amr\公众, \公众.awb\"大众, \"大众.wma\"大众, \"大众.wmv\"大众, \公众.webm\公众, \公众.mkv\"大众};

可以看出AAPT在资源处理时对这些文件后缀类型的资源是不做压缩的,那是不是可以修正它们的压缩办法从而达到瘦身的效果呢?

在先容怎么做之前,先来大概先容一下App的资源是怎么被打进APK包里的。
Android构建工具链利用AAPT工具来对资源进行处理,来看下图(图片来源于Build Workflow):

点击图片查看大图

通过上图可以看到ManifestResourcesAssets的资源经由AAPT处理后天生R.javaProguard ConfigurationCompiled Resources
个中R.java大家都比较熟习,这里就不过多先容了。
我们来重点看看Proguard ConfigurationCompiled Resources都是做什么的呢?

Proguard Configuration是AAPT工具为Manifest中声明的四大组件以及布局文件中(XML layouts)利用的各种Views所天生的ProGuard配置,该文件常日存放在${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt,下面是项目中该文件的截图,红框标记出来的便是对AndroidManifest.xmlXML Layouts中干系Class的ProGuard配置。

Compiled Resources是一个Zip格式的文件,这个文件的路径常日为${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_
通过下面经由Zip解压后的截图,可以看出这个文件包含了resAndroidManifest.xmlresources.arsc的文件或文件夹。
结合Build Workflow中的描述,可以看出这个文件(resources-${flavorName}-${buildType}-stripped.ap_)会被apkbuilder打包到APK包中,它实在便是APK的“资源包”(resAndroidManifest.xmlresources.arsc)。

我们便是通过这个文件来修正不同后缀文件资源的压缩办法来达到瘦死后果的,而在后面“resources.arsc的优化”一节中也是操作的这个文件。

笔者在自己的项目中是通过在package${flavorName} Task(感兴趣的同学可以查看源码)之提高行这个操作的。

下面是部分代码片段:

appPlugin.variantManager.variantDataList.each { variantData ->

当然也可以在其它构建步骤中采取更高压缩率的办法来达到瘦死后果,例如采取7Zip压缩等等。

本技巧的利用须要把稳以下问题:

如果音视频资源被压缩存放在APK中的话,在利用一些音频、视频API时尤其要把稳,须要做好充分的测试。

resources.arsc文件最好没关系缩存储,如果压缩会影响一定的性能(尤其是冷启动韶光)。

如果想在Android 6.0上开启android:extractNativeLibs=”false”的话,.so 文件也不能被压缩,android:extractNativeLibs的利用姿势看这里:App Manifest --- application。

classes.dex的优化

如何优化classes.dex的大小呢?大体有如下套路:

时候保持良好的编程习气和对包体积敏锐的嗅觉,去除重复或者不用的代码,慎用第三方库,选用体积小的第三方SDK等等。

开启ProGuard来进行代码压缩,通过利用ProGuard来对代码进行稠浊、优化、压缩等事情。

针对第一种套路,因各个公司的项目的差异,共性的东西较少,须要case by case的剖析,这里不做过多的先容。

压缩代码

可以通过开启ProGuard来实当代码压缩,可以在build.gradle文件相应的构建类型中添加minifyEnabled true

请把稳,代码压缩会拖慢构建速率,因此该当尽可能避免在调试构建中利用。
不过一定要为用于测试的终极APK启用代码压缩,如果不能充分地自定义要保留的代码,可能会引入缺点。

例如,下面这段来自build.gradle文件的代码用于为发布构建启用代码压缩:

android {

除了minifyEnabled属性外,还有用于定义ProGuard规则的proguardFiles属性:

getDefaultProguardFile(‘proguard-android.txt')是从Android SDKtools/proguard/文件夹获取默认ProGuard设置。

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

提示:要想做进一步的代码压缩,可考试测验利用位于同一位置的proguard-android-optimize.txt文件。
它包括相同的ProGuard规则,但还包括其他在字节码一级(方法内和方法间)实行剖析的优化,以进一步减小APK大小和帮助提高其运行速率。

在Gradle Plugin 2.2.0及以上版本ProGuard的配置文件会自动解压缩到${rootProject.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-files/目录下,proguardFiles会从这个目录来获取ProGuard配置。

每次实行完ProGuard之后,ProGuard都会在${project.buildDir}/outputs/mapping/${flavorDir}/天生以下文件:

文件名描述dump.txtAPK中所有类文件的内部构造mapping.txt供应原始与稠浊过的类、方法和字段名称之间的转换,可以通过proguard.obfuscate.MappingReader来解析seeds.txt列出未进行稠浊的类和成员usage.txt列出从APK移除的代码

可以通过在usage.txt文件中看到哪些代码被删除了,如下图中所示android.support.multidex.MultiDex已经被删除了:

R Field的优化

除了对项目代码优化和开启代码压缩之外,笔者在《美团Android DEX自动拆包及动态加载简介》这篇文章中提到了通过内联R Field来办理R Field过多导致MultiDex 65536的问题,而这一步骤对代码瘦身能够起到明显的效果。
下面是笔者通过字节码工具在构建流程中内联R Field的代码片段(字节码的修正可以利用Javassist或者ASM,该步骤笔者采取的是Javassist)。

ctBehaviors.each { CtBehavior ctBehavior -> if (!ctBehavior.isEmpty()) { try {

其它优化手段

针对代码的瘦身还有很多优化的技巧,例如:

减少ENUM的利用(详情可以参考:Remove Enumerations),每减少一个ENUM可以减少大约1.0到1.4 KB的大小;

通过pmd cpd来检讨重复的代码从而进行代码优化;

移除掉所有无用或者功能重复的依赖库。

这些优化技巧就不展开先容了。

资源的优化

图片优化

为了支持Android设备DPI的多样化([l|m|tv|h|x|xx|xxx]dpi)以及用户对高质量UI的期待,美团App中利用了大量的图片,在Android下支持很多格式的图片,例如:PNG、JPG 、WebP,那我们该怎么选择不同类型的图片格式呢? 在Google I/O 2016中提到了针对图片格式的选择,来看下图(图片来源于Image compression for Android developers):

通过上图可以看出一个大概图片格式选择的方法。
如果能用VectorDrawable来表示的话优先利用VectorDrawable,如果支持WebP则优先用WebP,而PNG紧张用在展示透明或者大略的图片,而其它场景可以利用JPG格式。
针对每种图片格式也有各种的优化手段和优化工具。

利用矢量图片

可以利用矢量图形来创建独立于分辨率的图标和其他可伸缩图片。
利用矢量图片能够有效的减少App中图片所占用的大小,矢量图形在Android中表示为VectorDrawable工具。
利用VectorDrawable工具,100字节的文件可以天生屏幕大小的清晰图像,但系统渲染每个VectorDrawable工具须要大量的韶光,较大的图像须要更长的韶光才能涌如今屏幕上。
因此只有在显示小图像时才考虑利用矢量图形。
有关利用VectorDrawable的更多信息,请参阅 Working with Drawables。

利用WebP

如果App的minSdkVersion高于14(Android 4.0+)的话,可以选用WebP格式,由于WebP在同画质下体积更小(WebP支持透明度,压缩比比JPEG更高但显示效果却不输于JPEG,官方评测quality参数即是75均衡最佳), 可以通过PNG到WebP转换工具来进行转换。
当然Android从4.0才开始WebP的原生支持,但是不支持包含透明度,直到Android 4.2.1+才支持显示含透明度的WebP,在笔者利用中是判断当前App的minSdkVersion以及图片文件的类型(是否为透明)来选用是否适用WebP。
见下面的代码片段:

boolean isPNGWebpConvertSupported() { if (!isWebpConvertEnable()) { return false

选择更优的压缩工具

可以利用pngcrush、pngquant或zopflipng等压缩工具来减少PNG文件大小,而不会丢失图像质量。
所有这些工具都可以减少PNG文件大小,同时保持图像质量。

pngcrush工具特殊有效:此工具在PNG过滤器和zlib(Deflate)参数上迭代,利用过滤器和参数的每个组合来压缩图像。
然后选择产生最小压缩输出的配置。

对付JPEG文件,你可以利用packJPG或guetzli等工具将JPEG文件压缩的更小,这些工具能够在保持图片质量不变的情形下,把图片文件压缩的更小。
guetzli工具更是能够在图片质量不变的情形下,将文件大小降落35%。

在Android构建流程中AAPT会利用内置的压缩算法来优化res/drawable/目录下的PNG图片,但也可能会导致本来已经优化过的图片体积变大,可以通过在build.gradle中设置cruncherEnabled来禁止AAPT来优化PNG图片。

aaptOptions {

开启资源压缩

Android的编译工具链中供应了一款资源压缩的工具,可以通过该工具来压缩资源,如果要启用资源压缩,可以在build.gradle文件中将shrinkResources true
例如:

android {

须要把稳的是目前资源压缩器目前不会移除values/文件夹中定义的资源(例如字符串、尺寸、样式和颜色),有关详情,请参阅问题 70869。

Android构建工具是通过ResourceUsageAnalyzer来检讨哪些资源是无用的,当检讨到无用的资源时会把该资源更换成预定义的版本。
详看下面代码片段(摘自com.android.build.gradle.tasks.ResourceUsageAnalyzer):

public class ResourceUsageAnalyzer {

上面截图中3个byte数组的定义便是资源压缩工具为无用资源供应的预定义版本,可以看出对.png供应了TINY_PNG, 对.9.png供应了TINY_9PNG以及对.xml供应了TINY_XML的预定义版本。

资源压缩工具的详细利用可以参考Shrink Your Code and Resources。
资源压缩工具默认是采取安全压缩模式来运行,可以通过开启严格压缩模式来达到更好的瘦死后果。

如果想知道哪些资源是无用的,可以通过资源压缩工具的输出日志文件${project.buildDir}/outputs/mapping/release/resources.txt来查看。
如下图所示res/layout/abc_activity_chooser_viewer.xml便是无用的,然后被预定义的版本TINY_XML所更换:

资源压缩工具只是把无用资源更换成预定义较小的版本,那我们如何删除这些无用资源呢?常日的做法是结合资源压缩工具的输出日志,找到这些资源并把它们进行删除。
但在笔者的项目中很多无用资源是被其它组件或第三方SDK所引入的,如果采取这种优化办法会带来这些SDK后期掩护本钱的增加,针对这种情形笔者是通过采取在resources.arsc中做优化来办理的,详情看下面“resources.arsc的优化”一节的先容。

措辞资源优化

根据App自身支持的措辞版本选用得当的措辞资源,例如利用了AppCompat,如果不做任何配置的话,终极APK包中会包含AppCompat中的所有已翻译措辞字符串,无论运用的别的部分是否翻译为同一措辞,可以通过resConfig来配置利用哪些措辞,从而让构建工具移除指定措辞之外的所有资源。
下图是详细的配置示例:

android {

针对为不同DPI所供应的图片也可以采取相同的策略,须要针对自身的目标用户和目标设备做一定的选择,可以参考Support Only Specific Densities来操作。
有关屏幕密度的详细信息,请参阅Screen Sizes and Densities。

.so文件也可以采取类似的策略,比如笔者的项目中只保留了armeabi版本的.so文件。

resources.arsc的优化

针对resources.arsc,笔者考试测验过的优化手段如下:

开启资源稠浊;

对重复的资源进行优化;

对被shrinkResources优化掉的资源进行处理。

下面将分别对这些优化手段进行展开先容。

资源稠浊

在笔者另一篇《美团Android资源稠浊保护实践》文章中先容了采取对资源稠浊的办法来保护资源的安全,同时也提到了这种办法有显著的瘦死后果。
笔者当时是采取修正AAPT的干系源码的办法,这种办法的痛点是每次升级Build Tools都要修正一次AAPT源码,掩护性较差。
目前笔者采取了微信开源的资源稠浊库AndResGuard,详细的事理和利用帮助可以参考安装包立减1M--微信Android资源稠浊打包工具。

无用资源优化

在上一节中先容了可以通过shrinkResources true来开启资源压缩,资源压缩工具会把无用的资源更换成预定义的版本而不是移除,如果采取人工移除的办法会带来后期的掩护本钱,这里笔者采取了一种比较取巧的办法,在Android构建工具实行package${flavorName}Task之前通过修正Compiled Resources来实现自动去除无用资源。

详细流程如下:

网络资源包(Compiled Resources的简称)中被更换的预定义版本的资源名称,通过查看资源包(Zip格式)中每个ZipEntryCRC-32 checksum来探求被更换的预定义资源,预定义资源的CRC-32定义在ResourceUsageAnalyzer,下面是它们的定义。

// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY

通过android-chunk-utils把resources.arsc中对应的定义移除;

删除资源包中对应的资源文件。

重复资源优化

目前美团App是由各个业务团队共同开拓完成,为了方便各业务团队的独立开拓,美团App进行了平台化改造。
改造时存在很多资源文件(如:drawable、layout等)被不同的业务团队都拷贝到自己的Library下,同时为了避免引发资源覆盖的问题,每个业务团队都会为自己的资源文件名添加前缀。
这样就导致了这些资源文件虽然内容相同,但由于名称的不同而不能被覆盖,终极都会被集成到APK包中,针对这种问题笔者采取了和前面“无用资源优化”一节中描述类似的策略。

详细步骤如下:

通过资源包中的每个ZipEntryCRC-32 checksum来筛选出重复的资源;

通过android-chunk-utils修正resources.arsc,把这些重复的资源都重定向到同一个文件上;

把其它重复的资源文件从资源包中删除。

代码片段:

variantData.outputs.each { def apFile = it.packageAndroidArtifactTask.getResourceFile();

通过这种办法可以有效减少重复资源对包体大小的影响,同时这种操作办法对各业务团队透明,也不会增加折衷相同资源如何被不同业务团队复用的本钱。

总结

上述便是我们目前在APK瘦身方面的做的一些考试测验和积累,可以根据自身情形取舍利用。
当然我们还可以采纳一些按需加载的策略来减少安装包的体积。
末了提一点,砍掉不必要的功能才是安装包瘦身的超级大招。
一个好的App的标准有很多方面,但供应尽可能小的安装包是个中一个主要的方面,这也是对我们Android开拓者职员自身的提出的基本哀求,要时候保持良好的编程习气和对包体积敏锐的嗅觉。

参考文献

Android application package (APK)

Zip (file format))

Build Workflow

Android AAPT Source Code

Reduce APK Size

Shrink Your Code and Resources

Manage Your App's Memory

Vector Drawable

Javassist

ASM

pngcrush

pngquant

zopflipng

android-chunk-utils

安装包立减1M--微信Android资源稠浊打包工具

减少 APK 的大小,Android 官方这样说

Google I/O 2016 条记:APK 瘦身的精确姿势

作者简介

建帅,Android技能专家,2015年3月加入美团点评,目前就职于到店餐饮技能部信息与交易技能中央。

到店餐饮技能部交易与信息技能中央,卖力美团点评美食用户端业务,做事于数以亿计用户,通过更好的榜单、真实的评价和完善的信息为用户供应更好的决策支持,致力于提升用户体验;同时承载所有餐饮商户端线上流量,为餐饮商户供应多种营销工具,提升餐饮商户营销效率,终极达到让国人“Eat Better、Live Better”的美好愿景!
我们的团队包含且不限于Android、iOS、FE、Java、PHP等技能方向,已完备覆盖前后端技能栈。
只要你来,就能点亮全栈开拓技能树。

不想错过技能博客更新?想给文章评论、和作者互动?第一韶光获取技能沙龙信息?

请关注我们的官方微信公众年夜众号“美团点评技能团队”。