Android打包流程

参考博客1:https://www.baidu.com/link?url=KwFAuPYou7QXOdlIewcTJHmus0jrIVt8AAPsFkPzhNyd0JvwuTQPhcHZerIdtEQG&wd=&eqid=fedef4dc001f552c000000035d412c62
参考博客2:https://www.cnblogs.com/xunbu7/p/7345912.html
参考博文3:https://mp.weixin.qq.com/s?__biz=MzI1NjEwMTM4OA==&mid=2651232113&idx=1&sn=02f413999ab0865e23d272e69b9e6196&scene=1&srcid=0831gT4p6M0NFG5HTTeRHTUC#wechat_redirect
官网的构建流程图:
在这里插入图片描述

典型 Android 应用模块的构建流程通常依循下列步骤:

1.编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括 Android 设备上运行的字节码),将所有其他内容转换成已编译资源。

2.APK 打包器将 DEX 文件和已编译资源合并成单个 APK。 不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上。

3.APK 打包器使用调试或发布密钥库签署您的 APK:

    a.如果您构建的是调试版本的应用(即专用于测试和分析的应用),打包器会使用调试密钥库签署您的应用。 Android Studio 自动使用调试密钥库配置新项目。

    b.如果您构建的是打算向外发布的发布版本应用,打包器会使用发布密钥库签署您的应用。 要创建发布密钥库,请阅读在 Android Studio 中签署您的应用。

4.在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时占用的内存。 

官网给出的流程图还是比较抽象的,很多细节都隐藏了,以下是Google官方发布的一张非常经典的Apk打包流程图。接下来会将基于下图的流程进行简单分析。
在这里插入图片描述

1. 打包资源文件

资源文件(res文件夹下的文件)通过 AAPT(Android Asset Packaging Tool)(E:\Documents\Android\sdk\build-tools\25.0.0\aapt.exe)。打包生成R.java类(资源索引表)以及.arsc资源文件。

在这个过程中,项目中的AndroidManifest.xml文件和布局文件XML都会编译,然后生成相应的R.java,另外AndroidManifest.xml会被aapt编译成二进制。

存放在APP的res目录下的资源,该类资源在APP打包前大多会被编译,变成二进制文件,并会为每个该类文件赋予一个resource id。对于该类资源的访问,应用层代码则是通过resource id进行访问的。Android应用在编译过程中aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。

经过aapt生成的R文件占4个字节

public static final int design_appbar_state_list_animator=0x7f020000;

  • 第一位字节0x7f表示packageID,用来限定资源的来源。系统资源包是ox01,SharedLibrary类型资源包是0x00, 普通App包则是0x7f;
  • 次一位字节02表示typeID,用来表示资源类型,如drawable、layouts、anims、color、menu等;
  • 后2字节0000表示EvtryID,指的是每一个资源在对应的TypID中出现的顺序。

aapt生成的.arsc资源文件对应我们将apk解压(apk本质是一个zip压缩包)得到的Resources.arsc,它实际上就是App的资源索引表。简单来说,通过R.java文件与Resources.arsc就可以定位到资源的内存地址。

aapt 编译源码的入口在 frameworks/base/tools/aapt/Main.cpp ,其中对 assert文件夹路径、res文件夹路径、AndroidManifest文件等会采取不同的策略

对asset目录下的资源不进行编译,assets目录下的资源会被原封不动的打入apk中,也就是说assets不会被压缩;aapt会对res下drawable资源进行压缩处理(raw目录下除外)

aapt将文本xml资源文件编译成二进制资源文件的方法buildResources函数在frameworks/base/tools/aapt/Resource.cpp 可以找到,这里着重关注了下overlay特性。

overlay是android中用来处理编译时替换资源的一种方式。比如说我们通过 aapt -S 命令指定了一个res路径res1,这时候我们再使用 -S 命令指定另一个res路径res2,如果res1 和res2 下的values/string.xml 都有对同一个String ID ,最后只会使用前面的(res1)描述。可以理解为overlay会以最先定义的路径作为基准包。

