APK构建过程-命令行编译

官方对APK构建过程的介绍

官方 - 构建流程介绍

apk构建流程-简版.png

典型 Android 应用模块的构建流程,按照以下常规步骤执行:

  1. 编译器将您的源代码转换成 DEX 文件(Dalvik 可执行文件,其中包括在 Android 设备上运行的字节码),并将其他所有内容转换成编译后的资源。
  2. 打包器将 DEX 文件和编译后的资源组合成 APK 或 AAB(具体取决于所选的 build 目标)。 必须先为 APK 或 AAB 签名,然后才能将应用安装到 Android 设备或分发到 Google Play 等商店。
  3. 打包器使用调试或发布密钥库为 APK 或 AAB 签名:
    • 如果您构建的是调试版应用(即专门用来测试和分析的应用),则打包器会使用调试密钥库为应用签名。Android Studio 会自动使用调试密钥库配置新项目。
    • 如果您构建的是打算对外发布的发布版应用,则打包器会使用发布密钥库(您需要进行配置)为应用签名。
    • 在 Android Studio 中为应用签名
  4. 在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,以减少其在设备上运行时所占用的内存。

构建流程结束时,您将获得应用的调试版或发布版 APK/AAB,以用于部署、测试或向外部用户发布。

精通APK构建

APK构建-详版.png

APK的一般生成步骤

  1. 打包资源文件 aapt/aapt2
    • 生成R文件
    • 生成编译后的资源文件
  2. aidl生成java文件 aidl
  3. Java代码生成class文件 javac
  4. class文件生成dex文件 dx/d8/r8
  5. 打包(未签名)apk apkbuilder
  6. 签名apk jarsigner/apksigner
  7. 对齐apk zipalign

注:官方对上述部分命令行工具的使用有介绍:[ 官方指导:命令行工具 ]

1. 打包资源文件 aapt/aapt2

  • aapt/aapt2:ANDROID_SDK/build-tools/

见[ 官方指导:命令行工具-aapt2 ]

aapt package -f -m -J ./gen -S res -M AndroidManifest.xml -I D:\android.jar

-f 如果编译生成的文件已经存在,强制覆盖。
-m 使生成的包的目录存放在-J参数指定的目录
-J 指定生成的R.java 的输出目录路径
-S 指定res文件夹的路径
-I 指定某个版本平台的android.jar文件的路径
-A 指定assert文件夹的路径

aapt 入口为 frameworks/base/tools/aapt/Main.cpp ,其中对 assets文件夹路径、res文件夹路径、AndroidManifest文件等会采取不同的策略。

对assets目录下的资源不进行编译,会被原封不动的打入apk中,也就是说assets不会被压缩。

AndroidManifest.xml会被aapt编译成二进制。

res下的资源,大多会被编译成针对Android平台优化过的二进制文件。对drawable下的png默认会进行压缩处理(raw目录下除外)

资源文件(res/下的文件) ---AAPT---|---> R.java(资源索引表)
                                |---> resource.arsc 资源文件
                                |---> res文件(二进制&非二进制如res/raw和pic保持原样)
// 每个资源ID占4字节
public final class R {
    public static final class anim {
        public static int fade_in = 0x7f010001;
        public static int fade_out = 0x7f010002;
    }
}
// 第一位字节 0x7f 表示 packageID ,用来限定资源的来源。系统资源包是 0x01,SharedLibrary类型资源包是 0x00, 普通App包则是 0x7f; 
// 次一位字节 01 表示 TypeID,用来表示资源类型,如 drawable、layouts、anims、color、menu 等;
// 后2字节 0001/0002 表示 EntryID,指的是每一个资源在对应的 TypeID 中出现的顺序

resources.arsc 是一个App的资源索引表,可以理解为一个map映射表,map的key是 R.java 中的资源ID,而 value 就是其对应的资源所在路径,通过 R.java 文件和 resources.arsc 就可以在代码中找到对应的资源引用。

res/raw和assets的相同点:

  1. 两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。

res/raw和assets的不同点:

  1. res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类,通过文件名访问。
  2. res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹

2. aidl生成java文件 aidl

  • aidl:ANDROID_SDK/build-tools/

3. Java代码生成class文件 javac

  • javac:jdk/
javac -target 1.7 -bootclasspath D:\android-sdk-windows\platforms\android-8\android.jar -d bin src\demo\project\*.java gen\demo\project\R.java

-target <版本>           生成特定 VM 版本的类文件
-bootclasspath <路径>    覆盖引导类文件的位置
-d <目录>                指定存放生成的类文件的位置
-sourcepath <路径>       指定查找输入源文件的位置

Javac 入口为 com.sun.tools.javac.main.JavaCompiler 类,主要逻辑集中在 compile() 和 compile2() 方法中

4. class文件生成dex文件 dx/d8/r8

  • dx:ANDROID_SDK/build-tools/
    • 最老、仅支持编译dex(dexing)
  • d8:ANDROID_SDK/build-tools/(高版本sdk才提供)
    • 较新,用于替代dx,支持编译dex(dexing)与脱糖(desugaring)
  • r8:/Applications/Android Studio.app/Contents/plugins/android/lib/r8.jar
    • 最新,相当于 ProGuard + d8,支持缩减(shrinking)、编译dex(dexing)与脱糖(desugaring)
    • sdk未提供r8.jar,网上也没有下载,需自行下载源码编译jar包,但有点麻烦,可在Android Studio/plugin/目录中找到

