Android APK 瘦身 - JOOX Music 项目实战

1,409 阅读21分钟

导语

JOOX Music是腾讯海外布局的一个音乐产品,2014年发布以来已经成为5个国家和地区排名第一的音乐App。东南亚是JOOX Music的主要发行地区,由于JOOX Music所面对的市场存在很多的低端机型,并且这些市场的网络环境相对来说是比较差的,为了提升下载转化率,对JOOX Music进行APK瘦身是必不可免的。

JOOX Music版本大小变化

JOOX Music(后面简称JOOX)现在已经在进行V3.8版本的开发了,不过,在这之前JOOX经历了从V2.1版本的18M暴涨到V3.1版本的51M,再到V3.7版本的36M暴瘦,在这涨幅33M到跌幅15M的期间(JOOX Android端是往GP上放的,不过为了方便,这里说的Size就指APK的大小而不是Download Size),它到底经历过了什么呢?接下来让我们细细品味~

JOOX裁包过程经历V3.5, V3.6, V3.7三个版本,并不是一次性‘水到渠成’的哦。本文主要介绍JOOX产品在Android端的APK大小裁剪所用到的方法,主要从理论,实践以及可持续化三个主要的方面进行介绍。

一,理论知识

首先,先上一下理论知识,毕竟任何事情都是先有理论然后再去实践的嘛。JOOX的减包主要通过一下几方面进行的:

  1. 图片资源

  2. 代码方面(JAR包,so库等等)

  3. 资源混淆


这里会根据上面三个方面进行理论介绍。不过,在这之前需要先给大家介绍一个Android Studio2.2提供的神器:Analyze APK。

如图,我们只需要将要分析的APK拖入到Android Studio中,通过该工具我们可以清楚的观察到我们的APK中各个部分所占的比重是多少,而且通过目录索引我们可以快速定位出导致APK变大的是哪些文件从而加快我们的APK瘦身速度。想要了解更多可以查看如下文章:

《使用 APK Analyzer 分析你的 APK》

1. 图片资源方面

图片资源基本上都是APP中占比重最大的一块,而且对于图片资源的优化对比于其他的方式来说是最简单的,一般也是最容易看到效果的。所以,我们先从图片资源方面入手。

对于大多数APP来说,基本上图片资源主要都是由PNG和JPG来组成的,PNG和JPG是最常见的图片格式了,这里我就不做过多的介绍了。一般来说,对于图片资源减包这方面,主流的做法都是采取一下方式进行的:

  1. 删除无用资源

  2. 压缩PNG图片,并将能转成JPG的图片进行转换

  3. 压缩JPG图片


以上是我们最常见的做法,也是最有效的方式,不过对于JOOX来说,这里面多加了一项做法,那就是把图片向WebP进行转换。

盗图一张~

WebP是Google在2010年发布的,现在WebP包含有损压缩,无损压缩,以及透明通道的有损压缩,上图是WebP和PNG的比较。根据官方给出的数据,基本上对于PNG进行无损压缩能有效减小30%的大小,对比于JPG格式转换成WebP基本上也是30%左右,而如果将图片进行有损WebP化的话,基本上就只有原图的1/3大小了,所以WebP的压缩比例显而易见。最重要的是,最新的数据显示,WebP在解码方面基本上和PNG差不多,甚至有时候比PNG快!如果想对WebP有更多的了解可以看看这篇文章:

《WebP原理和Android支持现状介绍》

基本上,对于Android4.0的手机就有对WebP的支持了,不过根据相关的数据显示,对于Android 4.1才开始对WebP有了比较稳定的部分支持(即大多数只支持不含alpha通道的WebP图片),Android 4.2才基本完全支持WebP。

所以,这里总结一下JOOX为什么要使用WebP的理由:

  1. WebP图片相对于PNG和JPG占用的空间更小

  2. WebP在解码方面基本上和PNG差不多,甚至有时候比PNG快

  3. JOOX目前最低支持的Android系统为4.1,而Android 4.1对WebP有了初步的支持

2. 代码方面

为了方便大家阅读这一节,我下面用A,B,C英文字母标注了一下。

a)  代码方面,基本上也是和主流的方式差不多,启用代码混淆,删除无用代码,无用的JAR包,以及无用so库等等,可以很有效的减少APK的大小。

