近日,香港联交所网站信息显示,菜鸟已正式向港交所提交上市申请,成为阿里巴巴“1+6+N”之后首个正式进入IPO进程的业务集团。花旗、中信证券、摩根大通为联席保荐人。至此,菜鸟有望成为阿里集团旗下六大业务集团中首家独立上市的公司。
明天就是中秋+国庆双假同至啦,祝大家双节快乐!我们节后再见。
本篇文章转自d袋鼠b的博客,文章深入分析了APK格式,相信会对大家有所帮助!
最近在升级项目的 Gradle 及 AGP 版本,在进行APK编辑再压缩时遇到了前后压缩比不一致的问题,所以抽空又一总结了下 APK (ZIP) 文件格式。
无论是使用 7z 进行包体积优化,或是快速构建多渠道包,又或是V2、V3签名等都是基于 APK (ZIP) 文件格式进行的,因此对 APK (ZIP) 格式的了解也颇为必要 。
未进行(V2、V3)签名的 APK 就是一个标准的 ZIP 文件,所以本文会先介绍 ZIP 文件格式,后续再介绍签名后的 APK 与 标准 ZIP 文件有何差异。
[local file header 1]
[file data 1]
[data descriptor 1]
.
.
.
[local file header n]
[file data n]
[data descriptor n]
[central directory]
[end of central directory record]
先看数据区,数据区可以理解为一个数组,数组的每一项由 [local file header][file data][data descriptor] 这三项组成。
首先看看 local file header (文件头) 的结构定义:
这里需要注意的是每个 Entry 的文件头 都有自己的 Compression method —— 压缩方式,也就是说 ZIP 文件内的不同 Entry 可以选择不同的压缩方式,并不要求里面的文件都采用同一种压缩方式。
compression method: (2 bytes)
(see accompanying documentation for algorithm
descriptions)
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(tm)
10 - PKWARE Data Compression Library Imploding
11 - Reserved by PKWARE
12 - File is compressed using BZIP2 algorithm
APK中,使用的是 0-Stored (不压缩)和 8-Deflated (Deflated 压缩算法压缩) 。
Flie data 则是文件压缩或直接存储后的二进制数据。
Data descriptor 区块,只有通用比特标志位的第三位bit为1时才会出现此区块。
Data descriptor:
crc-32 4 bytes
compressed size 4 bytes
uncompressed size 4 bytes
This descriptor exists only if bit 3 of the general
purpose bit flag is set (see below). It is byte aligned
and immediately follows the last byte of compressed data.
This descriptor is used only when it was not possible to
seek in the output .ZIP file, e.g., when the output .ZIP file
was standard output or a non seekable device. For Zip64 format
archives, the compressed and uncompressed sizes are 8 bytes each.
可以看出:在该APK文件中,classes2.dex 文件的压缩方式为 0x0008 (Deflated)。
在 ZIP 文件中,数据区后面紧接着的就是中心目录区,ZIP 里的每个文件 (Entry) 在数据区和中心目录区都分别有一条对应的数据记录 (File Record) 和目录记录 (Dir Record)。(完成V2、V3签名的 APK 中,签名区块则位于数据区与中心目录区之间,签名区块这一点单独讲,先看看中心目录区。)
中心目录区由多条目录文件头 (Fille header) 和一条目录尾部 (End of central directory record) 组成,
从 File header 的格式定义中不难看出:目录区文件头比数据区文件头多了几个字段,例如:数据文件头的偏移地址。其他字段基本是重复的。
目录记录 (Dir Record) 只有文件头,因此比起 数据记录 (File Record) 要小很多,解析起来也比较快,所以通过中心目录区可以快速获取压缩文件部分信息而不用解析整个 ZIP ,例如:ZIP 中是否含有某个文件。
同时目录记录里包含了对应的文件记录的偏移量,这样能通过目录快速定位并解压 ZIP 内的单个文件,避免从数据区从头遍历解析。
-
End of central directory record
上面提到,通过中心目录区可以优化数据的查找和解析过程,那如何快速知道中心目录区的起始位置呢?
目录尾部对中心目录进行了简要描述,中心目录区的起始位置就在目录尾 (End of central directory record) 当中,其格式如下:
通过目录尾部的中心目录起始位置偏移,能快速获取目录位置。
所以很有意思的一点:在某些场景下,ZIP 也可以从后往前解析的,而且能提效。常用的 ZipFile 正是使用了从后往前解析的方式。首先找到尾部,通过尾部,获取中心目录区的偏移量和长度,然后通过中心目录区域信息定位实际的数据区获取最终的数据。
这一步分析可能比较复杂,若阅读困难可以先略过~ 详细过程如下:
这一步主要是从目录尾部获取中心目录起始偏移等信息,并建立目录数据Hash与其起始偏移地址映射关系;在 ZipFile 的构造函数中会调用 ZipFile#open 函数:
[java.util.zip.ZipFile#open] -> [xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Open]-> [xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Open_Generic] -> [xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Put_In_Cache0] -> [xref/libcore/ojluni/src/main/native/zip_util.c#readCEN]
通过进一步阅读可以了解到 [readCEN]函数中只是对中心目录做了粗略的解析。只有在 getEntry 时才会仔细解析单条目录记录,其解析逻辑如下:
再看看尾部数据获取,从文件末尾查找能与尾部数据标志 0x06064B50 匹配的4个字节。
首先看下 ZipFile#getEntry(String ) 是如何查找并解析出 Entry 的:
今天周末,再深究一下,看看newEntry的解析。
这里就和我们看到头文件(File Header)的字段对应上了,头文件内容基本解析出来并放入ze 对象,ze 对象的指针做为函数的返回值。
看到这里,能体会到 ZipFile 做的优化还是挺细致的,ZipFile 创建的时候只做目录的粗解析,在获取具体 文件(Entry) 信息时只解析需要的头文件,到目前为止只获取了 Entry 基本信息,并没有去解析数据区。
再看看 ZipFile 如何获取文件的二进制数据流的?
通过上面的 getEntry,我们已经能拿到 Entry 在中心目录区的头文件对象了,因此java.util.zip.ZipFile#getInputStream(ZipEntry entry)获取数据流时,通过 Entry 对应的指针获取数据记录 (File Record) 的起始地址直接读取数据即可。
[java.util.zip.ZipFile#getInputStream(ZipEntry entry)] -> [java.util.zip.ZipFile.ZipFileInputStream#read(byte[], int, int)] -> [xref/libcore/ojluni/src/main/native/java_util_zip_ZipFile.c#ZipFile_read>] -> [xref/libcore/ojluni/src/main/native/zip_util.c#ZIP_Read]
前文提到:未进行(V2、V3)签名的 APK 就是一个标准的 ZIP 文件,完成V2、V3签名的 APK 中,签名区块则位于数据区与中心目录区之间。
目录尾部标识:06 05 4B 50,中央目录区起始位置偏移量:00 04 4E 7D
2)通过中央目录区偏移量,找到常量标识"APK Sig Block42"及签名区块长度:
3)查找 ID-Valuce 的起始位置,以魔术最后一位,前移动 0F F8 :
所以图中的偏移量为 0X043E86 就是ID-Value的起始位。
V2 的签名信息存放在 ID = 0xa 的数据块中
V3 的签名信息存放在 ID = 0xf05368c0 的数据块中
不难发现这里的Value字节全为0,这些 0 本身毫无含义,本以为能删除掉缩减一点包体,但是经查询,这里为了使签名区块为4096 的整数倍与内存页对齐(同时高版本 Android 会检测这个对齐规则,不可删除)。
部分团队的多渠道APK方案,则是在签名区块中加入额外的 ID-Value ,通常是固定一个 ID,Value设置为渠道信息即可。
至此完成部分了 ZIP 及 APK 文件的格式分析,另外 ZIP 相关的其他知识点也比较繁多,很难说一篇文章能盖全,或全面了解,例如各类压缩算法、ZIP 版本差异以及 7z (后续计划讲下 7z ) 等等,但是能掌握到文中介绍的这些,也足以解决日常遇到的绝大多 ZIP 相关问题了。
-
https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.0.txt
-
http://androidxref.com/9.0.0_r3/
-
https://www.youtube.com/watch?v=_a_keel8JFI
-
https://source.android.com/docs/security/features/apksigning/v2?hl=zh-cn#apk-signing-block
原创:写给初学者的Jetpack Compose教程,Modifier

到此这篇安卓软件后缀名是什么(安卓软件后缀名是多少)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/hd-yjs/16973.html