分享

App瘦身最佳实践(下)

 dxw121 2016-12-22

限制灵活性,减少layout文件


减少layout文件有两个方法:复用和融合(include)。


复用layout文件


把一些页面共用的布局抽出来,这无论是对layout文件的管理还是瘦身都是极为有用的。


就比如说任何一个app的list页面是相当多的,从布局层面来说就是一个ListView或者RecyclerView,其背后还可能会有loading的view,空状态的view等等,所以我的建议是建立一个list_layout.xml,其余的list页面可以复用或者include它,这样会从很大程度上减少layout文件的数目。


融合layout代码


对于可以被复用的layout我们可以做统一管理,但是对于不会被复用的layout怎么办呢?假设一个页面是由两个区域组合而成的,fragment的做法是一个页面中放两个container,然后再写两个layout,但实际上这两个layout经常是没有任何复用价值的。我希望找到一种方式,在view区块还没有复用需求的时候用一个layout搞定,需要被复用的时候也可以快速、无痛的拆分出来。


1. UiBlock


UiBlock是一个类似于fragment的解耦库,它可以为同一个layout中不同区域的view进行逻辑解耦(因为layout可预览的特性,ui定位方面不是难题),它能帮我们尽可能少地建立layout文件。


如果未来需求发生了变动,layout文件中的一块view需要抽出成独立的layout文件的时候,UiBlock的逻辑代码几乎不用改动,你只需要把抽出的layout文件include进来,然后在include标签上定义一个id即可。而这个工作可以通过as的重构功能自动完成,绝不拖泥带水。





2. ListHeader




我将listView和它的没有复用价值的header放到了同一个layout中,然后在activity中利用上述代码进行了操作,最终完成了用一个layout文件给listView加头的工作。这段代码我很久没动过了,有利有弊,放在这里我也仅仅是举个例子,希望可以帮助大家扩展下思路。


动态下载图片


做过滤镜和贴纸的同学应该会注意到贴纸、表情这类的东西是相当大的,对于这类的图片资源我强烈建议通过在线商店进行获取。这样既可以让你踏踏实实的卖贴纸,又可以减小应用的大小。这么做虽然有一定的复杂度和出错概率,但投入产出比还是很不错的。

准确放置不同分辨率的图片


这个虽然不算是app大小的优化,但是如果你放错了图片,对于app启动时的内存大小会有一定的影响

思考一下,如果把一个本来应该放在drawable-xxhdpi里面的图片放在了drawable文件夹中会出现什么问题呢?
在xxhdpi设备上,图片会被放大3倍,图片内存占用就会变为原来的9倍!

国内也有很多人说可以用一套图片来做,不用出多套图,借此来达到app瘦身和给设计减负的目的。谷歌官方是建议为不同分辨率出不同的图片,为此国内也有不少文章讨论过这件事情,这篇总结的不错推荐一读。


每次说到这个话题的时候总有很多人有不同的看法,况且很多人还不知道.9图也是需要切多份的,所以这里我还是先分析一下大厂的放图策略,最后咱们再讨论下较优的方案。


分析过程见:《淘宝、微博、微信的 Android 图片放置策略》


厂商mdpihdpixhdpixxhdpi
淘宝小icon表情国家icon弃用
微博小icon背景图&表情背景图背景图
微信弃用表情大图弃用


通过分析得出,传统的出多个分辨率图片的做法在大厂中已经发生了改变,阿里系、腾讯系的产品都采用了一套图走天下的路子。这样的做法还是有利有弊的,权衡之下我给出如下建议:


  • 聊天表情就出一套图,放在hdpi中

  • 纯色小icon用svg做

  • 背景等大图,出一套放在xhdpi中

  • logo等权重较大的图片可针对hdpi,xhdpi做两套图

  • 如果某些图在真机中确实展示异常,那就用多套图

  • 如果遇到奇葩机型,可针对性的补图


成年人不看对错,只看利弊,所以还请大家权衡一二。


丢弃特定资源


如果我们希望保留或丢弃特定的资源,需要在项目中创建一个res/raw/keep.xml文件,这里可以使用tools:keep和 tools:discard两个属性来保留或丢弃资源。


两个属性都可以使用逗号,分隔符声明资源名称列表。也可以使用*作为匹配符。




keep:



discard:



开启严格模式


开启严格模式后,可能对于编译会产生一些问题,警告也会增多,所以需谨慎开启此功能。

res/raw/keep.xml


shrinkMode默认的值为safe,你将它指定为strict便开启了严格模式。严格模式下,apk会保留确定引用到的资源。

