Apk包体减压

前言:

Android 开发中,一个成熟的App没有了前期快速迭代的试错,并且业务需求逐渐稳定,功能稳定,必然会面临着包体增大的问题。导致的这样的问题即与前期快速迭代无暇梳理app有关,也与用户增多功能日渐增加有关,而不断增大的包体,每次更新耗费了用户更多的流量,影响到用户的用户体验,就会导致下载量的下降,成为公司的损失,在用户获取成本高的互联网行业,这种影响更为沉重;所以我个人希望减小App包体大小应该是我们App开发者职业目标;

Android Apk包的组成:

一个Apk包的简而言之有8部分组成:

classes.dex

编写的所有的Java代码(包括各种引入的sdk代码)最终转化成在Android虚拟机上运行需要的字节码(和java的字节码有一定的区别)

res文件夹

存放所有资源的文件夹(除了里面raw文件夹的文件不会被编译,其他都会被编译)

resources.arsc

编译后的二进制资源文件

assets文件夹

用于保存需要保持原始文件的资源文件(这部分资源不会被编译)

lib文件夹

用于存放应用需要的native库文件

AndroidManifest.xml

程序全局配置文件

META-INF文件夹

存放几个签名校验相关的文件,用于保证APK的完整性和安全性

其他

其他一些配置生成的文件


分析包体成分占比工具

优化包体之前,我必须要了解到自己项目包体的中各个成分的大小,因为每个部分的优化方案各不相同,只有充分了解包体的情况才能制定有效的压缩方案,才能做到有的放矢。

通常来说分析包体有三种方式:

1、直接使用反编译或压缩工具解压正式APK包,使用操作系统的文件命令排序显示各个文件夹的大小

2、如果android studio是2.1.2之后地版本,可以直接将Apk文件拖进studio中,studio会自动进行分析,列出排序表

3、使用NimbleDroid(点击可跳转)在线工具,此工具是美国哥伦比亚一名教授带领团队开发,功能更全面,更强大。它除了了可以分析包体成分占比,还可以帮我分析第三放SDK的大小,以及方法数的多少,可以帮助我慎重选择第三方库(包体的大小有很大程度上就是选择过多第三方库导致的)

下面是我接手过的一个App没有优化之前的包体结构以及成分占比图:

压缩包体的方法

一、压缩资源文件

1、压缩图片

  压缩图片,一般是指对图片进行质量、尺寸压缩和对图片格式进行转换。前者通过在线图片压缩工具或者Linux 的ImgPack批处理脚本完成(现在比较好用的在线图片的工具TinyPing),后者需要根据Jpg、png、gif等图片格式的优缺点进行相应的转换,已达在保证显示效果的情况下减少文件大小,如果对jpg等格式不理解,可以让UI将一张图同事生成三种格式,对比大小和显示效果就可以了。

2、压缩音视频

多媒体文件通常有两种压缩方式,即有损压缩与无损压缩,对于有损压缩网上有一些误解,以为有损压缩的音质画质一定会很差,其实有损压缩只是采用压缩算法是忽略了原始数据中的一些数据,但是都是人的生理极限为前提忽略的。比如视网膜屏做到了每英寸326的像素停止更精细的追求,高清电影的帧率达到60fps就没在进步,这些不是因为技术达不到,而是人眼对分辨像素的极限但是每英寸326ps,对动画频率的识别的极限是60fps,没有必要再去要第327个像素和第61帧。图片压缩就是尽量压缩掉人眼不可识别的冗余数据,既保证显示有照顾到图片大小。

无损格式:WAV,PCM,ALS,ALAC,TAK,FLAC,APE,WavPack ( WV )

有损格式:MP3,AAC,WMA,Ogg Vorbis

实际开发中需要使用音频文件尽量采用 MP3、Ogg 这种有损格式,尽量不要用 WAV、PCM 这种无损音频。

3、在工程中保存一份图片资源

