App瘦身最佳实践(上)

本文介绍了如何对Android App进行瘦身,包括分析app组成结构、优化assets、lib、resources.arsc和META-INF,提供了删除无用字体、动态下载资源、按CPU引入so等实用技巧,并强调了优化对项目可维护性的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

业务方和开发都希望app尽量的小,本文会给出多个实用性的技巧来帮助开发者进行app的瘦身工作。瘦身和减负虽好,但需要注意瘦身对于项目可维护性的影响,建议根据自身的项目进行技巧的选取。


一、背景

目前app的大小越来越大,用户对于过大的app接受度不高,所以除了插件化和RN的方案外,我们只能老老实实的进行app的瘦身工作。

二、需求

  1. 我要利用混淆来让我的代码尽可能少

  2. 最好能用最少的切图完成功能

  3. layout文件不要太多,太多了乱

  4. 能动态下载的就做动态

  5. 我希望能用大小最小的图片

  6. 如果能用svg,我就用svg

  7. 对于无用的资源,我要as能自动删除掉

  8. 中国文字博大精深,而我只要我需要的字的字体

  9. 最好能根据下载用户手机的cpu和分辨率来引入不同的资源

三、实现

分析app组成结构


做瘦身之前一定要了解自己app的组成结构,要有针对性的进行优化,并且要逐步记录比对,这样才能更好的完成此项工作。关于apk的大小,我推荐google的这个视频。目前as的2.2预览版中已经有了apk分析器,功能相当强大,此外你还可以利用nimbledroid来分析apk。

nimbledroid是一个强大的工具,推荐一试

我们都知道apk是由:

  • asserts

  • lib

  • res

  • dex

  • META-INF

  • androidManifest

这几个部分构成的。

下面我会利用as的分析工具,以微信、微博、淘宝为例进行讲述。

概览

分析完成后你还可以看到具体类目占的百分比,清晰明了。旁边的“对比”按钮提供了diff的功能,让你可以方便的进行apk优化前后的对比,简直利器。

diff

assets


assets目录可以存放一些配置文件或资源文件,比如webview的本地html,react native的jsbundle等,微信的整个assets占用了13.4M。如果你的应用对本地资源要求很少的话,这个文件应该不会太大。


lib

lib目录下会有各种so文件,分析器会检查出项目自己的so和各种库的so。微博和微信一样只支持了arm一个平台,淘宝支持了arm和x86两个平台。



resources.arsc


这个文件是编译后的二进制资源文件,里面是id-name-value的一个map。因为微信做了资源的混淆,所以这里可以看到资源名称都是不可读的。

索性放个微博的图,易于大家理解:

META-INF


META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全性,帮助用户避免安装来历不明的盗版apk。

res


res目录存放的是资源文件,包括图片、字符串。raw文件夹下面是音频文件,各种xml文件等等。因为微信做了资源混淆,图片名字都不可读了。

res

微博就没有做资源混淆,所以可读性较好:

dex


dex文件是java代码打包后的字节码,一个dex文件最多只支持65535个方法,这也是为什么微信有了三个dex文件的原因。

因为dex分包是不均匀的,你可以理解为装箱,一个箱子的大小是固定的,但你代码的量是不确定的,微信把前两个箱子装满了,最后还剩了2m多的代码,这些代码也占用了一个箱子,最终产生了上图不均匀的情况。

现在,我们已经知道了apk中各个文件的大小和它们占的比例,下面就可以开始针对性的进行优化了。


优化assets


assets中会存放资源文件,这个目录中不同厂的app存放的内容各有不同,所以优化也比较难。自从引入RN以来,这个目录下还会有jsbundle的信息(可参考全民k歌)。如果你有地址选择的功能,这里还会存放地址的映射文件。
对于这块的资源,as是不会进行主动的删减的,所以一切都是需要靠开发者进行手动管理的。

全民k歌中的bundle文件

删除无用字体


中文字体是相当大的,我一直不建议将字体文件随意丢弃到assets中。有时候一个小功能急着上,开发者为了追求速度,可以先放在这里图省事。但一定要知道这个隐患,并且一定要多和产品核对功能的必要性。对于有些只会用在logo中的字体,我推荐将字体文件进行删减处理。

FontZip是一个字体提取工具,readme中写到:

经过测试,已经把项目5MB的艺术字体,按需求提取后,占用只有20KB,并且可正常使用。

gif2 (1).gif-204.5kB

减少icon-font的使用