Gradle Console中的日志中也会有移除APK资源的信息:


apk构建完成后会Gradle会在/build/outputs/mapping/release/下生成resource.txt,这个文件包括详细信息,如资源参考其他资源和使用或删除资源的详细信息等。


例如:找出为什么@drawable/ic_plus_anim_016,仍然包含在你的APK中,在resource.txt 搜索该文件名,你可能会发现它是被另一个资源引用,如下:


要 为什么add_schedule_fab_icon_anim 仍然在使用,搜索我们可以知道应该有代码引用着add_schedule_fab_icon_anim


此部分的内容大量参考自《Shrink Your Code and Resources》一文,请移步官网去详细了解。


移除第三方库中的配置文件


有时候引用的三方库会带有一些配置文件xxxx.properties,或者license信息,打包的时候想去掉这些信息,就可以这样做




优化图片


对于图片的优化应该是放在优化res一节中进行讲解的,但是因为图片这块比重太大了,所以我让其独立成为一节。


选择合适的图片格式


想要做好图片的优化工作首先要知道应该选择什么样的图片格式,对于这点我推荐一个视频,方便大家进行深入的了解。



这是谷歌给出的建议是:VD->WebP->Png->JPG


  1. 如果是纯色的icon,那么用svg

  2. 如果是两种以上颜色的icon,用webp

  3. 如果webp无法达到效果,选择png

  4. 如果图片没有alpha通道,可以考虑jpg


使用VectorDrawable


VD即VectorDrawable,是android上的svg实现类。在经历了长达半年的缓慢兼容之路后,现在终于被support库兼容了,官方文档中给出了这样一个例子:




配置好后,我们就可以利用强大的svg来替换纯色icon了。


因为svg矢量图的优势,终于可以通过一套图适配多个机型了。svg的好处有很多,缺点也不少,关于svg的优缺点和实践方案,建议移步:http://todo(未写完)


使用WebP


webp是一种新的图片格式。从Android4.0 开始原生支持,但是不支持包含透明度,直到4.2.1 才支持显示含透明度的webp,使用的时候要特别注意。

Lossy WebP support (suitable for replacing most JPEGs and some PNGs) is guaranteed on Android 4.0 devices. Newer WebP features (transparency, lossless, suitable for PNGs) are supported since Android 4.2.1

我们可以通过智图或者isparta将其它格式的图片转换成webP格式。

关于webP的优缺点和实践方案,建议移步到《WebP的问题和解决方案》继续阅读。


复用图片


利用现有的图片进行复用是一个相当有用的方案,关于复用的原则建议和设计进行讨论,当设计师认为二者均为同一图形的时候才可被认为可复用。


复用相同的icon


我们通过svg可以让一张图片适用于不同大小的容器中,以达到复用的目的。最常见的例子就是“叉”,除非你的x是有多种颜色的,那么这种表示关闭的icon可以复用到很多地方。

可以复用的x

利用svg和tint

上图中我通过组合的方式将长得一样的icon(facebook、renren等)复用到了不同的界面中,不仅实现了效果,可维护性也不错。


使用Tint


着色器(tint)是一个强大的工具,我将其和shape、svg等结合后产生了化学反应。


TintMode共有6种,分别是:add,multiply,screen,srcatop,srcin(默认),src_over。

一般用默认的模式就可以搞定大多数需求了,使用到的控件主要是TextView和ImageButton。ImageButton官方已经给出了支持方案,TextView因为有四个Drawable,官方的tint属性在低版本又不可用,所以我让SelectorTextView支持了一下。如果你想要了解具体的兼容方法,可以参考代码《Drawable 着色的后向兼容方案》


ImageButton


SelectorTextView



因为我用了SelectorTextView和SelectorImageButton,所以我对于背景的tint没有什么需求,也就没做兼容性测试,有兴趣的同学可以尝试一下。

如果你决定要采用tint,一定要通过云测等手段做下兼容性测试,下图是我对于上述属性的测试结果:


完美兼容


复用按压效果


一个应用中的list页面都应该做一定程度的统一,对于有限长度的list,我们可能偏向于用ScrollView做,对于无限长的list用RecyclerView做,但对于它们的按压效果我强烈建议采用同一个样式。

以微信为例,它的所有列表都是白色的item,我的优化思路如下:


  1. 列表由LinearLayout、RecyclerView组成

  2. 分割线用统一的shape进行绘制,不用切图

  3. 整个列表背景设置为白色

  4. item的背景是一个selector文件,正常时颜色是透明,按下后出现灰色


