Android--APK编译构建流程

Android--APK编译构建流程

1、前言

作为一个Android开发者,Android Studio进行开发、调试和发布应用程序是基本操作了。现如今公司的APP都是一键后台打包,但是实际上编译打包到底发生了什么,让我们的代码变成APK?顺带给新同事扫扫脑子。如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

2、介绍

放出已经包浆的官方编译打包流程,如下:

我们可以的分为7点:

  1. 资源处理(AAPT) :Android Asset Packaging Tool(AAPT)用于处理资源文件,生成R.java(它使代码可以引用资源)和编译后的资源文件。

  2. AIDL处理 :对于那些使用Android Interface Definition Language (AIDL)定义的远程接口,aidl工具会将.aidl文件转换为相应的.java文件。

  3. Java编译 :Java编译器(javac)会编译源代码、R.java和由AIDL生成的.java文件,将它们转换为.class文件。

  4. DEX编译.class文件(包括应用程序和任何依赖的库)都会被转换为.dex(Dalvik Executable)格式。这是通过D8DX工具完成的。

  5. APK打包apkbuilder或其现代版本(如Android Gradle插件提供的构建工具)将所有.dex文件、未编译的资源文件和已编译的资源文件打包到一个未签名的APK文件中。

  6. 签名 :未签名的APK必须被签名才能在设备上运行。签名过程使用Debug或Release密钥库,可以使用jarsigner工具或Android Studio的签名机制进行签名。

  7. 对齐 :使用zipalign工具对已签名的APK进行优化,确保正确的文件对齐。这样可以提高应用在设备上运行时的性能。

接下来将这7点展开。嘻嘻嘻

3、APPT

AAPT(Android Asset Packaging Tool)是一个专为Android应用程序开发而设计的工具。它的主要作用是将应用程序的资源文件转换、打包并生成对应的编码索引,以便在程序中使用。

写代码期间会自动调用appt.exe~

这里是AAPT执行的主要操作:

  1. 检查AndroidManifest.xml:

    使用parsePackage进行初始化并设置一些attribute,例如package, minSdkVersion, uses-sdk等。

  2. 引用其他资源包:

    使用table.addIncludeResources(bundle, assets)添加被引用的资源包,如系统的android:命名空间下的资源。

  3. 收集资源文件并处理overlay:

    如果指定的overlay(重叠包)包含与当前编译包重名的资源,则使用overlay的资源。

  4. 加载资源到资源表:

    处理res目录下的各种资源子目录,函数为makeFileResources。此函数会检查资源文件名的合法性并将其添加到ResourceTable中。

  5. 编译values资源并添加到资源表:

    因为values资源较为特殊,它们需要经过编译后才能添加到资源表中。

  6. 给bag资源分配ID:

    在继续编译其他资源之前,首先给bag资源(如attrs等)分配ID,因为其他资源可能会引用它们。

  7. 编译xml资源文件:

    这一步将对layout, anims, animators等资源进行编译,解析xml文件,为属性名称赋予资源ID,解析属性值,扁平化为二进制文件。

  8. 编译AndroidManifest.xml文件:

    • 清空原始数据,重新解析。
    • 处理包名重载,将各种相对路径名称转换为绝对路径名称。
    • 编译manifest.xml文件,生成最终的资源表。
  9. 生成R.java文件:

    根据资源ID,为每个资源生成一个对应的java常量。

  10. 生成resources.arsc文件

PS:有时候的布局ID报红,罪魁后手就是它!

4、AIDL

Android Interface Definition Language是Android中的一个工具,它允许开发者定义和使用跨应用间的IPC(进程间通信)接口。这个阶段处理.aidl文件,生成对应的.java文件。不过一般的项目中很少有此类文件。(我是没用过的,跳过SKKKKKIP)

5、Java Compiler

有了R.java和aidl生成的java文件,再加上工程的源代码,现在可以使用javac进行正常的java编译生成class文件了。这个过程是Java开发的核心部分,不仅适用于Android,还适用于所有Java应用。在对应的build下即可找到相关的代码。

