Android aapt 生成R.java和package.apk原理解析

本文基于AOSP-7.1.1-R9源码分析,源码可以参见frameworks/base/+/android-7.1.1_r9

Android Apk 编译原理解析的分析过程中,可以看到,为了生成最终的apk,在资源文件的编译生成过程中,会两次使用到aapt命令。

  • 生成R.java,编译系统通过acp命令将这个文件复制一份变成R.stamp。
  • 生成中间文件package.apk。编译系统为了生成Split.apk,aapt会把相关资源文件打包生成一个中间文件package.apk,位于通用的路径out/target/product/generic_x86_64/obj/APPS/Split_intermediates/,然后经过签名优化之后,直接拷贝生成我们最终的apk,out/target/product/generic_x86_64/data/app/Split/Split.apk。如果定义了分包功能,那么会同时生成对应的小包,比如中间文件package_mdpi-v4.apk和最终目标Split_mdpi-v4.apk。

实例选择frameworks/base/tests/Split/,正是因为Android.mk里面定义了LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4,方便讲解Split功能,分包之后总共会生成5个apk。

接下来我们将分析以上两个文件生成的原理。

编译参数

对于以上两个文件生成的编译依赖,请参考Android Apk 编译原理解析。本文不在讲解。

当我们执行mmm frameworks/base/tests/Split/编译Split的时候。对于aapt来说有如下的参数传递。

  • 生成R.java时,编译系统解析传给aapt的参数如下:
out/host/linux-x86/bin/aapt   package   --split   mdpi-v4   --split   hdpi-v4   --split   xhdpi-v4   --split   xxhdpi-v4   --pseudo-localize   -m   -J   out/target/common/obj/APPS/Split_intermediates/src   -M   frameworks/base/tests/Split/AndroidManifest.xml   -P   out/target/common/obj/APPS/Split_intermediates/public_resources.xml   -S   frameworks/base/tests/Split/res   -A   frameworks/base/tests/Split/assets   -I   out/target/common/obj/APPS/framework-res_intermediates/package-export.apk   -G   out/target/common/obj/APPS/Split_intermediates/proguard_options   --min-sdk-version   25   --target-sdk-version   25   --version-code   25   --version-name   7.1.1   --skip-symbols-without-default-localization
  • 生成Package.apk时,编译系统传递给aapt的参数如下:
out/host/linux-x86/bin/aapt   package   -u   --split   mdpi-v4   --split   hdpi-v4   --split   xhdpi-v4   --split   xxhdpi-v4   --pseudo-localize   -c   en_US,en_US,cs_CZ,da_DK,de_AT,de_CH,de_DE,de_LI,el_GR,en_AU,en_CA,en_GB,en_NZ,en_SG,eo_EU,es_ES,fr_CA,fr_CH,fr_BE,fr_FR,it_CH,it_IT,ja_JP,ko_KR,nb_NO,nl_BE,nl_NL,pl_PL,pt_PT,ru_RU,sv_SE,tr_TR,zh_CN,zh_HK,zh_TW,am_ET,hi_IN,en_US,en_AU,en_IN,fr_FR,it_IT,es_ES,et_EE,de_DE,nl_NL,cs_CZ,pl_PL,ja_JP,zh_TW,zh_CN,zh_HK,ru_RU,ko_KR,nb_NO,es_US,da_DK,el_GR,tr_TR,pt_PT,pt_BR,sv_SE,bg_BG,ca_ES,en_GB,fi_FI,hi_IN,hr_HR,hu_HU,in_ID,iw_IL,lt_LT,lv_LV,ro_RO,sk_SK,sl_SI,sr_RS,uk_UA,vi_VN,tl_PH,ar_EG,fa_IR,th_TH,sw_TZ,ms_MY,af_ZA,zu_ZA,am_ET,en_XA,ar_XB,fr_CA,km_KH,lo_LA,ne_NP,si_LK,mn_MN,hy_AM,az_AZ,ka_GE,my_MM,mr_IN,ml_IN,is_IS,mk_MK,ky_KG,eu_ES,gl_ES,bn_BD,ta_IN,kn_IN,te_IN,uz_UZ,ur_PK,kk_KZ,sq_AL,gu_IN,pa_IN,be_BY,bs_BA   -M   frameworks/base/tests/Split/AndroidManifest.xml   -S   frameworks/base/tests/Split/res   -A   frameworks/base/tests/Split/assets   -I   out/target/common/obj/APPS/framework-res_intermediates/package-export.apk   --min-sdk-version   25   --target-sdk-version   25   --product   emulator   --version-code   25   --version-name   7.1.1   --skip-symbols-without-default-localization   -F   out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk  

