• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

安卓看ZIP文件体积优化

武飞扬头像
三雒
帮助105

序言

我们在做Android包体积优化时候会将Apk拖入AS中分析,很自然发现Apk是由Dex、So、资源文件(resource.arsc,xml,asests等)三大部分组成,针对每一部分都可以进行相应的深入优化。但是我们往往会忽略Apk文件本身也是可以优化的,有点身入其中,不识庐山真面目的意思。 APK文件本身是一个ZIP文件,理解ZIP格式,从ZIP文件入手优化APK也是包体积优化不可忽略的一部分。

ZIP格式简介

ZIP文件作为一个压缩文件的归档格式,在大家在日常工作和学习中广泛使用,可谓是计算机文件传输家族的顶梁柱,对于它的深入了解我认为是非常必要的,接下来我们来看一下ZIP文件的格式组成。按照 ZIP标准 中,一个ZIP文件的整体格式如下,主要由三大部分组成数据区中央目录记录区中央目录记录尾部区

    
    ----- 数据区
    [local file header 1]
    [file data 1]
    [data descriptor 1]
    . 
    .
    .
    [local file header n]
    [file data n]
    [data descriptor n]
    
    ----- 中央目录记录区
    [archive decryption header] (EFS)
    [archive extra data record] (EFS)
    [central directory]
    
    -----  中央目录记录尾部区
    [zip64 end of central directory record]
    [zip64 end of central directory locator] 
    [end of central directory record]

如下使用010 editor解析后的一个ZIP文件,该文件中只包含3个webp文件,基本上也可以看出是按照上述三大部分进行解析的。

数据区

我们在日常使用都中都会往ZIP文件中放入多文件,直观地感觉是所有的文件被作为一个整体被压缩的,但其实每个文件的数据都是单独压缩的。这也不奇怪我们在使用编程语言API读取文件时候是可以随机读取出任何一个Entry的,如果是所有文件整体压缩的话,是很难高效单独读取的,因为压缩算法的原理一般解压数据是需要先解压前面的,才能解压出后面的,这意味着只想解压一个存储靠后的文件效率时候非常低的,需要几乎把所有文件全部解压。铺垫了这么多,来看看一个文件压缩后在ZIP中存储的相关信息对应的结构,主要由如下三个子部分:

[local file header]
[file data]
[data descriptor]

local file header

Offeset Bytes Description  
0 4 local file header signature 文件头标识 (固定值0x04034b50)
4 2 version needed to extract 解压时遵循ZIP规范的最低版本
6 2 general purpose bit flag 通用标志位
8 2 compression method 压缩方式
10 2 last mod file time 最后修改时间(MS-DOS格式)
12 2 last mod file date 最后修改日期(MS-DOS格式)
14 4 crc-32 冗余校验码(crc-32)
18 4 compressed size 压缩后的大小
22 4 uncompressed size 未压缩之前的大小
26 2 file name length 文件名长度(n)
28 2 extra field length 扩展区长度(m)
30 n file name 文件名
30 n m extra field 扩展区

其中我们主要关注两个信息:

  • 解压最低版本

2个字节,记录解压缩文件所需的最低支持的ZIP规范版本,apk解压版本默认是20, 即Deflate压缩方式。当前最低功能版本定义如下:(压缩包记录的解压版本都是需要版本*10,比如:2.0 * 10 = 20)

1.0 - 默认值
1.1 - 文件是卷标
2.0 - 文件是一个文件夹(目录)
2.0 - 使用 Deflate 压缩来压缩文件
2.0 - 使用传统的 PKWARE 加密对文件进行加密
2.1 - 使用 Deflate64™ 压缩文件
2.5 - 使用 PKWARE DCL Implode 压缩文件
2.7 - 文件是补丁数据集
4.5 - 文件使用 ZIP64 格式扩展
4.6 - 使用 BZIP2 压缩文件压缩
5.0 - 文件使用 DES 加密
5.0 - 文件使用 3DES 加密
5.0 - 使用原始 RC2 加密对文件进行加密
5.0 - 使用 RC4 加密对文件进行加密
5.1 - 文件使用 AES 加密进行加密
5.1 - 使用更正的 RC2 加密对文件进行加密
5.2 - 使用更正的 RC2-64 加密对文件进行加密
6.1 - 使用非 OAEP 密钥包装对文件进行加密
6.2 - 中央目录加密
  • 压缩方法

记录当前文件的压缩方式,有如下12种,其中0表示原文件存放不压缩,8表示使用Deflate算法压缩。JDK 7的Zip实现只支持0和8两种,其他的均不支持。对于Andorid Apk而言,大部分文件都是使用Defalte压缩,也有一些情况下为了提升文件的运行时加载速度是选择不压缩的,比如resource.arsce, so文件等, 在不压缩的情况下可以直接mmap,加快IO的速度。