不过,上面说的都是比较简单的情况,比较蛋疼的情况在于,大多数时候你会发现你在使用第三方的JAR包时,你只想用它一丢丢功能,而且这部分功能的实现代码只有一点点,而你却要导入整个JAR包。更蛋疼的是,你还不得不用,这时候你肯定整个人都不好了。想一想,如果你需要的功能代码就占几十KB,而你却导入了个几百KB甚至以MB为单位的JAR包,你的leader不会砍你吗?如果不会,我只想说:这样的leader给我来一车!

开个玩笑^_^

b) JOOX在开发过程中碰到上述的情况,采用的方法是将我们需要的代码部分提取出来,并重新生成jar再导入我们的工程中,这样虽然操作麻烦了一些,不过也能减少很大一部分的APK大小。而且,有时候我们可以通过减少第三方的SDK升级或JAR更换来减小APK大小,毕竟一般来说,SDK只会越来越大(毕竟加了新特性),如果我们能确定当前版本的JAR包或者SDK继续使用下去没问题的话可以选择不用替换,这样也可以达到抑制APK Size增长的效果。

c) 还有就是,现在项目开发都是采用主工程依赖子工程的方式进行的,而子工程中很多时候需要使用到相同的组件,这时候我们需要将这些组件封装好并放入到通用的工程中,子工程去依赖该工程实现代码的复用,避免子工程重复实现从而减小APK大小。

d) 当然,这里也少不了so库的动态加载以及Android的插件化技术啦,通过这两个方式也可以达到减包的目的。

  • 关于so库,一般都是打包在APK内部然后在运行时再加载运行的,而这里说的动态加载是指我们将APP中并非启动必用的so库放在服务器上,当用户需要使用某项功能时再从网络获取相关的so库并加载运行,一般我们都是使用System.loadLibrary来加载so库的,而通过SDcard加载so库的话需要我们将该so库拷贝至APP内部存储空间,再调用System.load方法进行加载即可。对于JOOX来说,其当中使用到的DTS的相关so库如果采用此方式来加载的话可以减少6.5M的APK大小,可见此方式的优化效果有多‘流弊’。不过此方案的难点不在于上面的描述,在于如何对so库的安全校验,更新替换策略等等,但是这不是本文的重点。更多相关的知识可以搜索以下文章:《Android动态加载补充 加载SD卡中的SO库》

  • 而Android的插件化原理基本上就是以ClassLoader为基础进行的啦,Android中包含DexClassLoader和PathClassLoader,而我们则是通过DexClassLoader加载外部的APK, JAR或者DEX文件来加载我们下发的插件,当然,完成了这一步还不算完,后面还需要考虑该用代理Activity还是通过Hook系统的startActivity来启动插件的Activity。目前已经有很多的方案存在了,大家可以去学习一下,说不定哪天就用上了呢?

e) 说了这么多,再提一个基本都不陌生的神器:ProGuard。ProGuard不仅能够帮助我们移除无用的代码(当然,不可能很完全),而且使用简短并且无意义的字符来重命名我们的类名,字段以及方法,从而达到对代码进行压缩,优化,混淆的效果,从而使我们的APK更小,而且使得APK更难以被逆向工程。

可以去官方那了解更多:
www.guardsquare.com/en/proguard

f) 这里总结一下如何从代码方面减小APK大小:

  1. 删除无用的代码,无用的JAR包,无用的so库

  2. 对于使用到的第三方库,尽量做代码提取,将JAR包中用不到的代码去除,尽量减少SDK或者JAR的更新。

  3. 项目中尽量实现代码复用,提取公共组件工程,以供其他项目使用

  4. 动态加载so库以及插件化技术

  5. 启用代码混淆

3. 资源混淆

关于资源混淆,其核心就在于对APK中resources.arsc文件的修改,大家都知道,Android项目中res目录下每个资源都会有其对应的ID,而ID在R文件下关联着资源名称(如R.string.xxx),通过这些ID我们可以很方便的锁定某一项资源,而资源混淆的原理就在于修改ID对应的资源路径进行优化处理(如将res/drawable/xxx修改成res/drawable/a,或者修改为r/d/a),通过这个方式可以大大减小resources.arsc文件的内容,从而达到减包的目的。