icon-font和svg都能完成一些icon的展示,但因为icon-font在assets中难以管理,并且功能和svg有所重叠,所以我建议减少icon-font的使用,利用svg进行代替,毕竟一个很小的icon-font也比svg大。这里给出一个提供各种格式icon的网站,方便大家进行测试:https://icomoon.io/app/

  • svg:549字节

  • png:375字节(单一分辨率的一张图)

  • ion-font:1.1kb


动态下载资源


字体、js代码这样的资源能动态下载的就做动态下载,虽然这样会增加出错的可能性,复杂度也会提升,但对于app的瘦身和用户来说是有长远的好处的。
如果你用了rn,你可以在app运行时动态去拉取最新的代码,将图片和js代码一并下载后解压使用。也可以把rn模块化,主线的rn代码随着app发布,入口较深的次要界面可以在app启动后通过断点下载。


压缩资源文件


有些资源文件是必须要随着app一并发布的。对于这样的文件,可以采用压缩存储的方式,在需要资源的时候将其解压使用,下面就是解压zip文件的代码示例:



全民k歌中的assets目录下我就发现了大量的zip文件:

android上也有一个7z库帮助我们方便的使用7z,这个库我目前没用到,有需求的同学可以尝试一下。


优化lib


配置abiFilters


一个硬件设备对应一个架构(mips、arm或者x86),只保留与设备架构相关的库文件夹(主流的架构都是arm的,mips属于小众)可以大大降低lib文件夹的大小。配置方式也十分简单,直接配置abiFilters即可:



armeabi就不用说了,这个是必须包含的,v7是一个图形加强版本(如果用到模糊算法,则不要删除),x86是英特尔平台的支持库。


官方例子


按 ABI 拆分



  • enable: 启用ABI拆分机制

  • exclude: 默认情况下所有ABI都包括在内,允许移除一些ABI

  • include:指明要包含哪些ABI

  • reset():重置ABI列表为只包含一个空字符串(这也是允许的,在与include一起使用来可以表示要使用哪一个ABI)

  • universalApk:指示是否打包一个通用版本(包含所有的ABI)。默认值为 false。


根据手机的cpu来引入so


我们在舍弃so之前一定要进行用户cpu型号的统计,这样你才能放心大胆地进行操作。
我先是花了几个版本的时间统计了用户的cpu型号,然后排除了没有或少量用户才会用到的so,以达到瘦身的目的。



注意:

  1. 如果你和我一样用到了renderscript,那么你必须包含v7,否则会出现模糊异常的问题。

  2. 如果你用了RN,那么对于x86需要谨慎的保留,否则可能会出现用户找不到so而崩溃的情况。毕竟rn是一个全局的东西,稍有不慎就可能会出现开机崩的情况。

  3. so这个东西还是比较危险的,我们虽然可以通过统计cpu型号来降低风险,但我还是推荐发布app前走一遍大量机型的云测,通过云测平台把风险进一步降低。

  4. 小厂的项目可能会舍弃一些so,但随着公司规模的增大,你未来仍旧要重复考虑这个问题。所以我推荐在崩溃系统中上传用户cpu型号的信息,这样我们就可以在第一时间知道因找不到so引起的崩溃量,至于是否需要增加so就看问题的严重程度了。


避免复制so


so有个常年大坑:在Android 6.0之前,so文件会压缩到apk中,系统在安装应用的时候,会把so文件解压到data分区。这样同一个so文件会有两份存在,一个在apk里,一个在data中。这也导致多占用了一倍的空间,而且会出现各种诡异的错误。这个策略虽然和apk的瘦身无关,但它和app安装在用户手机中的大小有关,因此我们也是需要多多留意的。

Starting from Android Studio 2.2 Preview 2 and newest build tools, the build process will automatically store native libraries uncompressed and page aligned in the APK

在6.0 中,可以通过如下的方式进行申明:



如果想了解更多信息或者想知道这种配置的限制,可以浏览下SmallerAPK(8)


优化resources.arsc


resources.arsc中存放了一个对应关系:

id name default v11
0x7f090002 PopupAnimation @ref/0x7f040042, @ref/0x7f040041

我们在程序运行的时候肯定要经常用到id,因此它在安装之后仍需要被频繁的读取。如果将这个文件进行了压缩,在每次读取前系统都必须进行解压的工作。这就会有一些性能和内存的开销,综合考虑下来,压缩这个文件是得不偿失的。


删除无用的资源映射


