• 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文件夹的路径
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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保持原样)
    
    • 1
    • 2
    • 3
    // 每个资源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 中出现的顺序
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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 <路径>       指定查找输入源文件的位置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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文件的路径>
    
    • 1
    • 2
    • 3
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    r8用法
    $ java -jar build/libs/r8.jar --release --output out --pg-conf proguard.cfg input.jar
    
    • 1

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

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

    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 指定源码路径
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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是需要签名的文件)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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)。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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 重新压缩。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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对齐图解

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

    附加:smali工具

    baksmali:将dex编译为smali
    java -jar baksmali-1.3.2.jar -o classout/ classes.dex
    
    • 1
    smali:将smali反编译成dex
    java -jar smali-1.3.2.jar classout/ -o classes.dex
    
    • 1
  • 相关阅读:
    thinkphp8路由
    基于FPGA的VGA图像显示
    Java开发-WebSocket
    详解矩阵求导法则
    JavaScript事件执行机制
    YOLOv5 分类模型 OpenCV和PyTorch两者实现预处理的差异
    【真送礼物】1 分钟 Serverless 极速部署盲盒平台,自己部署自己抽!
    rails 常量自动加载和重新加载机制
    若依框架的使用
    C语言c89(c90)的所有的32个关键字分类
  • 原文地址:https://blog.csdn.net/lovelease/article/details/128067031