资源混淆方面,JOOX采用的是微信提供的Android资源混淆打包工具。工具传送门:

《安装包立减1M--微信Android资源混淆打包工具》

关于resources.arsc的生成可以搜索这篇文章:

《Android应用程序资源的编译和打包过程分析》

二,减包实战

上一节介绍了相关的理论知识,讲的比较无聊枯燥,这一节尽量来点有趣的内容,帮助大家在快乐中学习。

一样的,按照图片压缩-代码优化-资源混淆方面进行实战讲解。

1. 图片方面

现在JOOX最低支持4.1Android系统的手机,系统本身对于WebP有了初步的支持。

目前大多数Android App基本都只包含PNG和JPG的资源图片,WebP相对来说由于手机支持的不是很完备(基本上Android4.2及以上才完全支持WebP),所以使用率相对就低一些。不过这里还是要介绍如何使用WebP以及使用它的好处。

1.1 应用图片资源格式(PNG, JPG, WebP)

1.1.1 PNG和JPG图片

PNG和JPG图片是大家见得最多的图片格式了,这里就不多介绍了。相信大家经常有这样的感受,每当UI给出视觉切图的时候,心中总会有种万马奔腾而过的赶脚——什么?我才刚减的包又被你搞大了?

就拿JOOX项目的一个需求来说,UI给的图片统一PNG格式,整个文件夹包含15张切图,大小2.88M…这要直接放进去别说leader要砍我了,赶紧跑路才是真的。

回到正题,相信上面说到的是开发新需求中经常碰到的事情,所以关键点在于我们如何缩小UI给的图片而又不让他们的像素眼发现。

一般我们从UI那里拿到的图片都是PNG格式的图片,不过我们都需要对其进行简单的压缩然后再放到项目当中(理由大家都知道了吧),不过你会发现对UI给的PNG图片进行PNG压缩好像没有什么太大的效果…这里大多数人的做法是筛选出可以不使用alpha通道的PNG图片转为JPG图片,你会发现效果大大的好,如图:

原图

转JPG后

这里我只简单的进行了JPG转换,图片大小裁剪效果怎么样就不解释了,大家可以看的出来。这是我们经常做的方式,不过你以为这样纸就不会被leader砍了?而且还有一些特别恶心的图片,Size又大个,又要alpha通道,压缩一下下都能被看出来的怎么办?我也很绝望呀。

too young too simple

1.1.2 WebP登场

前面的理论篇已经讲了WebP的知识以及列出了相关的文章,不过这里我还是贴一下,方便大家“穿越”过去。想对WebP有更多的了解可以看看这篇文章:

《WebP原理和Android支持现状介绍》

WebP格式是Google2010年推出的一种图片格式,支持有损和无损,可以包含alpha,也可以不要等等,不过这些都不是关键,关键是它的裁包效果呀。先上一下效果图。同样是拿之前的bg图做无损压缩:

转换WebP后

啊啊啊啊啊,震惊啊,感动中国十大—-扯远了。比较原图的900多K,这几十倍的差距…而且显示效果连UI都看不出来有什么两样。这里是使用转换后的JPG图进行转换的,不过用原图进行转换也是24KB左右。

1.2 确定需要WebP化的图片方案

前面提到了,Android4.2开始基本才完全支持WebP(4.0有些手机可能不支持,4.1可能不支持有alpha通道的WebP),而JOOX本身最低支持的系统版本为4.1,怎么办?这里采用了折中的方式,将可以去掉alpha通道的图片进行WebP化,其他图片尽量都进行一次压缩。不过如果以后能将大部分甚至是全部图片WebP化,估计应用大小会大大减小。

说到这里,可能有人感觉很麻烦,还不如直接全部压缩一下呢。这里就需要用数据来说说为什么要用WebP啦。

这里我分别用未处理的包,PNG压缩后的包,WebP部分图片的包以及WebP+PNG压缩后的包进行了对比。先给出一些数据:

  1. 对res目录下的图片进行PNG压缩之后文件夹整体大小小了2.2M

  2. 对res目录下的图片进行部分图片WebP化之后小了1.8M

  3. 对res目录下的图片进行上述方法两者结合小了4M左右。