0 - The file is stored (no compression)
1 - The file is Shrunk
2 - The file is Reduced with compression factor 1
3 - The file is Reduced with compression factor 2
4 - The file is Reduced with compression factor 3
5 - The file is Reduced with compression factor 4
6 - The file is Imploded
7 - Reserved for Tokenizing compression algorithm
8 - The file is Deflated
9 - Enhanced Deflating using Deflate64™
10 - PKWARE Data Compression Library Imploding
11 - Reserved by PKWARE
12 - File is compressed using BZIP2 algorithm

file data

file data紧跟在local file header之后,存储文件的具体数据,根据其压缩方式不同可能是源文件本身数据也可能是压缩后的数据。

data descriptor

只有当 local file header的 general purpose bit flag 字段第3位bit置1时,data descriptor才会存在。 它是字节对齐的,紧跟在文件数据的最后一个字节之后。当且仅当无法在ZIP 文件中查找时才使用此描述符,例如:当输出的ZIP文件是标准输出或不可查找设备时使用文件描述。 说人话就是,看看就好,正常情况下都不需要使用。

Offset Bytes Description  
4 4 crc-32 冗余校验码
8 4 compressed size 压缩后的大小
12 4 uncompressed size 未压缩之前的大小

中央目录记录区

中央目录记录区是由一系列的file header所组成,一个file header对应数据区中的一个压缩文件。

[file header 1]
      .
      .
      . 
[file header n]
[digital signature] 

file header中存储的信息如下:

Offset Bytes Description  
0 4 central file header signature 文件头标识 (固定值0x02014b50)
4 2 version made by 高位字节表示文件属性信息的兼容性, 低位字节表示压缩软件支持的ZIP规范版本
6 2 version needed to extract 解压时遵循ZIP规范的最低版本
8 2 general purpose bit flag 通用标志位
10 2 compression method 压缩方式
12 2 last mod file time 最后修改时间(MS-DOS格式)
14 2 last mod file date 最后修改日期(MS-DOS格式)
16 4 crc-32 冗余校验码
20 4 compressed size 压缩后的大小
24 4 uncompressed size 未压缩之前的大小
28 2 filename length 文件名长度(n)
30 2 extra field length 扩展区长度(m)
32 2 file comment length 文件注释长度(k)
34 2 disk number start 文件开始位置的磁盘编号
36 2 internal file attributes 内部文件属性
38 4 external file attributes 外部文件属性
42 4 relative offset of local header 对应 [local file header] 的偏移位置
46 n file name 文件名
46 n m extra field 扩展域
46 n m k file comment 文件注释内容

中央目录记录尾部区

中央目录记录尾部主要作用是用来定位中央目录记录区的开始位置,同时记录压缩包的注释内容

Offset Bytes Description  
0 4 end of central dir signature 中心目录结束标识 (固定值0x06054b50)
4 2 number of this disk 当前磁盘编号
6 2 number of the disk with the start of the central directory 中心目录开始位置的磁盘编号
8 2 total number of entries in the central directory on this disk 该磁盘上所记录的entry数量
10 2 total number of entries in the central directory 中心目录中总共的entry数量
12 4 size of the central directory 中心目录大小
16 4 offset of start of central directory with respect to the starting disk number 中心目录开始位置相对于.ZIP archive开始的位移
20 2 .ZIP file comment length ZIP文件注释内容长度(n)
22 n .ZIP file comment ZIP文件注释内容

APK体积优化分析

上述我们已经对ZIP文件有了基本了解,知道三大部分中中央目录记录尾部只有固定数量的字节,是很小的,因此从ZIP视角看主要是针对中央目录记录区、数据区进行优化。

中央目录记录区优化

中央目录区是由一系列的file header组成的,占用的空间大致受file heaer大小数量两个因素影响。

资源混淆

从减少单个file header大小的角度出发,分析其中包含的的信息格式,字段大小基本上是固定的,有些无从下手,经过一通猜测有两个地方我们可能是可以优化的,因为它们的内容长度可变,一个是file name, 另一个是file comment。 但对于Android Apk文件而言一般打包过程中并不会写入file comment. 那么file name到底能不能优化呢?

Apk中的很多file name我们是可以自定义的,比如res目录下的文件res/xxhdpi/test.webp,我们完全可以叫做res/xxhdpi/a.wep,这样就比原来的字符更加短,所占用的空间也更加少。那我们是不是可以将其缩短为r/a.webp或者更极端缩短为a.webp,答案是可以的,这也就是我们耳熟能详资源混淆。由于我们代码或者编译过后的xml中基本上都是使用资源id来进行资源加载的,而资源id和资源文件路径的对应关系时候存储在resoure.arsc文件中的,这样就给了我们可乘之机,我们通过修改文件路径,并且同时修改resource.arsc文件,即可保证运行时资源加载的正确性。这个优化除了优化ZIP中央目录记录区之外,也同时能优化resource.arsc文件大小。

shrikResources

从减少file header的数量角度出发,主要就是尽可能删除APK内的无用文件,由于Apk中数量最多的是资源文件,所以shrinkResources对这部分有明显的贡献,最好开启模式效果更好。不过删除无用文件的收益主要还是来自文件大小本身,减少file header只是其”隐形“的附加的收益。

总的来说中央目录记录区占用的大小并不是很大,优化空间也比较有限。

