在Novoda,我们一直都在探索新的方式来提高我们的App的用户体验。和平常为人熟知的用户体验相反,用户体验甚至在应用安装之前就已经开始了。原文链接
决定你的应用是否成功有几个关键要素。从用户第一眼从应用商店里看到你的应用开始,1. 优化应用的排序结果,让你的应用在拥挤的应用市场里站住脚跟,甚至力压群雄。2.让安装过程尽可能的顺利。
第一印象很重要!
接下来我们将会介绍我们如何一步步优化我们的应用,从用户首次启动应用之前开始。
臃肿的应用带来糟糕的用户体验,安装过程将会花费相当长的一段时间。
成长的烦恼
随着应用更加复杂,屏幕显示效果更加精细,应用也因此一步步变大。它的大小将随着时间——从2008年,Android发布开始——一路变大。现在,绝大多数的应用大小在15M、20M甚至超过50M。应用的功能增多,对应的代码、依赖库、asset资源文件也越来越多。而且,随着屏幕尺寸分辨率的提升,资源文件的分辨率也随之水涨船高。看图:
如果打开Apk分析它的结构组成,你会发现,头三个位置常常是同样的三类东西:一个或多个classes.dex,resources.asrc文件,res文件夹。有的时候,asset目录页包含了很多东西。
dex文件里的代码对Apk的大小有很重要的影响,对它进行瘦身需要用另外一整篇文章来讨论,这里就先不说了。
压缩过的resources.asrc资源文件的大小直接依赖于Apk里有多少资源和有多少配置选项,对它进行优化是另一个高级的主题;如果你感兴趣的话,有一些工具可以帮我们进行瘦身,但大部分情况下你并不会需要他们。
那还剩下什么可以优化?资源文件。哪些最容易进行优化?Drawale们!
解决方案?唔,变通策略
有好几种方法来解决日益增长的的Apk asset大小。有些很有效,但会导致一些警告(Some are very effective, but bring caveats with them.)。其他的方法虽然有点快但效果会打点折扣。我们来看下具体有哪些方法。
Apk Splits(Apk分割)
生成小Apk的一种方式是:用构建工具生成对应屏幕密度的Apk包。这很容易配置,而且,如果你有native库的话,还能通过配置,生成指定架构的Apk。Google Play商店和Amazon应用商店让我们可以方便的分发合适的Apk安装包到用户的机器上,从根本上帮我们做好了这些事情。
最终你会得到一堆的Apk包,以及对它们进行测试、维护、版本更新、归档。这处理起来相当的棘手,因为它们的数量会随着分割选项的增加而指数级增长。如果开发流程中涉及到手工操作,那对这些Apk进行测试将耗费大量时间。
WEBP
另一个减少Apk资源大小的方式是使用WEBP,Google开源的图像格式,派生自视频编码格式VP8,最广为人知的是其在[WEBM]编解码器(http://www.webmproject.org/)上的应用。为什么要使用WEBP?首先,它比过时的有损压缩方式Jpg更加高效,同样的,它的无损压缩的方式也比PNG高效。
$ ls -l image-test/
39610 24 May 16:45 carota.png
19031 24 May 17:09 carota-optim.png
5868 24 May 17:03 carota.webp
同样的一张图片,直接输出为PNG、优化过的PNG、使用cwevp转换成WEBP格式后的大小比较。
在性能上,谷歌已经做了严密的研究,具体可以看这几篇文章:有损WEBP vs JPG,无损WEBP vs PNG
显然WEBP更有效率,为什么我们不直接使用WEBP呢?这是一个双重的问题。首先,兼容性问题。对有损WEBP格式的支持是从Android 4.0开始的,而对无损WEBP的支持要Android 4.2.1才支持。而且,WEBP图片不能用作启动图标。
第一个限制并不是很严格,很少有应用依然支持API 13或更低版本的设备。不幸的是,API 18的要求就没那么容易满足了。使用无损的WEBP格式是最让人感兴趣的地方,因为PNG是项目中用得最多的格式。
还有,没有多少内容创作软件(Photoshop, Illustrator, Sketch, 以及很多其他的应用)支持导出WEBP格式的文件。你必须在你的设计流程里添加一个步骤:使用cwebp、imagemagick(一个PhotoShop插件)或其他工具进行格式转换。设计师不习惯使用这种格式,对于它的使用推广可能会遇到一些阻力。
如果可以的话,依旧强烈推荐使用WEBP作为图片格式。至少在通过网络传输无损格式的图片上使用它,在同等质量的JPG上,它拥有更小的体积或者在同等大小的体积上,拥有更高的质量。这需要API 14及以上的支持,在大多数app的minSdkVersion配置上应该是支持的。
矢量图片(Vector Drawables)
应用内使用的图片很多是一些简单的、单色的图标,我们可以使用矢量图来替换掉他们。Lollipop引入了对矢量图的支持,包括动画。矢量图是SVG的一个子集,相对而言对设计师也更加友好,大部分工具支持以SVG格式导出,还有对应的工具将其转换成最终要用到的矢量图。
Google官方发布了Support Library 23.2.0,为在旧版本的设备上使用矢量图形提供了官方支持(其他类库没有提供完整的特性支持也不是官方支持维护)。不幸的是,这个版本有内存泄露的问题,导致Google发布了一个新的版本,把这个特性给关闭了。后来Google在23.4.0这个版本恢复了对矢量图的支持,但在如何使用和何时使用这个特性上添加了一些比较强的限制。
手动压缩Drawable
最后的一个方法是,在把图片添加到res目录前,使用工具对其大小进行压缩。ImageOptim就是这样的一个工具,一个高效的工具,但只能运行在Mac OS X上。也有一些其他的替代工具(免费/自由软件/开源/付费),适用于Mac OS X和其他平台。
这些工具通过运行一系列的算法,从文件中移除多余的数据、优化压缩选项,甚至重新对数据流进行压缩,以达到最优的效率。举例来说:ImageOptim这个工具可以把一个24位的PMG文件压缩成一个8位的PNG,剔除所有的元数据,优化调色板,使用Google的Zopfli算法对数据流进行的重新压缩。
还记得文章最开始我们看到的那个Apk吧?那是写作的时候的最新版的Google+ APK。看起来它并没有对它的drawable资源做任何优化,如果我们在它的基础上使用ImageOptim进行优化,会有多少效果?
这还没完——这是个耗时的任务,这如我们前面提到的。
“真是个了不起的壮举”
为什么不试试用内置的AAPT PNG cruncher选项?除了不支持JPG格式,AAPT cruncher工具只做了一点点的优化,仅靠这点优化并不会帮我们减掉多少体积。详细情况可以通过观看Google I/O 2016上McAnlis关于图片压缩的演讲视频(链接最后附上)
构建流程
运行图片优化目前来说是一个额外用于提供Drawable的步骤。如何将这个步骤整合到我们的流程中来?一起来看下。
这应该是谁的责任
在你决定把文件加入app的时候,如果你决定人工进行资源文件的压缩,那么需要明确定义由哪个角色来进行这项工作。优化图片再放入APK,这是一件很重要的任务,鉴于我们已经放弃使用AAPT内置的压缩选项,我们必须明确这是设计师还是开发人员的责任。
你可能会问:为什么不用自动化来搞定它?是这样的,如果你只有很少的新资源会添加到你的APP里,使用自动化来做这个可能有点过度,或者比较浪费时间(优化一堆图片花费的时间开销为 aLot^N)
在我们的一个项目里,开发者和设计师坐下来讨论,决定如何将优化集成到我们的工作流程来,结果设计师自愿承担起了这个任务,感谢我们伟大的设计师们。
持续集成
一个很显而易见的主意是自动化我们的图片优化过程。毕竟,你也不想依靠人工去完成这些重复性的工作,是吧?而且,通过自动化,将会降低人工操作带来的风险,比如有些步骤忘了执行;通过自动化,让每个人都能从中解放出来。
什么时候做这件事合适呢?在CI服务器上设置一个cron任务,定期压缩所有的资源文件,或者每次执行CI构建的时候,进行资源文件压缩,这看起来是个不错的主意。然而,这通常是不切实际的。对所有的资源文件进行压缩将会耗费很多时间,如果经常做的话,时间花费会更多。这些算法通常在“尝试,直到无法做任何改善,或者我说你已经花费了足够的时间”的原则上。其他的算法,像Zopfli,仅仅是浪费时间罢了。一次完整的运行可能会花费几分钟到几小时不等,取决于很多的因素。
这也是为什么使用CI的方式进行资源压缩仅仅在你经常变动你的资源文件的情况下才有效的原因,因此你可以把多余的时间节省掉,或者拥有一个非常健壮的CI。
优化并不是幂等的(Optimisations aren't idempotent)
对上述过程的一个非常重要的告诫是:优化不一定是幂等的。这意味着,如果你使用工具如ImageOptim进行一次完整的图片优化,然后再使用另一个工具进行另一次完整的优化,最终得到的图片大小可能比原本的大小还大。这种情况可能是因为重新处理图片导致上一次进行的优化处理无效化了,这取决于使用的工具。
以AAPT为例,它是APK构建工具的一部分,负责打包APK,处理资源,还有其他的一些事情。许多人不知道的是,它也提供了一些基础的PNG优化功能(有且仅有PNG)。这个功能默认是开启的,而你可能需要关闭它。因为它会尝试重新压缩一遍图片,结果是有效的增大了我们优化过的图片的体积。
我们通过测试发现,保持aapt cruncher选项的开启甚至会让你的最终的APK大小比原始的还大。这不好。希望这个问题能早点被修复,以便我们可以移除这个步骤,让事情更加简单。
要禁用AAPT cruncher,在build.gradle文件里添加:
aaptOptions {
cruncherEnabled = false
}
注意:这一步并不会排除掉对点九图的预处理。
Colt McAnlis在这点上探究得更加深入,在这里我就不深入了。
结论
为你的APK瘦身,你能让你的用户更加开心。通过使用一些技巧,让看起来无法阻止的应用体积增长趋势停止。优秀的用户体验从不需要漫长的下载等待和安装开始。
致谢
感谢Google的Wojtek Kaliciński,几个月前他在Londroid上的演讲给我了很大的启发。他的演讲包含了很多超级有用的信息,远不止本文讨论的东西,也启发我优化我们日渐臃肿的APK的方式。推荐大家看下他的I/O演讲。
感谢Mike Wolfson和Rui Teixeira在编辑上对我的帮助和建议。
延伸阅读
如果你想了解更多,以下一些资源可以而帮助到你:
Wojtek Kaliciński在Google I/O 2016上的演讲Lean and Fast: Putting Your App on a Diet,以及他的文章。
Colt McAnlis在Google I/O 2016上的演讲Image compression for Android developers talk at Google I/O 2016,以及他的文章。