不多说了,直接上图:

原包VS PNG压缩后的包

原包VS WebP化后的包

原包VS PNG压缩+WebP化后的包

发现了吗,我们对PNG图片进行压缩后,虽然res文件夹大小整体小了2.2M,但是打包出来后APK只小了350k,而对部分图片WebP化后res文件夹小了1.8M,打出来的包小了有1.5M。而采用PNG压缩+WebP化打出来的包Size减少量差不多就是它们两者之和。现在,你还敢说不打算考虑用用WebP咩?

这里提一下在裁包过程碰到的神秘事件,先上图:

一张WebP引起的神秘事件

这里截图了在持续集成平台上dailybuild生成的包大小的记录,最下面的红框圈着的,是我将一张2kb的图片转换成WebP后(转换后2KB以内)打出来的包先对于上一个包小了103kb…是不是觉得很奇怪?更奇怪的还在后面,后来发现这张图片没有用到,然后将它删除掉,然后…对应第一个红框,包大了81kb…原因我也不知道,不过感觉应该是APK在打包过程对于WebP压缩的更好?

1.3 项目res资源裁剪流程

在确定需要WebP化的方案之后,我们该开始裁剪了咩?当然不是啦。

在这之前我们一定要学会使用Android Studio提供的Analyze APK哦,通过该工具快速定位项目中的大图以及找出需要WebP化的图片,提高裁包的速度。

现在可以开始我们的裁包啦,由于项目中图片资源过多,我们把目标锁定在文件大小大于30KB的图片,对于小于30KB的则不管啦。

我们将APK拖入Android Studio中,即可看见APK各部分所占用的大小,然后通过查看res下的资源查找占用大的图片。如下图:

看,我们一下子就能找出大图在哪了,然后可以把能WebP的大图揪出来啦。不过找出的大图在WebP化的过程需要注意一点哦,像JOOX Android端最低支持4.1的系统,就要小心转换之后的图片包含alpha通道,所以需要先将图片转JPG去掉alpha通道,然后在转换成WebP,这些细节都要多加注意噢。

简单的总结一下流程:

  1. 通过APK Analyzer找出能WebP化的大图

  2. 将该图片转JPG,去掉alpha通道 //根据实际情况考虑哦

  3. 将JPG转WebP,并压缩其余PNG图片

  4. 最总要的一点,项目中没用到的图片赶紧删掉啦啦啦

是不是很简单?o(∩_∩)o

1.4 总结

这里主要介绍了JOOX在图片资源方面如何进行包Size裁剪,并将JOOX的这方面走的路线介绍给大家(什么?你不知道?当然是图片WebP化啦)。通过本文可以看见,转换成WebP之后的图片不仅占用的空间小,最主要的是生成的APK也减小了很多。通过本次减包,在图片方面JOOX大约减5M左右的APK大小,效果不是一般的好哦。

当然,WebP也有相关的缺陷,比如压缩速度慢,支持得不太好等等,不过这些都不是问题噢,压缩的话我们完全不用考虑呀,4.0及以下的可以采用相应的库来支持呀,反正是个问题基本都是能解决的啦。

我承认我说错了(┬_┬)

2. 代码方面

其实在这一方面并没有太多要说的,不过呢,为了保证文章的完整性,我还是要“扯”一会 ~ ^_^

代码方面的减包,基本上就是按照理论一节的方式进行的,去除无用JAR包,无用so库算是比较简单的事情了,麻烦的地方在于去除无用代码和提取第三方库使用到的代码(当然,第三方库里面的资源就按照图片篇的处理啦)。

裁剪so库,JAR包之前

裁剪so库,JAR包之后

可以清楚的看见,通过裁剪so库,JAR包,我们实现了5.4M的瘦身,效果很明显啊有木有!当然,这也不是说裁就裁的,JOOX在这次裁包中进行了一些架构的调整以及大量的代码调整才得到的效果。

