如何优雅地将你的APK安装包减少20%

如何优雅地将你的APK安装包减少20%_第1张图片
45ba10e9e3029116ad0be19134c50f2a_thumb.png

一、写在前面

如何精简Android市场安装包(APK)体积大小一直是Android开发者比较关注的点,因为它对应着用户升级等待时间和流量消耗,我们在网络上可以搜索到很多资料,比如:

  • 如何将 APK 大小减少 6M 的
  • Android APP终极瘦身指南

为什么我还要写这篇文章呢?首先,我觉得上面这些文章里面的思路固然正确,但是只是简单罗列一些方法,而并没有实践之后的方法论;其次,我们项目组除了使用上面提到的这些常规方法之外,还有一些自己积累的经验,我觉得很有价值拿出来跟大家分享。标题中我特意加了一个关键字--优雅,主要指的是我们有一些自动化方面的实践,相当于一旦你开始使用,那么就会永久获益,而不需要每个版本都需要人力投入。

1.1 项目现状

我目前维护的Android项目是从12年1月份开始的,到今天已经发布到了8.x版本,上一个版本线上包大小在14.3M,从迭代时间和项目大小来看,已经很给力了,我经常看见一些刚开发一两年的APP大小就已经超过了20M.....

首先我们对APK大小的变化做了自动化统计,我们使用的自动化集成环境是jenkins,我在jenkins上面配置了一个Plot图表插件,每次上线打出APK时它会自动记录下来APK的大小,然后生成曲线图:


如何优雅地将你的APK安装包减少20%_第2张图片
image.png

从曲线上可以看出来,有几个版本APK大小较上一个版本会变小,那是因为我们在产品迭代过程中固定的周期会对APK进行一次瘦身,这个周期大概会在5-6个大版本,这是一个很好的习惯,也是我们APK五年迭代之后还这么轻量级的原因,如果大家有持续集成环境(比如jenkins),可以统计每次发布的包的大小,这样就可以生成一个APK大小变化曲线,作为瘦身的参考依据。

好,收回来,我们上个版本的线上包大小是14.3M,刚好最近业务需求较少,我觉得是时候来一波瘦身了,其中还参杂了一个技术需求:接入微信的资源压缩框架AndResGuard。调研时用这个库简单处理了一下线上APK,发现可以减少体积大概在0.75M左右,所以定了一个大概的目标:减少APK1.5M~2M左右

二、 开始减肥

所谓APK瘦身,无非就是从两个思路去减少,1、减少代码的大小,对应于APK中的dex文件;2、减少资源的大小,对于APK中的其他部分。

2.1 代码压缩

关于如何减少dex文件大小,目前我了解到的主要有以下几种途径:

  • 删除无用的代码逻辑,删除无用的产品逻辑;--->针对源代码 .java
  • proguard混淆代码; --->针对.class文件

它减少体积的原理是首先会删除掉我们源代码里面没有使用的类或者字段,然后分析调用关系,把一些代码做内联,最后把我们代码中的类名 方法名和变量名替换成更加短小的无意义名字。
其实class -> dex格式也可以在一定程度上减少代码的大小

  • facebook的redex;----> 针对dex文件

redex我们项目里面没有使用,主要原因是和我们的热修复框架robus冲突(好像暴露公司了...),所以没有深入去研究

关于代码压缩,我们在很早就做了上面的事情,所以这一次瘦身并不考虑。

2.2 资源压缩

首先,我们平时使用资源主要有两类,一类是可以生成资源ID,比如我们放在 drawable-xxhdpi中的图片;一类不生成资源ID,比如assets目录下面的东西。

2.2.1 lint检查删除无用资源

这个是最简单的,Android Studio自带lint检查,我们输入unuse关键字就可以看到各种选项。


如何优雅地将你的APK安装包减少20%_第3张图片
image.png

2.2.2 assets文件删除和压缩

需要注意的是lint能够检查出来drawable-xxhdpi中的资源是否被引用,但是不能找出来assets里面是否有无用的资源(lint是根据资源id去做索引的,如果一个有资源id的文件没有代码或者xml指向,那么lint认为就是一个无用资源),所以我们需要手动去判断。你可以全局去搜索一个文件的文件名,因为项目使用这个资源必须要通过文件名的方式去打开,所以如果整个项目都搜不到,那么这个文件多半就是没有被使用的。