通过旋转来复用


如果一个icon可以通过另一个icon的旋转变换来得到,那么我们就可以通过如下方法来实现:


旋转图片

这种方法虽好,但是不要滥用。需要读代码的人具备这种知识,否则会出现不好维护的情况。而且在设计师真的是认为两个图有如此的关系的时候才可这样实现,万不可耍小聪明。


压缩图片


图片的压缩策略是:


  1. 优先压大图,后小图

  2. 不压.9图(svg在侠义上不算图)

  3. 对于开屏大图片的压缩需注意力度,要和设计确认后再做

  4. 对于体积特别大(超过50k)的图片资源可以考虑有损压缩


关于如何量化两张图片在视觉上的差别,Google 提供了一个叫butteraugli的工具,有兴趣的同学可以尝试一下。

关于更加详细的内容可以参考:《smallerapk-part-6-image-optimization》和《QQ音乐团队的PNG图片压缩对比分析》


ImageOptim


mac上超好用的图片压缩工具是ImageOptim,它集成了很多好用图片压缩库,很多blog中的图片也是用它来压缩的。

值得一提的是,借助Zopfli,它可以在不改变png图像质量的情况下使图片大小明显变小。


pngquant


pngquant也是一款著名的压缩工具,对于png的疗效还不错。它不一定适合app中那种背景透明的小icon,所以对比起tinypng来说,优势不明显。



数据来自:http://www.jianshu.com/p/a721fbaa62ab


tinypng


tinypng是一款相当著名的商用压缩工具,tinypng提供了开放接口供开发者开发属于自己的压缩工具(付费服务)。tinypng对于免费用户也算友好,每月可以免费压缩几百张图片。


我通过TinyPic来使用tinypng,更加简单方便。我一般是发版本前才做一次图片压缩,每次debug的时候是直接跳过这个task的,完全不影响日常的debug。




有人说tinypng的缺点是在压缩某些带有过渡效果(带alpha值)的图片时,图片可能会失真,这时你可以将png图片转换为webP格式的图片来解决此问题。


注意事项


aapt可以在构建过程期间优化放置在res/drawable/中的图像资源,以及无损压缩。 aapt可将不需要多于256种颜色的真彩色png转换为带有调色板的8位png,借此来得到质量相同但占用内存较小的图像。


请记住,aapt有以下限制:


  • aapt工具不会压缩资源/文件夹中包含的png文件

  • 图像文件需要使用256个或更少的颜色的aapt工具来优化它们

  • aapt工具可能会使已压缩的png文件膨胀。原因请看:Smaller PNGs, and Android’s AAPT tool

如果你自己做了图片压缩,那么请使用cruncherEnabled来禁用aapt的压缩功能:




优化dex


dex本身的体积还是很可观的,虽说代码这东西不占用多少存储空间,但是微信这样的大厂的dex已经达到了20多M。我大概估计了一下,如果你没有达到方法数上限,那么你的dex的大小大约是10M,可没有用multiDex的又有几家呢?


记录方法数和代码行数


dexcout


要优化这个部分,你首先要对公司的、android库的、第三方库的代码进行深入的了解。我用了dexcount来记录项目的方法数:




通过分析后你可以得出很多有用的结论,比如某个第三方库是否已经不用了、自己项目的哪个包的方法数最多、目前代码情况是否合理等等。


statistic


我是通过Statistic这个as插件来评估项目中开发人员写的代码量的,它生成的报表也不错:


预览

java代码

现在我可以知道:


  • 哪些类空行数太多,是不是没有按照代码规范来

  • 哪些类的代码量很少,是否有存在的必要

  • 哪些类行数过多,是否没有遵守单一职责原则,是否可以进行进一步的拆分


apk method


你还可以用apk-method-count这个工具来查看项目中各个包中的方法数,它会生成树形结构的文档,十分直观。

利用Lint分析无用代码


如果你想删掉没有用到的代码,可以借助as中的Inspect Code对工程做静态代码检查。


search action




Lint是一个相当强大的工具,它能做的事情自然不限于检查无用资源和代码,它还能检测丢失的属性、写错的单位(dp/sp)、放错目录的图片、会引起内存溢出的代码等等。从eclipse时代发展到现在,lint真的是越来越方便了!


Lint的强大也会带来相应的缺点,缺点就是生成的信息量过多,不适合快速定位无用的代码。


我推荐的流程是到下图中的类目中直接看无用的代码和方法。