代码这里的话大家应该都有同样的感受,就拿JOOX来说,JOOX这里合入了P2P直播的代码,而P2P直播那边使用的是Glide作为图片加载框架,JOOX本身就实现了自己的图片加载框架,项目中又存在一些第三方使用了Universal-ImageLoader框架,这就蛋疼了,本来只需要一个框架就能搞定的事情,因为合入了其他工程代码导致了存在多个相同功能的框架代码,导致项目不经意间就变大了好几百KB。所以,JOOX在这方面进行了框架的统一,即统一使用Glide图片加载框架,这样可以减少多出的框架的代码占用的空间。

由于JOOX目前还没有实现动态加载so库以及插件化的方式,所以文章就在理论里面给大家介绍了一下能够通过这两个方式达到的效果。当然,JOOX下一步要做的也是要朝这两个方面靠齐啦。

3. 资源混淆

同样的,为了方便大家,工具传送门:

《安装包立减1M--微信Android资源混淆打包工具》

资源混淆采用的是微信提供的混淆方案,不过该方案可能和热补丁方面有些冲突,不过没关系,就如上面所说,问题基本都是能解决的,毕竟微信本身也有热补丁。

其实使用该工具比较麻烦的地方在于列出我们的白名单列表(毕竟人家都给我们实现工具了),将不需要混淆的资源加入白名单,防止混淆之后应用运行异常。而如何将所有白名单找出来呢?当然是使用CTRL+SHITF+F(即全局搜索)啦,这还用我说咩?直接查找getIdentifier基本就能将需要保护的对象给找出来啦。

通过该方式JOOX基本减包了1.5M左右,可以看见,资源混淆减包的方式还是‘灰’常强大的,当然,要注意它列出的注意事项。

三,可持续化

前面说了辣么多减包的操作,这里就不说这些东西啦,就讲讲我们该如何进行可持续化减包吧。

从前面的减包过程可以知道,APK大小占比重最大的算是图片了,所以这里也是主要针对图片进行可持续化处理以达到持续减包的效果。好,现在请大家拿出自己的钱包多翻几下,发现问题了咩?现在月底啦,我们的钱包只会越来越扁,而需求只会越来越多啊!

程序员&产品

开个小玩笑,跑题了~如上所说,需求是不会有做完的一天的,所以,UI也不会停止给出新的切图,如果我们不对UI给的图片进行处理的话,过不了多久就会发现,我们之前减包基本就是白干活啊。所以,我们需要对图片进行可持续化处理,以保证我们的应用不会在短时间内快速增大。

当然,你觉得和UI说一声以后给图记得压缩就好了…但是,你会发现,换了几批UI之后,又回到了从前的样子,又要开始新一轮的包裁剪了。而且UI还不一定记得压缩图片,你每次拿到切图都要自己检测一下,多累呀。

所以控制APK大小的任务最后还是落到我们的头上,怎么办捏?当然是操起键盘就是干呀!JOOX在这方面有两点参考实现:

  • 第一点: JOOX在gradle中加入了相应的图片压缩task,每当版本发布之前会执行该task,将新加入的图片进行一次无损压缩(为什么要无损压缩?因为有损压缩可能会导致图片出问题,压缩完之后需要肉眼查看,比较麻烦,而且前面也提到了,对于PNG进行压缩之后对于APK的裁剪效果不是很明显),当然,能够转换成WebP的图片需要开发人员进行自己转换,因为Android Studio 2.3提供了WebP转换的工具,所以很方便。

  • 第二点: JOOX在持续集成平台上集成了大文件监控报警机制,如果在提交SVN之后编译出来的包大小相对于上一个包的大小超出了相应的阈值,将会进行报警以及生成相应的BUG单给对应的开发人员进行提醒,开发人员在收到BUG单之后进行相应的处理。这样可以防止可以避免使用的大文件积累到后面而使得我们不得不再次大动干戈的进行包Size裁剪。

四,展望未来

接下来,JOOX要走的方向即文章提到的,so库动态加载和插件化技术,通过这两个方案预估能够再为JOOX带来几M左右的包大小裁剪,实现真正的完美瘦身。当然,瘦身虽好,但是也是要付出相应的代价的呀,不是说瘦就能瘦的。通过动态加载so库进行减包的话需要考虑业务划分呀,可能会影响用户体验等等,缺点也是相应的存在的,所以说,凡事都是需要做两手考虑,世上可没有后悔药吃哟~



如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~