数据区优化

数据区是占用空间的大头,同样受单个大小和数量两个因素影响。

提升压缩率

单个大小的主体是文件压缩后的数据,从ZIP的视角看就是如何提高压缩率。上面我知道ZIP支持很多压缩方法,总体切入点有两个。

  • APK内并不是所有的文件都是压缩的,有些文件是直接Store的,可以考虑将Sotore改为压缩状态。
  • APK使用的Deflate其实并不是压缩率最高的算法,可以考虑更换压缩率更高的算法。不过更换压缩算法的话需要考虑解压器,Android(JDK) 的ZIP实现只支持Store和Deflate两种,这就限制APK只能使用Deflate算法,那这样就没有优化空间了么?不然,Deflate算法只是个标准,具体的实现也是有优劣的,JDK的Deflate压缩并不算很优,使用更优Defalte算法也是尝试的方向之一。
Store改为压缩
public class PackagingUtils {
/**
* List of file formats which are already compressed or don't compress well, same as the one
* used by aapt.
*/
public static final ImmutableList<String> DEFAULT_AAPT_NO_COMPRESS_EXTENSIONS =
        ImmutableList.of(
                ".jpg", ".jpeg", ".png", ".gif", ".opus", ".wav", ".mp2", ".mp3", ".ogg",
                ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy",
                ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr",
                ".awb", ".wma", ".wmv", ".webm", ".mkv");
                
}

如上代码所示,考虑到运行时的性能,AGP在package阶段针对如上的文件格式不进行压缩,这些文件以Store形式存放到APK中。从包体积的角度考虑的话可以配置这些类型的文件压缩,AndResguard也提供了这项能力,如下我们的应用已经配置了其中四种格式的文件进行压缩。

andResGuard {

    use7zip = true
   
    compressFilePattern = [
                "*.png",
                "*.jpg",
                "*.jpeg",
                "resources.arsc",
                ]
        
 }

但仍然有一些文件是未压缩的,通过unzip -lv test.apk > zipinfo.txt来查看文件的压缩状态

如上有.webp , .mp3 , .jar文件没有进行压缩,但经过尝试发现仅有jar文件能压缩并有效。

jar文件的压缩前后变化

压缩前:

压缩后:

  • webp本身就是一种数据高度压缩的文件格式,很多webp经过Defalte压缩之后会更大,因此一些ZIP压缩器在压缩过程中选择将其以Stored形式存放。

  • mp3倒是压缩之后可以获取80KB的收益,但由于MediaPlayer在播放assets或者raw目录下的mp3时候经常会用到如下两个api,会在native层直接mmap,而mmap要求不压缩并且四字节对齐,否则会报错。

更优Defalte算法

按照 7z官网的说法 7-Zip 创建的 zip 格式比大多数其它压缩软件创建的都小 2-10%,因此AndResguard使用命令 7z a -tzip out.apk ./apkdir/* -mx=9 对APK进行重新压缩,此时使用的仍然是Deflate算法,压缩等级为最大9,得到的APK确实小了2%左右。我目前并未对Deflate以及7z的实现进行深入研究,按照微信的说法7z使用了大字典优化。

我们也测试和验证了另一个对ZIP重压缩的库advzip,其使用libdeflate算法,发现其比7z压缩之后的更小,可以再小1%左右,已经在我们的应用上做了验证。压缩前后信息对比如下图,左侧为7z压缩之后的,右侧为advzip压缩之后的。其中Defl:X表示压缩的最好,Defl:N表示正常压缩,从压缩前后entry的size上也可以看出收益。 advzip 库重压缩会把apk内所有的文件都压缩,不支持配置一些文件不压缩,这个需要修改代码扩展一下功能。

删除无用文件

从减少数量上看,主要还是无用文件删除,这个主要还是依赖APK内部文件所对应的优化手段去实现,本文不做详细讨论。

文件合并

从提升ZIP文件整体压缩视角看,还有另一个切入点文件合并,因为ZIP文件是单个文件压缩,无损压缩的方式只有重复数据压缩、编码压缩两种,而多个文件合并到一起之后重复数据会更多,而且编码压缩需要的字典也只需要一份,因此总体上能提高压缩率。该部分对于Dex这种大小有限制的文件并没有什么空间,如果能将一些So文件合并或者业务上的资源文件合并应该会有些优化效果,目前为止这部分并没有重大效果的优化实践。

总结

本文先对ZIP文件格式做了简单的介绍,并在ZIP文件的视角下分析可能对Apk体积优化的地方。通过逐个对ZIP文件进行拆分以及挖掘,引出了的资源混淆shrinkResources提升压缩率文件合并等优化。可能很创新的地方不是很多,很多知识都是旧的知识,但是以一种更加系统的分析方式呈现出来,希望能对大家有所帮助,也希望后来者能有更多的探索和创新吧。

参考文档

ZIP压缩算法详细分析及解压实例解释

zip 的压缩原理与实现

浅析ZIP格式

压缩包Zip格式详析

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfejik
系列文章
更多 icon
同类精品
更多 icon
继续加载