注意:
这种删除无用代码的工作需要反复多次的进行(比如一月一次)。当你删除了无用代码后,这些代码中用到的资源也会被标记为无用,这时就可以通过上文提到的Remove Unused Resources来删除了。


通过proguard来删除无用代码


手动删除无用代码的流程太繁琐了,如果是一两次倒还会带来删除代码的爽快感,但如果是专人机械性的持续工作,那个人肯定是要疯的。为了保证每次打包后的apk都包含尽可能少的无用代码,我们利用一下proguard这个强大的工具。




虽然这种方式成果显著,但也需要配合正确的proguard配置才能起作用,推荐看下《读懂Android中的代码混淆》一文。


这种利用混淆来删除代码的方式是一种保险措施,真正治本的方法还是在开发过程中随手删除无用的代码,毕竟开发者才是最清楚一段代码该不该被删的。我之前就是随手清理了下没用的代码,然后就莫名其妙的不用使用mulitdex了。


剔除测试代码


我们在测试的时候可能会随便写点测试方法,比如main方法之类的,并且还会引入一些测试库。对于测试环境的代码gradle提供了很方便的androidTest和test目录来隔离生产环境。


对于测试时用到的大量库,可以进行test依赖,这样就可以保证测试代码不会污染线上代码,也可以防止把测试工具、代码等发布到线上的错误(微博就出过这样的错误)。



PS:在layout中利用tools也是为了达到上述目的。


区分debug/rtm/release模式


debug模式是开发者的调试模式,这个模式下log全开,并且会有一些帮助调试的工具(比如:leakcanarystetho),我们可以通过debugCompilereleaseCompile来做不同的依赖,有时候也会需要no-op(关于no-op的内容可以参考下开发第三方库最佳实践)。




debug和release是android本身自带的两种生产环境,在实际中我们可能需要有多个环境,比如提测环境、预发环境等,我以rtm(Release to Manufacturing 或者 Release to Marketing的简称)环境做例子。


首先在目录下创建rtm文件:

image_1arpi1tfq1dd5pga35j10ntuhjjc.png-12.3kB

复刻release的配置:




配置rtm依赖:



rtm环境自然也有动态替换application文件的能力,我为了方便非开发者区分app类别,我做了启动icon的替换。


现在我可以将环境真正需要的代码打包,不需要的代码全部剔除,以达到瘦身的目的。


使用拆分后的support库


谷歌最近有意将support-v4库进行拆分,可无奈v4被引用的地方太多了,但这不失为一个好的开始。目前来看使用拆分后的support库是没有什么优点的,所以我也不建议现在就开始动手,当谷歌和第三方库作者都开始真的往这方面想的时候,你再开始吧。


减少方法数,不用mulitdex


mulitdex会进行分包,分包的结果自然比原始的包要大一些些,能不用mulitdex则不用。但如果方法数超了,除了插件化和RN动态发包等奇淫巧技外我也没什么好办法了。


使用更小库或合并现有库


同一功能就用一个库,禁止一个app中有多个网络库、多个图片库的情况出现。如果一个库很大,并且申请了各种权限,那么就去考虑换掉他。


话人人都会说,但如果一个项目是由多个项目成员合作完成的,是很难避免重复引用库的问题的。同一个功能用不同的库,或者一个库用不同版本的现象比比皆是,这也是很难去解决的。我的解决方案是部门之间多沟通,尽量做base层,base层由少数人进行维护,正如微信在so库方面的做法:


  1. C 运行时库统一使用stlport_shared
    之前微信中的C 运行库大多使用静态编译方式,使用stlport_shared方式可减小APK包大小,相当于把大家公有的代码提取出来放一份,减少冗余。同时也会节省一点内存,加载so的时候动态库只会加载一次,静态库则随着so的加载被加载多份内存映像。

  2. 把公用的C 模块抽成功能库
    其实与上面的思路是一致的,主要为了减少冗余模块。大家都用到的一些基础功能,应该抽成基础模块。

四、总结

app的瘦身是一个长期并且艰巨的工作,如果是小公司建议一两个月做一次。大公司的话一般都会对app的大小进行持续的统计和追踪,瘦身工作会有专人负责。总之,希望大家在阅读完本文后可以着手对项目进行优化工作,带来真正的收益。


weibo:@天之界线2010
developer-kale@foxmail.com



关注公众号得到以上效果源码




觉得文章对你有帮助的童鞋可以关注下方的公众号 GitHub小伙伴 ,同时动动手指转发给其他需要的童鞋们吧。




    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多