6、Dex

  1. 从.class到.dex

    经过Java编译过程后,会得到一系列的.class文件,这些文件包含Java字节码。但Android不直接运行这些Java字节码。相反,它们需要被转换为Dalvik字节码,这是Android专用的格式。这个转换是由工具如dx完成的(在更早的Android开发工具链中),或者由D8/R8完成(在较新的Android Studio版本中)。

  2. 字节码优化

    转换过程中,会进行一些优化,以减少最终DEX文件的大小并增强执行效率。例如,它会删除未使用的代码(死代码)或合并相似的代码片段。

  3. 处理多个类文件

    Android应用可能会产生大量的.class文件。所有这些文件会被聚合到一个或多个DEX文件中。主DEX文件通常被称为classes.dex,但如果应用大到超过DEX文件的方法数限制(约64k方法),那么会生成多个DEX文件,如classes2.dexclasses3.dex等(classesMAXXXXXX.dex)。

  4. 多DEX处理

    Android有一个方法数的限制,一个DEX文件中的方法数不能超过约64k(65536)方法。大型应用可能会达到这个限制,需要使用多DEX解决方案。工具如Multidex允许应用超过这个限制,但可能需要开发者进行一些额外的配置和处理。

DEX其实就是Class集合包~,插桩一般发生在5、6之间~

7、APK打包

  1. 资源整合

    所有编译后的资源文件(如由AAPT生成的二进制文件)和已经转换为Dalvik字节码的.dex文件都被聚合在一起。

    除了这些,还包括在应用中的其他原始资源,如图片、音频文件、XML配置文件等。

  2. JNI和本地库

    如果的应用使用了本地代码,如C或C++代码,那么编译后的本地库(如.so文件)也会被包含在APK中。这些库通常位于lib/目录下,并根据不同的架构进行组织,如armeabi-v7aarm64-v8ax86等。

  3. Zip打包

    所有这些组件(资源、.dex文件、库等)最后都会被打包成一个ZIP文件,这就是APK。实际上,可以使用任何ZIP工具打开一个APK文件,并查看其中的内容。

  4. META-INF目录

    这个目录包含APK的签名信息以及MANIFEST.MF,它是Manifest文件,列出了APK包中的所有文件和它们的摘要。

AGP3.6.0之后,使用zipflinger作为默认打包工具来构建APK

8、签名

为什么需要签名?

  • 身份验证: 签名确保应用来源的真实性和完整性。一旦应用被签名,任何对其的修改都会使签名失效。
  • 更新和版本管理: Google Play使用签名作为应用的身份标识。当尝试更新现有的应用时,新的APK必须使用与之前版本相同的密钥签名。

签名过程:

  1. 生成密钥 : 使用keytool命令行工具生成一个私人密钥。这个密钥将被用于签名应用。

  2. 签名 : 使用jarsigner工具或Android Studio的签名工具对APK进行签名。

  3. 签名模式: 有两种主要的签名模式 - V1 (JAR签名) 和 V2 (完整APK签名)。V2更快,更安全,因为它涵盖了整个APK文件。

    V1和V2的最大区别在于签名的范围和存储位置。V1为APK中的每个文件单独进行签名,而V2为整个APK文件进行一次签名。这使得V2签名提供了更强大的安全性和更高的验证效率。另外,从Android 7.0 (Nougat) 开始,V2签名成为了默认的签名方法,但为了确保向后兼容性,许多开发者会同时使用V1和V2进行签名。

(V1、2就不展开说了~)

9. 对齐:

为什么需要对齐?

  • 性能优化: 通过对APK内的文件进行对齐,可以确保Android系统在运行应用时从APK中读取文件时更加高效。
  • 快速映射: 对齐确保资源在APK中以4字节边界对齐,这使得运行时系统可以直接mmap APK,而不是需要进行额外的内存拷贝,从而提高效率。

对齐过程:

  • 使用zipalign工具进行对齐。这是Android SDK的一部分。
  • zipalign会对APK中的所有数据进行重新排序,确保与4字节边界对齐。
  • 一般来说,当使用Android Studio进行构建时,对齐会自动进行。但如果手动签名APK,可能需要手动运行zipalign

注意事项:

  • 虽然zipalign是一个很重要的步骤,但并不会改变应用的功能或其行为。它纯粹是一个性能优化步骤。
  • 永远在签名APK之后进行对齐操作。因为对齐会改变APK的内容,而这会破坏之前的签名。

总之,签名为Android应用提供了一个身份标识,确保了其来源和完整性,而对齐则确保了应用在设备上的性能优化。两者都是发布Android应用时的关键步骤。

10、Gradle Task

我们已经了解了Apk编译构建流程的主要步骤,实际上在你点击Run或者build时控制台输出的一大片日志,恰好对应着这些步骤(这是一个新建的单Actitvity项目日志,实际上会复杂得多)

