Apk构建流程
Apk是什么
维基百科,自由的百科全书
跳到导航跳到搜索
APK
APK format icon (2014-2019).png
扩展名 .apk、.xapk、.apks、.apkm
互联网媒体类型 application/vnd.android.package-archive
格式类型 包管理器 存档文件
专门属 软件包
延伸自 JAR 和 ZIP
APK 文件基于 ZIP 文件格式,它与JAR文件的构造方式相似。它的互联网媒体类型是:application/vnd.android.package-archive[5]。
扩展资料:
Android5.0引入了Split APK机制,这是为了解决65536上限以及APK安装包越来越大等问题。Split APK机制可以将一个APK,拆分成多个独立APK。
在引入了Split APK机制后,APK有两种分类:
Single APK:安装文件为一个完整的APK,即base APK。Android称其为Monolithic。
Mutiple APK:安装文件在一个文件目录中,其内部有多个被拆分的APK,这些APK由一个 base APK和一个或多个split APK组成。Android称其为Cluster。
Apk构成的属性
META-INF目录:包含两个签名文件(CERT.SF和CERT.RSA),以及一个manifest文件(MANIFEST.MF)
assets目录:包含工程中的asset目录下的文件,可以使用AssetManager获取
res目录:包含那些没有被编译到resources.arsc的资源
lib目录:包含适用于不同处理器的第三方依赖库,这里边可以有多个子目录,比如armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, 以及mips
resources.arsc文件:存储编译好的资源,包括项目工程中的res/values目录里的xml文件,它们都被编译成二进制格式,也包括一些路径,指向那些没有被编译的资源,比如layout文件和图片
classes.dex文件:项目中的java类都被编译到该dex文件,这个文件可以被Android的Dalvik/ART虚拟机解析。
AndroidManifest.xml:二进制格式的manifest文件,这个文件是必须的。
这些文件是Android系统运行一个应用程序时会用到的数据和代码,下面介绍系统如何安装一个APK包。
Apk编译的流程
- 通过aapt-Android Asset Packing Tool 打包res资源文件,生成R.java、resources.arsc和res文件(二进制 & 非二进制如res/raw和pic保持原样)
这一过程主要是aapt对res和asset文件夹,AndroidManifest.xml,android库(aar,jar)等的资源文件进行处理。
- 先检查AndroidManifest.xml的合法性
- 然后编译res与asserts目录下的资源并生成resource.arsc文件
- 再生成R文件。
除了assets和res/raw资源被原封不动地打包进APK之外,其它的资源都会被编译或者处理,大部分文本格式的XML资源文件会被编译成二进制格式的XML资源文件。
除了assets资源之外,其他的资源都会在R文件中被赋予一个资源ID。也就是说,R文件中只会存在id,真正的资源存在于resource.arsc中,resource.arsc相当于一个资源索引表,资源id是key,value是资源路径。我们使用drawable-xdpi或者drawable-xxdpi这些不同分辨率的图片的时候,就是依靠resource.arsc根据设备的分辨率选择不同的图片
(R.java相关知识)
2. 处理.aidl文件,生成对应的Java接口文件
3. 通过Java Compiler编译R.java、Java接口文件、Java源文件,生成.class文件 (编译期超出64k?)
4. 通过dex命令,将.class文件和第三方库中的.class文件处理生成classes.dex
5. 通过JapkbuilderJ工具,将aapt生成的resources.arsc和res文件、assets文件和classes.dex一起打包生成apk
6. 通过Jarsigner工具,对上面的apk进行debug或release签名
通过zipalign工具,将签名后的apk进行对齐处理
(引申出问题,为什么需要签名,签名的方式有几种,有什么区别?)
7. 通过zipalign工具,将签名后的apk进行对齐处理
(引申一个问题,为什么需要对齐?)
8. 混淆proguard:proguard主要的目的是混淆代码,保护应用源代码。次要的功能还有移除无用类等,优化字节码,缩小包体积。
APK扩展性问题:
Android十万个为什么
1. R.java是什么(R.java构成)
R.java由ADT(aapt)编译生成,
记录应用apk-res路径所有资源,并根据这些资源建立对应的ID(生成唯一的标识符)
编译期间,同一个资源在普通的apk中只会属于一个package,一个type,只拥有一个entry次序。
以0x01开头的就是系统已经内置的资源id,以0x7f开头的是咱们自己添加的app资源id
由于项目开发是多模块的开发 - 编译期相关的知识
- 主模块中的R.java中的字段以final修饰,以常量形式存在。
- 库模块中的R.java中的字段不以final修饰,以变量形式被项目中的代码所引用。
Android资源的合并
覆盖的优先级如下:
build variant > build type > product flavor > main source set > library dependences
这种依赖关系不同于gradle里面的implementation依赖传递,implementation是跨级不能传递,但是R文件的生成是跨级可以传递的。
module的R文件数 = 依赖的module/aar数量 + 1(自身的R文件)
R.文件的生成是底层到上层
为什么主模块(application module)资源有final修饰,非主模块(library module)都不是final的?
比较早的aapt的版本生成的非主模块的资源id确实都是final修饰的,这样会带来一个问题,这些资源id全部内联到代码中,一旦新增或者删除,修改了资源,资源id就会有变化,所有的代码都需要重新编译,造成严重的编译耗时。
后来改为主模块final常量方式内联,非主模块引用方式,这样等按照从下到上编译到App模块的时候,所有的资源id都已经确定了,底层模块的资源只需要通过引用就能拿到自己对应的id,而修改(新增,删除,修改)了资源之后,也只需要重新生成R文件就好了。编译耗时大大减少。
在我们平常打包的时候,反编译apk,再合并资源回编时,也是要重新生成R文件。
开发时,在其他module代码引用资源文件时,使用以下代码进行索引
1 | /** |
为什么同一个资源,不同模块产生的R.java中的资源id值是不统一的?
因为资源id只是表示资源的次序,而不是别的跟资源本身绑定的属性。当到了不同的模块以后,参与编译的资源变多了,那次序肯定会改变。资源id也就改变了。并且子模块的资源id只是引用形式存在于代码中,id具体是什么值并不是很care。
apk或者aab,为什么需要对齐?
zipalign的主要工作是将apk包进行对齐处理,使apk包中的所有资源文件举例文件起始偏移为4字节的整数倍,这样通过内存映射访问apk时的速度会更快。
为什么快呢?如果每个资源的开始位置上都是一个资源之后的4n字节,那么访问下一个资源就不用遍历,直接跳到4字节之后即可
apk或者aab混淆的方式有哪些()
压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性
优化(Optimize):字节码进行优化,移除无用的指令。
混淆(Obfuscate):使用a、b、c、d这样简短而无意义的名称,对垒、字段和方法进行重命名。
预检测(Preveirfy):在Java平台对处理后的代码进行预检测,确保加载class文件是可执行的
“Android” 64k报错
应用及第三方库包含的方法数(method)总和超过65536,在开发的时候,会遇到一个构建的错误。
指明您的应用达到Android构建规定的应用的限制:
构建错误的原因:
dex文件格式的限制
Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 二进制文件形式的可执行字节码文件,这些文件包含用来运行应用的已编译代码。
Dalvik Executable 规范将可在单个 DEX 文件内引用的方法总数限制为 65536 (其中包括 Android 框架方法、库方法以及您自己的代码中的方法)
其他限制
系统对dex文件进行优化操作时分配的缓冲区大小的限制 ,方法如果超出缓存区,同样也是会报错的。