resources.arsc的正确瘦身方式是删除不必要的string entry,你可以借助 android-arscblamer来检查出可以优化的部分,比如一些空的引用。

ArscBlamer


进行资源名称混淆


微信团队开源了一个资源混淆工具,AndResGuard。它将资源的名称进行了混淆,所以可以用它对resources.arsc进行优化,只是具体优化效果与编码方式、id数量、平均减少命名长度有关。


表1:

id name default v11
0x7f090001 Android @ref/0x7f040042, @ref/0x7f040041
0x7f090002 ios @ref/0x7f040042, @ref/0x7f040041
0x7f090003 Windows Phone @ref/0x7f040042, @ref/0x7f040041


表2:

id name default v11
0x7f090001 a @ref/0x7f040042, @ref/0x7f040041
0x7f090002 b @ref/0x7f040042, @ref/0x7f040041
0x7f090003 c @ref/0x7f040042, @ref/0x7f040041


我们一眼就可以知道表2肯定比表1存储的字符要小,所以整个文件的大小肯定也要小一些。

详细信息请参考:smallerapk-part-3-removing-unused-resources


关于AndResGuard


这个压缩工具其实就是一个task,使用也十分简单,具体的用法请参考中文文档

原理介绍:安装包立减1M--微信Android资源混淆打包工具 



使用这个工具的时候需要注意一些东西:像友盟这种喜欢用反射获取资源的SDK就是一个坑(友盟的SDK就是坑王!)对于app启动图标这样的icon可以不做混淆,推荐将其放入白名单中。

优化META-INF

META-INF文件夹中有三个文件,分别是MANIFEST.MF、CERT.SF、CERT.RSA。下面我将会列出简要的分析,如果你希望更详尽的了解原理,可以查看《Android APK 签名文件MANIFEST.MF、CERT.SF、CERT.RSA分析》


MANIFEST.MF



每一个资源文件(res开头)下面都有一个SHA1-Digest的值。这个值为该文件SHA-1值进行base64编码后的结果。
如果要探究原理,可以看下SignApk.java。这个类中的main方法:




上述代码说明了SHA1-Digest-Manifest是MANIFEST.MF文件的SHA1并base64编码的结果。


CERT.SF



这里有一项SHA1-Digest-Manifest的值,这个值就是MANIFEST.MF文件的SHA-1并base64编码后的值。后面几项的值是对MANIFEST.MF文件中的每项再次SHA1并base64编码后的值。所以你会看到在manifest.mf中的资源名称在这里也出现了,比如abc_btn_check_material这个系统资源文件就出现了两次。


MANIFEST.MF:



CERT.SF


  • 前者:4XHnecusACTIgtImUjC7bQ9HNM8=

  • 后者:YFDDnTUd6St4932sE/Xk6H0HMoc=

如果你把前一个文件打开在后面加上\n\r,然后进行编码,你就会得到CERT.SF中的值。



CERT.RSA


CERT.RSA包含了公钥、所采用的加密算法等信息。它对前一步生成的MANIFEST.MF使用了SHA1-RSA算法,用开发者的私钥进行签名,在安装时使用公钥解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被修改。

这点和app瘦身就完全无关了,这块我平时也没有仔细研究过,就不误人子弟了。具体的签名过程可以参考:http://blog.csdn.net/asmcvc/article/details/9312123


优化建议


通过分析得出,除了CERT.RSA没有压缩机会外,其余的两个文件都可以通过混淆资源名称的方式进行压缩。

优化res


资源文件的优化一直是我们的重头戏。如果要和它进行对比,上文的META-INF文件的优化简直可以忽略不计。res的优化分为两块:一个是文本资源(shape、layout等)优化和图片资源优化。本节仅探讨除图片资源优化外的内容,关于图片的内容下面会另起一节。

说明:
上图中有-v4,-v21这样的文件有些是app开发者自己写的,但大多都是系统在打包的时候自动生成的,所以你只需要考虑自己项目中的drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi即可。


通过as删除无用资源


在as的任何文件中右击,选择清除无用资源即可删除没有用到的资源文件。

不要勾选清除id!如果清除了id,会影响databinding的使用(id绝对占不了多少空间)

Tips:
做此操作之前,请务必产生一次commit,操作完成后一定要通过git看下diff。这样既方便查看被删除的文件,又可以利用git进行误删恢复。


打包时剔除无用资源