开发目录下会有个drawable或者mipmap目录用于适配不同dpi的屏幕,目前市面上绝大部分机型都处于xxhdpi的适配范围,所以可以考虑只保留xxhdpi目录下一份图片资源,具体保留哪个目录下的资源和保留几份资源还得依照应用自身的实际机型分布决定

此种做法的理论依据可以浏览我收藏这两篇博客:

Android drawable微技巧,你所不知道的drawable的那些细节

那些值得你去细细研究的Drawable适配

4、使用 Drawable XML、Color代替PNG图片

一些情况下,我们可以考虑使用 Drawable XML 来代替 PNG,如:渐变的背景图,用几行 XML 就可以描绘出来,何必使用几十到上百K的 PNG 文件。

用 Color 代替 PNG,如:纯色的背景。

从性能上看,比起使用图片资源需要先将其生成 Bitmap 再传到底层交由 GPU 渲染,用Drawable XML和Color则更加高效,它是直接将 Shape 指令传到底层由 GPU 进行渲染,CPU和内存的占用会更少。

5、不需要透明度时使用JPG代替PNG(这点是对1点的一个补充)

当不需要透明度的图片时,可以考虑用JPG代替PNG,由于JPG没有Alpha 通道,所以文件更小。

6、考虑使用SVG格式图片去替换一些icon

SVG的全称是Scalable Vector Graphics,叫可缩放矢量图形。它和位图(Bitmap)相对,SVG不会像位图一样因为缩放而让图片质量下降。它的优点在于体积小,不用考虑屏幕适配问题。Android 5.0中引入了 VectorDrawable 来支持矢量图(SVG),同时还引入了AnimatedVectorDrawable 来支持矢量图动画。但是5.0以前的版本还是需要引入支持库,也一定程度上增加了包体。所以说可以考虑使用。

二、移除无用资源

这一点主要是使用lint检查并清除冗余资源(Android Studio 选中项目右键 => Analyze => Run Inspection by Name => 输入 unused resuroces)。

如果你的资源是通过资源名称使用Resources的getIdentifier(String name, String defType, String defPackage)方法去获取到资源的id来使用资源(这种方式是通过反射的方法根据资源名称去获取资源的id),而不是直接通过R文件自动生成的id来使用资源的,lint会检测判定你这个文件并没有被使用,而作为未使用的文件列出来。这时候就不能使用一键删除的功能,需要确认后自己手动删除。例如我要使用doodle.png这个资源,一般情况下我们是通过R.drawable.doodle去使用的,如下代码

Resources res = context.getResources();Drawable doodleDrawable = res.getDrawable(R.drawable.doodle);

但是,有些时候我们需要在代码中动态地根据资源名称去使用资源,这时候就要用到getIdentifier()去获取到资源的id,然后再使用这个id去获取资源,示例代码如下:

Resources res = context.getResources();int doodleDrawableId = res.getIdentifier("doodle","drawable", context.getPackageName());Drawable doodleDrawable = res.getDrawable(doodleDrawableId);

这种情况就不能使用一键删除的功能,需要确认后自己手动删除。

三、使用APK Splits对APK进行拆分

APK Splits能让应用程序更有效地构建一些形式的多个apk。使用Splits构建出来的APK是只含有不同的单套资源但功能用途一样的APK。

APK Splits支持两个维度:

屏幕密度 ABI

简而言之,APK Splits就是我们要根据用户的手机去提供基于不同屏幕分辨率(xxhdpi,mhdpi等),so库版本的单个APK,并且应用市场支持发布这种多个APK的功能(即要求应用市场能根据用户的手机的屏幕分辨率,CPU的架构而为用户选择对应的版本的APK提供下载)。这做法目前只适用于海外市场,因为目前只有GooglePlay支持这种Multiple APK Support发布功能。不过我们可以个根据它的知识做一些我自己想做的事:

按屏幕密度拆分,配置代码如下:

android {

splits {  

  density { 

     enable true   

    exclude "ldpi", "tvdpi", "xxxhdpi"

  compatibleScreens 'small' , 'normal' , 'large' , 'xlarge'

 }

enable: 启用屏幕密度拆分机制

exclude: 默认情况下,不设置这个属性所有屏幕密度都包括在内,如果设置,则显式声明移除一些密度。

include: 表示要包括哪些屏幕密度

reset( ): 重置屏幕密度列表为只包含一个空字符串 (这能够实现,在与include一起使用时可以表示使用哪一个屏幕密度,而不是要忽略哪一些屏幕密度)

compatibleScreens:表示兼容屏幕的列表。这将会注入到manifest中匹配的 节点。这个设置是可选的。

构建完成后可以在out/apk/目录下看到多个版本的APK。

按 ABI 拆分:

android {

splits {

abi {

enable true

reset()

include'x86','armeabi-v7a','mips'universalApk true

    }

  }

}


enable: 启用ABI拆分机制

exclude: 不使用这个属性默认情况下所有ABI都包括在内,可以指明移除一些ABI。

include:指明要包含哪些ABI

reset():重置ABI列表为只包含一个空字符串(这可以实现,在与include一起使用来可以表示要使用哪一个ABI,而不是要忽略哪一些ABI)

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

四、减resources.arsc文件

简单介绍下resources.arsc文件来源与作用:除了assets和res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理。除了assets资源之外,其它的资源都会被赋予一个资源ID。打包工具负责编译和打包资源,编译完成之后,会生成一个resources.arsc文件和一个R.java,前者保存的是一个资源索引表,后者定义了各个资源ID常量,供在代码中索引资源。

所有的png文件是以STORE的方式存储到apk里的,关于zip里的STORE和DEFLATE,详见:Zip (file format)

通俗的说,当文件是STORED的方式存储到zip,表示这个文件并没有经过压缩,如果是Defl:N的方式,表示通过DEFLATED normal的方式压缩存储到zip。

现在业内有一个开源的插件针对以上原理进行了一定的压缩,就是下面要讲的

微信资源压缩插件:AndResGuard(这推荐使用,根据我们上一个项目的经验,10MApk压缩掉了将近1M

其原理就是:

(1)对资源(png, xml, jpg等)名称混淆,资源路径名称混淆以及名称长度压缩

(2)将原来以STORED形式存储到zip中的png文件改成DEFLATED(普通压缩存储)方式。

其Gitghub地址为(里面有详细的接入流程):Android资源混淆工具使用说明

五、压缩assets文件夹

assets目录可以存字体文件、WEB页面、配置文件、图片文件。除了配置文件之外,其它的文件我们都可以进行适当的压缩处理:

字体文件:可以使用字体资源文件编辑神器Glyphs进行压缩,其压缩方式其实就是通过删除不需要的字符从而减少APK的大小。

WEB页面:可以考虑使用7zip压缩工具对该文件进行压缩,在正式使用的时候解压

图片文件:还是使用我们的Tinypng在线工具

六、减少lib文件夹

目前市场上移动端主流的架构主要是arm架构,所以可以考虑不支持x86和mips架构,而CPU是x86或mips架构的手机使用放在arm目录下的兼容so库:

还有arm架构中的eabi-v7a相比于eabi只是在图形渲染方面有了很大的改进,所以如果so库对图形渲染没有很高的要求的话,完全可以把so库只存放在arm eabi目录中,减少eabi-v7a

下面说下配置起,在 build.gradle 使用 abiFilters DSL 配置需要用到的 CPU 架构,并将不需要兼容的 so 文件从项目中移除。

defaultConfig {   

     ndk{

     // 设置支持的so库

      abiFilters 'armeabi', 'x86'

     }

  }

七、资源联网加载

这种方式本质是通过将APK里面的资源文件外置到服务器上,在需要时候再进行加载。这种方案最常用的应用的是游戏;如果是工具性应用或者业务型应用需要一些变通:

1、将一些对性能要求不高,或者UI变换频繁的页面考虑使用H5实现。这种做法的前提是需要优化WebVIew的缓存功能,以便从第二次开始,每次开启页面都可以做到快速渲染,不影响交互体验;

2、使用插件化开发。插件化开发,在动态加载和动态修复技术框架基础上,将业务抽成多个独立的插件APk;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342