注:关于dex生成的发展史,可以参考我的另一篇文章:[通过命令行进行R8混淆]

dx用法
dx --dex --output=D:\ProjectDemo\bin\classes.dex D:\ProjectDemo\bin

--output=<要生成的classes.dex路径> <要处理的class文件的路径>
d8用法

见[ 官方指导:命令行工具-d8 ]

# debug mode
$ java -jar build/libs/d8.jar --output out input.jar
# release mode
$ java -jar build/libs/d8.jar --release --output out input.jar
# example: java -jar build/libs/d8.jar --release --output . --pg-conf ./proguard-project.txt ./input.jar
r8用法
$ java -jar build/libs/r8.jar --release --output out --pg-conf proguard.cfg input.jar

ANDROID_SDK中提供的有些jar包由于没有指定入口类main方法,不能直接通过 -jar 执行,可通过 -cp 显式的指定入口,举例:

java -cp r8.jar com.android.tools.r8.R8 --help

5. 打包(未签名)apk apkbuilder

  • apkbuilder:
    • 较老SDK:ANDROID_SDK/tools/
    • 较新SDK:ANDROID_SDK/tools/lib/sdklib.jar

apkbuilder只是一个脚本,实际上调用的是 ANDROID_SDK/tools/ 下的jar包中的 com.android.build.ApkBuilderMain 类。
这个jar包在低版本SDK中是哪个我没注意看过。而高版本SDK中已经不提供 apkbuilder 了,但 ANDROID_SDK/tools/lib/sdklib.jar 中有ApkBuilderMain类,可以使用(未指定Main方法入口,调用时需显式的指定:java -cp sdklib.jar com.android.build.ApkBuilderMain --help)。

apkbuilder D:\ProjectDemo\bin\projectdemo.apk -v -u -z D:\ProjectDemo\bin\resources.ap_ -f D:\ProjectDemo\bin\classes.dex -rf D:\ProjectDemo\src 

-v Verbose 显示过程信息
-u 创建一个无签名的包
-z 指定apk资源路径
-f 指定dex文件路径
-rf 指定源码路径

6. 签名apk jarsigner/apksigner

  • jarsigner:v1签名,jdk/bin/,参考 [jarsigner指导文档]
  • apksigner:v2签名,ANDROID_SDK/build-tools/
jarsigner用法
jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore mykeystore.key -storepass STORE_PASS MY_APP.apk KEY_ALIAS

-verbose    签名/验证时输出详细信息
-keystore   密钥库位置
-storepass  用于密钥库完整性的口令
-keypass    专用密钥的口令(如果不同)
-signedjar  已签名的 JAR 文件的名称 (第一个apk是签名之后的文件, 第二个apk是需要签名的文件)
apksigner用法

见[ 官方指导:命令行工具-apksigner ]

  • apksigner:ANDROID_SDK/build-tools/
apksigner sign --ks **.keystore --ks-key-alias [别名] --ks-pass pass:[别名密码] --key-pass pass:[证书密码] --out [签名后文件存放路径] [未签名的文件路径]

// 私钥和证书必须提供,有两种方式:
--ks:指定密钥库文件(即.keystore/.jks,私钥和证书被存放其中)
--key 和 --cert:分别指定私钥和证书,私钥必须使用 PKCS #8 格式(.pk8),证书必须使用 X.509 格式(x509.pem)。

7. 对齐apk zipalign

见[ 官方指导:命令行工具-zipalign ]

  • zipalign:ANDROID_SDK/build-tools/
  • 若使用v1签名,zipalign 必须在签名后进行
  • 若使用v2签名,zipalign 必须在签名前进行
// 对齐
zipalign -p -f -v 4 infile.apk outfile.apk
// 确认existing.apk的对齐方式
zipalign -c -v 4 existing.apk

-c  仅检查对齐情况(不会修改文件)。
-f  覆盖现有输出文件。
-h  显示工具帮助。
-p  使未压缩的 .so 文件对齐页面。
-v  详细输出。
-z  使用 Zopfli 重新压缩。

zipalign是一个zip归档文件对齐工具,它将zip(apk也是zip文件)中的所有未压缩文件相对于文件开头对齐(偏移为4字节的整数倍),这样就可以通过内存映射(mmap)访问这些文件,而无需在 RAM 中复制这些数据,既高效又减少了应用的内存使用

CPU在处理内存数据时,并非一次提取一个memory cell,通常是提取一组相邻内存单元。在32-bit machine,CPU一次从内存中读取4个连续的memory cell(4-byte) 。4 byte chunk(4字节流) 为一个读取周期。比如,读取一个int型 数据时,需要一个读取周期(int 占4 byte),读取Double型,则需要2个读取周期。

zipalign对齐图解.png

注:上图是以c代码为例,c语言中 1char = 1byte,而在java中 1char=2byte。

附加:smali工具

baksmali:将dex编译为smali
java -jar baksmali-1.3.2.jar -o classout/ classes.dex
smali:将smali反编译成dex
java -jar smali-1.3.2.jar classout/ -o classes.dex
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容