我们在assets中通常会放置各种资源:
一类是html、xml 或者json文件;对于这种文件,我们不需要考虑其可读性,把所有的格式字符和注释都去掉吧,下图是一个我们HTML文件,可以看到被我弄成了一行,删掉了各种注释和格式字符:


image.png

一类是javascript文件,那我们可以让前端帮忙,给做一下混淆压缩;


如何优雅地将你的APK安装包减少20%_第4张图片
image.png

所以对于assets文件的压缩遵循一条原则,精炼、不要考虑可读性。

注意一下:某些情况下,如果我们需要在assets中放入一个大文件,那么我们可以把它压缩成一个zip,然后在代码运行时加压到sdcard,这样也可以大大减少APK体积,不过目前在我们项目中没有遇到。

2.2.3 webp图片压缩

首先说一下,如果你的项目minsdk < 14,这个小节对你没有意义,因为小于14的系统原生不支持webp(可以引入第三库支持)。

关于什么是webp格式,建议可以看一下腾讯团队的调研-WebP 探寻之路。现在很多公司都在使用webp做后端图片压缩,比如我们通过picasso去加载一张图片的时候,可以把这张图片转化成webp,可以减少png、jpg等图片的大小。
同样,对于安装包来说,我们也可以把项目里面的资源图片用webp进行压缩,可以达到减少体积的作用。那么怎么把一张png或者jpg压缩成webp呢?还好,我们可以找到一个工具叫cwebp,下载地址戳这里,它的基本使用方法如下:

cwebp -q 80 -m 6 ./a.png -o ./a.webp
  • -q 80 代表压缩质量,0~100,一般建议75~80
  • -m 6 压缩方法次数,0~6,次数越大,压缩时间越长,但是压缩比更好
  • a.png 和 ./a.webp 代表输入输出

更多的 cwebp使用姿势可以戳这里

有了这个工具,你就可以把项目里面的图片转化成webp了?nonono,现在看起来还不够优雅,主要有两个问题:

  • 我需要一个个去找到项目中的图片,然后跑一下命令,再把之前的图片删除...累人不说,对于aar中的资源看起来我们就无能为力了。
  • 虽然android api level 14支持了webp,但是一直到android 4.2.1才修复带透明度的webp的显示问题,如果你的minsdk没有大于等于18,带透明度的png是不能进行webp压缩的。

这里有个蛋疼的问题,Android 4.2大版本有三个小版本:4.2 4.2.1 4.2.2 ,但是这三个版本的api level都是17,所以尽管我们项目目前minsdk是17,也不能全部压缩。

基于此,我写了个插件,其实也很简单,就是在打包过程中,在gradle mergeResource任务之后,processResource之前,对APK中所有的图片进行遍历,然后自动压缩成webp,并删除原来的图片,这个插件我已经上传到github了,欢迎使用欢迎提issue。

当然,如果你只需要简单使用,那么你可以直接接入插件就好了。

   //project buildscript
   buildscript {
     repositories {
      jcenter()
    }
    dependencies {
      classpath 'com.chuyun923.android.plugin:webpcompress:3.0.0'
    }
  }
   //module script
    apply plugin: 'webpcompress'

    webpCompress {
     q = 80  //压缩比例 0~100,100是无损压缩,默认80
     skipDebug = true //debug下是否开启webp压缩 默认不开启
     cwebpPath = "/Users/pengliang/Downloads/libwebp-0.6.0-rc3-mac-10.12/bin/cwebp" //cwebp命令的路径
     openLog = true //是否打开log
     filterAlpha = true //是否过滤有透明通道的图片
   //配置压缩白名单
     whiteList = [
           "notify_panel_notification_icon_bg.png"
     ]
}

需要注意的是,这个插件依赖于cwebp命令,所以你在上面的下载地址下载好之后,要把路径赋值给cwebpPath。

如果你的minsdk >= 18 ,那么就可以关闭 filterAlpha=false了,这样所有的图片都会尝试进行webp压缩。最后,会生成一个 projectbuildDir/outputs/webpcompressoutput.txt 文件,里面记录了插件进行压缩的记录log。