因为我没有使用AIDL文件~所以没有AIDL Task:

ruby 复制代码
// 初始化并启动Gradle守护进程,通过缓存信息和重用构建过程使构建更快
Starting Gradle Daemon...
Gradle Daemon started in 1 s 301 ms
> Task :app:createDebugVariantModel
// 预构建步骤
> Task :app:preBuild UP-TO-DATE
// 为'debug'构建准备
> Task :app:preDebugBuild UP-TO-DATE
// 合并Native调试元数据
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
// 为'debug'构建生成Res
> Task :app:generateDebugResValues
// 检查AAR(Android Archive)元数据
> Task :app:checkDebugAarMetadata
// 映射'debug'资源路径
> Task :app:mapDebugSourceSetPaths
// 为'debug'构建生成资源
> Task :app:generateDebugResources
// 打包'debug'资源
> Task :app:packageDebugResources
// 创建与屏幕尺寸兼容的'debug'Manifest
> Task :app:createDebugCompatibleScreenManifests
// 提取'debug'的深度链接
> Task :app:extractDeepLinksDebug
// 解析'debug'本地资源
> Task :app:parseDebugLocalResources
// 处理主要的'debug'Manifest
> Task :app:processDebugMainManifest
// 处理'debug'Manifest
> Task :app:processDebugManifest
// 'debug'的Java预编译
> Task :app:javaPreCompileDebug
// 合并'debug'的Shaders
> Task :app:mergeDebugShaders
// 编译'debug'的Shaders
> Task :app:compileDebugShaders NO-SOURCE
// 为'debug'构建生成Assets
> Task :app:generateDebugAssets UP-TO-DATE
// 合并'debug'资源
> Task :app:mergeDebugResources
// 合并'debug'Assets
> Task :app:mergeDebugAssets
// 压缩'debug'Assets
> Task :app:compressDebugAssets
// 处理'debug'的Java资源
> Task :app:processDebugJavaRes NO-SOURCE
// 分解'debug'的文件依赖
> Task :app:desugarDebugFileDependencies
// 合并'debug'的JNI库文件夹
> Task :app:mergeDebugJniLibFolders
// 检查'debug'的重复类
> Task :app:checkDebugDuplicateClasses
// 合并Native库
> Task :app:mergeDebugNativeLibs NO-SOURCE
// 删除'debug'的调试符号
> Task :app:stripDebugDebugSymbols NO-SOURCE
// 验证'debug'签名
> Task :app:validateSigningDebug
// 写入'debug'应用元数据
> Task :app:writeDebugAppMetadata
// 合并库的Dex文件
> Task :app:mergeLibDexDebug
// 写入'debug'签名配置版本
> Task :app:writeDebugSigningConfigVersions
// 为包处理'debug'Manifest
> Task :app:processDebugManifestForPackage
// 处理'debug'资源
> Task :app:processDebugResources
// 合并扩展的Dex文件
> Task :app:mergeExtDexDebug
// 使用Kotlin编译'debug'
> Task :app:compileDebugKotlin
// 使用Javac编译'debug'
> Task :app:compileDebugJavaWithJavac NO-SOURCE
// 构建'debug'的Dex文件
> Task :app:dexBuilderDebug
// 合并项目的Dex文件
> Task :app:mergeProjectDexDebug
// 合并'debug'的Java资源
> Task :app:mergeDebugJavaResource
// 打包'debug'
> Task :app:packageDebug
// 创建'debug' APK列表文件重定向
> Task :app:createDebugApkListingFileRedirect
// 完成'debug'组装
> Task :app:assembleDebug
BUILD SUCCESSFUL in 21s
31 actionable tasks: 31 executed

11、结尾

本文主要详细介绍了Apk编译构建流程,当然光知道其实用处不大,一般而言我们会在这个流程中做各种事情,如编译优化,包体缩减,注解处理器应用,字节码插桩、自动化构建等等~不过篇幅有限,如果你感兴趣留言吧!很快更新下一篇。希望本文能为你在Android开发旅程中提供有价值的知识。如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

相关推荐
ChinaDragonDreamer2 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院4 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下4 小时前
android navigation 用法详细使用
android
小比卡丘7 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭8 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss9 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.10 小时前
数据库语句优化
android·数据库·adb
GEEKVIP12 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200514 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68914 小时前
Android广播
android·java·开发语言