shrinkResources顾名思义————收缩资源。将它设置为true后,每次打包的时候就会自动排除无用的资源(不仅仅是图片)。有了它的帮忙,即使你忘记手动删除无用的资源文件也没事。




删除无用的语言


大部分应用其实并不需要支持几十种语言的,微信也做了根据地区选择性下载语言包的功能。作为国内应用,我们可以只支持中文。推荐在项目的build.gradle中进行如下配置:




这样在打包的时候就会排除私有项目、android系统库和第三方库中非中文的资源文件了,效果还是比较显著的。


控制raw中资源的大小


  • assets目录允许下面有多级子目录,而raw下不允许存在目录结构

  • assets中的文件不会产生R文件映射,但raw会

  • 如果你app最低支持的版本不是2.3的话,assets和raw应该都不会对资源文件的大小进行限制

  • raw文件会生成R文件映射,可以被as的lint分析,而assets则不能

  • raw缺少子目录的缺点让其无法成为存放大量文件的目录


一般raw文件下会放音频文件。如果raw文件夹下有音频文件,尽量不要使用无损(如:wav)的音频格式,可以考虑同等质量但文件更小的音频格式。


ogg是一种较适合做音效的音频格式。当年我初中做游戏的时候,我全都是用的mp3和png,最终游戏达到了2G。在换为ogg和jpg后,游戏缩小到了1G以内(因为游戏中音频和大图较多,所以效果比较夸张)。

移动端的音频主要是音效和短小的音频,所以淘宝大量选择了ogg格式,微博的选择格式比较多,有wav、mp3、ogg,我更加推荐淘宝的做法。当然,你仍旧不要忘记opus格式,opus也是一种有损压缩格式,如果感兴趣的话也可以尝试一下。

统一应用风格,减少shape文件


一个应用的界面风格是必须要统一的,这个越早做越好,最基本的就是统一颜色和按钮的按压效果。无UI设计和扁平化风格流行后,倒是给应用瘦身带来了极大的的福利。界面变得越朴实,我们可以用shape画的东西就越多。




当你的app统一过每种颜色对应的按下颜色后,接下来就需要统一按钮的形状、按钮的圆角角度、有无阴影的样子、阴影投射角度,阴影范围等等,最后还要考虑是否支持水波纹效果。


我简单将按钮分为下列元素:


元素 属性01 属性02 属性03 属性04
形状 正方形 三角形 圆角矩形 圆形
颜色 绿
有无阴影    
阴影大小 3dp 5dp    
阴影角度 90° 120° 180°  
水波纹效果    

各个元素组合后会产生大量的样式,shape和layer-list当然可以实现各种组合,但这样的话光按钮的背景文件就有n个,很不好维护。


一般为了开发方便,都会把需要用到的各种selector图片事先定义好,做业务的时候只需要去调用就行。但这大量的selector文件对于业务开发者来说也是有记忆难度的,所以我推荐使用SelectorInjection这个库,它可以将上面的每个元素进行各种组合,用最少的资源文件来实现大量的按压效果。


用库虽然好,但库也会带来学习成本,所以引入者可以将上述的组合定义为按钮的一个个的style。因为style本身是支持继承的,对于这样的组合形态来说,继承真是是一大利器。当你的style有良好的命名后,调用者只需要知道引入什么style就行,至于你用了什么属性别人才不希望管呢。


如果业务开发中有一些特别特殊的按压状态,没有任何复用的价值,那你就可以利用库提供的丰富属性在layout文件中进行实现,再也不用手忙脚乱的到处定义selector文件了。

我将不能继承和不灵活的shape变成了一个个单一的属性,通过库将多个属性进行组合,接着利用支持继承的style来将多个属性固定成一个配置文件,最后对外形成强制的规范性约束,至此便完成了减少selector文件的工作。


使用toolbar,减少menu文件


menu文件是actionBar时代的产物,as虽然对于menu的支持做的还不错,但我也很难爱上它。


menu的设计初衷是解耦和抽象,但因为过度的解耦和定制,让开发变得很不方便,很多项目已经不再使用menu.xml作为actionbar的菜单了。


就目前的形势来看,toolbar是android未来的方向。我虽然作为一个对actionbar和actionbar的兼容处理相当了解的人,但我还是不得不承认actionbar的时代过去了。如果你不信,我可以告诉你淘宝的menu文件就3个,微博的menu文件就9个,如果你还是苦苦依恋着actionbar的配置模式,我推荐一个库AppBar,它可以让你在用灵活的toolbar的同时也享受到配置menu的便利性。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值