2.2.4 微信资源混淆

AndResGuard这个插件其实可以做两件事情,一是和proguard一样,把我们命名的文件名或者资源名简化为"icon_change_city.png"->"s.png”,这样可以显著减少APK中的resources.arsc文件大小。二是可以使用7zip对APK包进行压缩。由于我们项目目前在使用v2签名,AndResGuard的7zip压缩不兼容v2签名,所以我们项目并没有开启APK压缩。

资源进行混淆之后,我们再使用 Resources.getIdentifier(String name, String defType, String defPackage) 或者 Resources.getResourceName(int resid) 的时候就会失效,如果有这样的需求,记得配置白名单

2.2.5 字体提取

问题产生的原因是视觉给的ttf字体文件通常都是在网上copy的,里面包含了很多东西,以我们在使用的一个字体为例:

如何优雅地将你的APK安装包减少20%_第5张图片
image.png

这个字体大小是98KB,我们可以看到这个字体里面有数字 大写字母和小写字母,但是其实这个字体我们只是用来展示一个评分而已...
所以我们如果把里面的数字字体提取出来,那么这个字体文件(ttf or otf)就能显著减少!

我找到了fonttools,这个工具里面有个命令:

pyftsubset ~/Desktop/Old.TTF --text="0123456789."

就提取出来了只包含数字的字体,如下图:

如何优雅地将你的APK安装包减少20%_第6张图片
image.png

这个字体大小从 98KB->3KB

2.2.6 图片压缩

可能你要说了,前面不是已经说过了webp对图片进行压缩了么?为什么还需要图片压缩?别忘了,少年,我们很多png是不能进行webp化的,至少目前是这样。
网上能找到的各种压缩方法,我一直使用 https://tinypng.com/,但是如果你是直接上传到网页,然后压缩完成之后,重新导入项目,那就太不优雅了。
我们公司平台测提供了一个命令行脚本,只要一跑,就会把所有的图片进行tinypng压缩,原理很简单,遍历一下项目的文件夹,然后调用tinypng的API:https://tinypng.com/developers,由于不是我自己写的这个命令行,所以不能贡献出来给大家,但是思路应该还算简单。

2017-08-17日更新
底下评论有同学说蘑菇街有类似的插件,我之前也记得我看见过,写文章的时候找了一下没有找到。所以这里补上来:
TinyPIC_Gradle_Plugin

2.2.7 移除x86

解了几个线上的大厂的APK,几乎都已经去掉了x86的支持,所以我们还有什么犹豫的呢?x86下面的so大小大概在500KB左右,我们只需要去掉在外发的包中的x86即可,内部的话还是需要模拟器调试的:

debug {
    ndk {
        abiFilters "armeabi", "x86"
    }
publish {
    ndk {
        abiFilters "armeabi"
    }
}

2.2.8 测试代码分离

所谓测试代码分离,主要分为两部分:

  • 不需要发布到线上的第三库,比如leakcanary,stetho等,只需要在debug阶段需要的代码,我们可以直接给compile设置buildType
image.png
  • 测试逻辑代码,现在基本所有测试包都会有一个测试入口:
如何优雅地将你的APK安装包减少20%_第7张图片
image.png

这个测试入口里面的代码量也很可观,之前的做法可能是存一个标记,如果是线上包,就把这个入口关闭。这样就有个问题,这些代码仅仅是没有入口而已,但是线上包里面是有代码和资源的,所以我们可以利用Gradle的buildType文件夹来把测试代码完全分离出来:


如何优雅地将你的APK安装包减少20%_第8张图片
image.png

这是我拆出来可以放到unpublish flavor下面的代码和资源。

三、结果

经过上面的优化之后,我重新打包:
线上包:14.3217945M
优化完之后的包:11.4988794
优化大小在:2.8229M
APK体积减少:20%

ps:有同学可能会说,现在手机闪存越来越大,用户已经不太会关注APK的几M的大小了,看表面来说确实这样,14.3M的APK和11.4M的APK对用户来说其实并不敏感,但是如果开发人员自己都没有去持续关注,没有对项目优化有热情,很有可能我们项目现在已经30M了....

你可能感兴趣的:(如何优雅地将你的APK安装包减少20%)