为了快速了解aapt的相关功能,可以查看aapt的帮助文档,在终端下执行aapt –help。aapt的源码位于frameworks/base/tools/aapt,里面有一个aapt2的文件夹,aapt2是新版的aapt,Google在持续优化之中,aapt2可以编译aar资源文件。当所有的参数传递给aapt之后,直接执行aapt源码Main.cpp的main函数。

解析所有的参数

如前面所述,aapt的入口类Main.cpp里面会解析当前传入的所有参数,在aapt的源码中,专门有一个Bundle类用来设置解析之后的所有参数。实例中我们传递的参数是argv[1][0] == 'p',表示我们进行的是打包的动作,会设置bundle.setCommand(kCommandPackage),等参数解析完成之后在handleCommand函数里面会进行打包的动作。

doPackage

在doPackage里面会生成R.java和package.apk,整个过程总结大致分为如下步骤:

Created with Raphaël 2.1.0 doPackage 解析语言配置 收集所有文件 处理SplitApk 编译资源 写R.java文件 生成package.apk

接下来我们一一进行展开分析:

解析语言配置

a. 解析当前编译的apk需要的所有语言,对应-c参数,如en_US,en_US,cs_CZ,da_DK,de_AT。这一步里面除了正常的语言参数之外,还会判断是否传入了en_XA和ar_XB,分别表示伪本地化语言,en_XA表示从左向右的伪语言,ar_XB表示从右向左的语言伪语言。这两种语言主要作用:方便测试翻译成从左向右和从右向左的语言之后,可能发生的UI以及Layout问题。详情可以参考: Test Your App with Pseudolocales
b. 判断当前aapt传入的参数个数必须大于0,否则无效。
c. 判断-F 后面的参数对应的文件格式必须正确。-F out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk必须是常规的文件类型。
代码片段:

@Command.cpp

    // -c en_XA or/and ar_XB means do pseudolocalization
    sp<WeakResourceFilter> configFilter = new WeakResourceFilter();
    err = configFilter->parse(bundle->getConfigurations()); //[a]
    if (err != NO_ERROR) {
        goto bail;
    }
    if (configFilter->containsPseudo()) {
        bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
    }
    if (configFilter->containsPseudoBidi()) {
        bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
    }
    //[b]
    N = bundle->getFileSpecCount();
    if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0
            && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) {
        fprintf(stderr, "ERROR: no input files\n");
        goto bail;
    }

    outputAPKFile = bundle->getOutputAPKFile();

    // Make sure the filenames provided exist and are of the appropriate type.
    if (outputAPKFile) {
        FileType type;
        type = getFileType(outputAPKFile);
        if (type != kFileTypeNonexistent && type != kFileTypeRegular) {
            fprintf(stderr,
                "ERROR: output file '%s' exists but is not regular file\n",
                outputAPKFile);
            goto bail;
        }
    }

    // Load the assets.
    assets = new AaptAssets();

收集所有文件

在aapt的源码里面定义了三个类来表示资源文件、目录、和配置。

aaptFile

对于每个资源文件来说,aapt会用aaptFile来表示。aaptFile由sourceFile(文件名)、groupEntry(当前的config配置信息,如xhdpi-v4)、resType(资源类型)三部分组成,下面用表格来表示一下对应的关系示意图:

sourceFile groupEntry resType
frameworks/base/tests/Split/AndroidManifest.xml
frameworks/base/tests/Split/assets/blah.txt
frameworks/base/tests/Split/assets/statement.xml
frameworks/base/tests/Split/res/mipmap-xhdpi/ic_app.png
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值