有一种情况,假如我们在res2下定义了一个资源Sting a,但是基准包没有定义它,那么就会报错,这时候就可以需要加入 --auto-add-overlay,把新的资源都添加进去。

以下是aapt源码中会进行buildResources的流程

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
    // First, look for a package file to parse.  This is required to
    // be able to generate the resource information.
    sp<AaptGroup> androidManifestFile =
            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
    if (androidManifestFile == NULL) {
        fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
        return UNKNOWN_ERROR;
    }
    status_t err = parsePackage(bundle, assets, androidManifestFile);
    if (err != NO_ERROR) {
        return err;
    }
...
// apply the overlay files to the base set
if (!applyFileOverlay(bundle, assets, &amp;drawables, "drawable") ||
        !applyFileOverlay(bundle, assets, &amp;layouts, "layout") ||
        !applyFileOverlay(bundle, assets, &amp;anims, "anim") ||
        !applyFileOverlay(bundle, assets, &amp;animators, "animator") ||
        !applyFileOverlay(bundle, assets, &amp;interpolators, "interpolator") ||
        !applyFileOverlay(bundle, assets, &amp;transitions, "transition") ||
        !applyFileOverlay(bundle, assets, &amp;xmls, "xml") ||
        !applyFileOverlay(bundle, assets, &amp;raws, "raw") ||
        !applyFileOverlay(bundle, assets, &amp;colors, "color") ||
        !applyFileOverlay(bundle, assets, &amp;menus, "menu") ||
        !applyFileOverlay(bundle, assets, &amp;fonts, "font") ||
        !applyFileOverlay(bundle, assets, &amp;mipmaps, "mipmap")) {
    return UNKNOWN_ERROR;
}
...
//preProcess &amp; makeFileResources
...

// compile resources
// Finally, we can now we can compile XML files

}

在Gradle中可以通过aaptOptions的DSL来对aapt进行配置,在 Android.mk语法中则有LOCAL_AAPT_FLAGS配置项

这里只是对aapt流程简单的说明了下,具体的细节还是蛮多的,从xml解析到具体类型的编译流程、到二进制Manifest生成等细节都未展开,由于本人c++能力有限,很多东西也没看懂,也就不误人子弟了。

2. 处理 aidl files

如果有aidl文件,会通过aidl工具(源碼位于system/tools/aidl)。打包成java接口类

这一过程中使用到的工具是aidl(Android Interface Definition Language),即Android接口描述语言(E:\Documents\Android\sdk\build-tools\25.0.0\aidl.exe)。

aidl工具解析接口定义文件然后生成相应的Java代码接口供程序调用。如果在项目没有使用到aidl文件,则可以跳过这一步。

AIDL(Android Interface Definition Language),是Android接口定义语言。目的是为了方便实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。它的本质是对Binder通信的封装。

3. 编译(Compilers)

R.java+工程源码(项目中所有的Java代码)+aidl.java   通过javac生成   .class文件。

生成的class文件位于工程中的bin/classes目录下。

Javac 编译过程大致可以分为3个阶段

  • 解析与填充符号表过程

    解析的步骤包括词法分析与语法分析两个过程

    词法分析是将源代码的字符串流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记是编译过程的最小元素。关键字、变量名、运算符等都可以成为标记

    语法分析是根据 Token 序列构造抽象语法树的过程,抽象语法树是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表程序中的一个语法结构,如包、类型、修饰符、接口等

  • 插入式注解处理器的注解处理过程

    插入式注解处理器,可以读取、修改、添加抽象语法树中的任意元素。Android中的APT(Annotation Processing Tool)就是在这个阶段工作的。

  • 语义分析与字节码生成过程

    语法分析后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序抽象,但是无法保证源程序是符合逻辑的。而语义分析主要是对结构正确的进行上下文有关性质的审查。语义分析一般要经历标注检查、数据及控制流分析、解语法糖等过程,然后才会走到javac编译的最后一个阶段:字节码生成。大致流程如下:

标注检查 -> 数据及控制流分析 -> 解语法糖 -> 字节码生成

Javac 编译动作的入口是 com.sun.tools.javac.main.JavaCompiler类,主要逻辑集中在 compile()和 compile2()方法中,感兴趣的可以去看看

4. dex(生成dex文件)

源码.class文件和第三方jar或者library通过dx工具打包成dex文件。

dx工具生成可供Android系统Dalvik虚拟机执行的classes.dex文件,该工具位于(E:\Documents\Android\sdk\build-tools\25.0.0\dx.bat)。

任何第三方的libraries和.class文件都会被转换成.dex文件。dx工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等

上面生成的 .class 文件虽然已经可以在 JVM 环境中运行,但是如果要在 Android 运行时环境中执行还需要特殊的处理,那就是 dx 处理,它会对 .class 文件进行翻译、重构、解释、压缩等操作。

关于dex的操作,我们了解最多的可能就是 Tinker 的热修复方案了,Tinker 的基本思想是利用双亲委派原则,将patch的相关dex放在数组前端,保证classloader先到patch中查找加载。如果想要对细节更一步了解,如如何保证资源id不变,资源Diff是怎么做的,对dex文件格式有所了解是必不可少的。关于dex的文件格式,官网有详细介绍,这里就不做说明了。

AndroidStudio有提供 proguard、D8、R8等工具来处理这一流程。Android 还会针对 Dalvik 虚拟机和 Art 虚拟机对dex进行优化

  • dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。

  • dex2oat 是对 dex 文件的 AOT 提前编译操作,其需要一个 dex 文件,然后对其进行编译,结果是一个本地可执行的 ELF 文件,可以直接被本地处理器执行。

5. apkbuilder(生成未签名apk)

apkbuilder工具会将所有没有编译的资源、.arsc资源、.dex文件打包到一个完成apk文件中

所有没有编译的资源,如images、assets目录下资源(该类文件是一些原始文件,APP打包时并不会对其进行编译,而是直接打包到APP中,对于这一类资源文件的访问,应用层代码需要通过文件名对其进行访问);编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。

打包的工具apkbuilder位于 android-sdk/tools目录下。apkbuilder为一个脚本文件,实际调用的是(E:\Documents\Android\sdk\tools\lib)文件中的com.android.sdklib.build.ApkbuilderMain类。

6. Jarsigner(签名)

jarsigner工具会对未签名的apk验证签名。得到一个签名后的apk(signed.apk)

可以通过在命令行中输入jarsigner来获取详情信息,如果沒有特殊需求,使用下面命令即可完成签名

jarsigner -verbose -keystore [私钥存放路径] -signedjar [签名后文件存放路径] [未签名的文件路径] [您的证书名称]

一旦APK文件生成,它必须被签名才能被安装在设备上。

在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试,在Eclipse或者Android Studio中直接run以后跑在手机上的就是使用的debug.keystore。

另一种就是用于发布正式版本的keystore。

7. zipalign(对齐)

zipAlign工具对6中的signed.apk进行对齐处理

如果你发布的apk是正式版的话,就必须对APK进行对齐处理,用到的工具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.0\zipalign.exe) 对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。

所谓对齐,主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用主要是为了减少运行时内存的使用。

工具列表

名称功能介绍路径
aaptAndroid资源打包工具${ANDROID_SDK_HOME}/platform-tools/appt
aidlAndroid接口描述语言转化为.java文件的工具${ANDROID_SDK_HOME}/platform-tools/aidl
javacJava Compiler${JDK_HOME}/javac
dex转化.class文件为Davik VM能识别的.dex文件${ANDROID_SDK_HOME}/platform-tools/dx
apkbuilder生成apk包${ANDROID_SDK_HOME}/tools/opkbuilder
jarsigner.jar文件的签名工具${JDK_HOME}/jarsigner
zipalign字节码对齐工具${ANDROID_SDK_HOME}/tools/zipalign
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

隔壁de小刘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值