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开发旅程中提供有价值的知识。如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

相关推荐
潘潘潘8 分钟前
Android多线程机制简介
android
CYRUS_STUDIO2 小时前
利用 Linux 信号机制(SIGTRAP)实现 Android 下的反调试
android·安全·逆向
CYRUS_STUDIO2 小时前
Android 反调试攻防实战:多重检测手段解析与内核级绕过方案
android·操作系统·逆向
黄林晴6 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我6 小时前
flutter 之真手势冲突处理
android·flutter
法的空间6 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止6 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭7 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech7 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831677 小时前
为何Handler的postDelayed不适合精准定时任务?
android