Android AAPT详解

article/2025/10/28 12:05:27

目录

  • AAPT解释,作用
  • AAPT基本命令
  • AAPT编译资源源码解析
  • AAPT打包和系统不一致的资源ID

AAPT是什么

AAPT - Android Asset Packaging Tool

看全称,就可知道AAPT是Android资源打包工具。�讲这个之前,是有必要简单说下Android是如何构建一个APK的。

 

上图是Google官方发布的一张非常经典的Apk打包流程图。�

流程概述:

  1. 工程的资源文件(res文件夹下的文件),通过AAPT打包成R.java类(资源索引表),以及.arsc资源文件
  2. 如果有aidl,通过aidl工具,打包成java接口类
  3. R.java和aidl.java通过java编译成想要的.class文件。
  4. 源码class文件和第三方jar或者library通过dx工具打包成dex文件。dx工具的主要作用是将java字节码转换成Dalvik字节码,在此过程中会压缩常量池,消除一些冗余信息等。
  5. apkbuilder工具会将所有没有编译的资源,.arsc资源,.dex文件打包到一个完成apk文件中中。
  6. 签名,5中完成apk通过配置的签名文件(debug和release都有),jarsigner工具会对齐签名。得到一个签名后的apk,signed.apk
  7. zipAlign工具对6中的signed.apk进行对齐处理,所谓对齐,主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用主要是为了减少运行时内存的使用。

总结:

  • 输入:res文件夹所有的资源(layout\drawable\string\array等),asset下的资源,AndroidManifest.xml,Android.jar文件
  • 工具: aapt 地址(/your sdk path/build-tools/your build tools version/aapt)
  • 输出:res下的资源都会被编译成一个资源索引文件resource.arsc以及一个R.java类。asset下的资源不会编译,�直接压缩进apk。

AAPT命令详解

按照上面aapt的地址配置好环境变量后,在终端中输入 aapt v 会得到aapt版本信息,如下:

 

再输入 aapt 命令会列出所有的aapt命令集合,下面我们一条条来使用并且分析其作用:

1.aapt l[ist] [-v] [-a] file.{zip,jar,apk}

作用:列出压缩文件(zip,jar,apk)中的目录内容。

例如:

 

    aapt l /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk

结果如下:

 

 

可以看出来不加任何参数,aapt只是简单的罗列压缩文件中每一项的内容。

 

    aapt l -v /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk

结果如下:

 

 

从图中可以看出,加上-v后,输出的内容很详细,并且以列表的形式标识出很多参数,其中表目有:

  • Length:原始文件的长度

  • Date:日期

  • Time:时间

  • Name:名称

  • Method:压缩方法,Deflate及Stored两种,即该Zip目录采用的算法是压缩模式还是存储模式;可以看出resources.arsc、*.png采用压缩模式,而其它采用压缩模式。

  • Ratio:压缩率

  • Size:这个是压缩省掉的大小,即如果压缩率是xx%。那Size是原始长度*(1-xx%)

  • CRC-32:循环冗余校验。这个计算是有特定的算法的。

  • offset:zipfile中偏移量的意思

      aapt l -a /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk 
    

结果如下:

 

 

-a表示会详细输出压缩文件中所有目录的内容,详细到什么程度的,可以看上图,上图截取的只是很小的一部分,这部分是manifest.xml文件的所有数据,可以看出来基本上所有的manifest信息都列了出来。

2.aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]

作用:通过参数配置可以dump apk中各种详细信息。

  • strings 官方解释:

Print the contents of the resource table string pool in the APK

即打印apk中所有string资源表

 

    aapt dump strings /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk

结果:

 

 

不太了解这个结果是什么具体的意思,猜测应该是序列化的string字段。

  • bading 官方解释:

Print the label and icon for the app declared in APK.

 

    aapt dump badging /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk

结果:

 

 

 

查看APK中的配置信息,包括包名,versionCode,versionName,platformBuildVersionName(编译的时候系统添加的字段,相当targetSdkVersionCode的描述)等。同事该命令还列出了manifest.xml的部分信息,包括启动界面,manifest里配置的label,icon等信息。还有分辨率,时区,uses-feature等信息。

  • permissions 官方解释:Print the permissions from the APK

较简单,输出APK中使用到的权限信息。

  • resources 官方解释:

Print the resource table from the APK

 

    aapt dump resources /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk

结果:

 

 

输出了apk中所有的资源信息,从这里也可以看出来aapt打包时也包含了android系统很多资源。并且这里也发现,系统通过标准的aapt构建出来的资源绝大部分的资源id都是0x7f开头的,这个也是和我们在R.java文件中看到的资源编号是对应起来的。

  • configurations 官方解释:Print the configurations in the APK

       aapt dump configurations /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
    

结果:

可以看出该命令输出了apk所有的资源目录,仅仅是目录,里面有语言,分辨率,夜间模式相关的数据。

  • xmltree 官方解释:

Print the compiled xmls in the given assets

 

    aapt d xmltree /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk res/layout/activity_main.xml

结果:

该命令直接反编译除了apk中某一个xml布局文件的组织结构。命令需要两个参数 第一是apk的地址 第二后面是apk中某个编译好的xml的相对路径地址

  • xmlstrings 官方解释:

Print the strings of the given compiled xml assets

 

    aapt d xmlstrings /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk res/layout/activity_main.xml

结果:

从字面解释,输出xml文件中所有的string信息。看结果,实际上并没看出来什么特殊的,也并不是简单的string信息,猜测可能是索引吧。

3.aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml]

 


aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \[-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \[--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \[--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \[--rename-manifest-package PACKAGE] \[--rename-instrumentation-target-package PACKAGE] \[--utf16] [--auto-add-overlay] \[--max-res-version VAL] \[-I base-package [-I base-package ...]] \[-A asset-source-dir]  [-G class-list-file] [-P public-definitions-file] \[-D main-dex-class-list-file] \[-S resource-sources [-S resource-sources ...]] \[-F apk-file] [-J R-file-dir] \[--product product1,product2,...] \[-c CONFIGS] [--preferred-density DENSITY] \[--split CONFIGS [--split CONFIGS]] \[--feature-of package [--feature-after package]] \[raw-files-dir [raw-files-dir] ...] \[--output-text-symbols DIR]

官方解释:

Package the android resources. It will read assets and resources that are supplied with the -M -A -S or raw-files-dir arguments. The -J -P -F and -R options control which files are output.

android 编译资源打包资源文件的命令。

  • -d:包括一个或多个设备资源,由逗号分隔;
  • -f:覆盖现有的文件命令,加上后编译生成直接覆盖目前已经存在的R.java;
  • -m:使生成的包的目录放在-J参数指定的目录;
  • -u:更新现有的包 u = update;
  • -v:详细输出,加上此命令会在控制台输出每一个资源文件信息,R.java生成后还有注释。
  • -x:创建扩展资源ID;
  • -z:需要本地化的资源属性标记定位。
  • -M:AndroidManifest.xml的路径
  • -0:指定一个额外的扩展. apk文件将不会存储压缩
  • -g:制定像素迫使图形的灰度
  • -j:指定包含一个jar或zip文件包,这个命令很特别
  • –debug-mode:指定的是调试模式下的编译资源;
  • –min-sdk-versopm VAL:最小SDK版本 如是7以上 则默认编译资源的格式是 utf-8
  • –target-sdk-version VAL:在androidMainfest中的目标编译SDK版本
  • –app-version VAL:应用程序版本号
  • –app-version-name TEXT:应该程序版本名字;
  • –custom-package VAL:生成R.java到一个不同的包
  • –rename-mainifest-package PACKAGE:修改APK包名的选项;
  • –rename-instrumentation-target-package PACKAGE:重写指定包名的选项;
  • –utf16:资源编码修改为更改默认utf – 16编码;
  • –auto-add-overlay:自动添加资源覆盖
  • –max-res-version:最大资源版本
  • -I:指定的SDK版本中android.jar的路径
  • -A:assert文件夹的路径
  • -G:一个文件输出混淆器选项,后面加文件逗号隔开.
  • -P:指定的输出公共资源,可以指定一个文件 让资源ID输出到那上面;
  • -S:指定资源目录 一般是 res
  • -F:指定把资源输出到 apk文件中
  • -J:指定R.java输出的路径
  • raw-file-dir:附加打包进APK的文件

该命令也是aapt最核心、最复杂的命令。这边我只尝试了一下简单的实践,讲工程的资源编译到一个包里。下面是命令

 


aapt package -f -S /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/res -I /Users/zfxu/Library/Android/sdk/platforms/android-25/android.jar -A /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/assets -M /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/AndroidManifest.xml -F /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/out.apk

输出了一个apk文件,解压以后文件格式如下:

这个apk文件除了没有代码dex,资源都在的。这个是aapt打包的关键步骤之一,还有一个步骤就是把资源文件编译成R.java,供程序调用。命令如下:

 


aapt package -m -J <R.java目录> -S <res目录> -I <android.jar目录>  -M <AndroidManifest.xml目录>

4.aapt r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]

官方解释:

Delete specified files from Zip-compatible archive

就是从一个zip archive文件中删除一个文件。较简单,不做实例了。

5.aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]

官方解释:

Add specified files to Zip-compatible archive.

�即在一个zip包中添加一个一个指定的文件。

6.aapt c[runch] [-v] -S resource-sources ... -C output-folder ...

官方解释:

Do PNG preprocessing on one or several resource folders and store the results in the output folder.

对多个或者单个资源文件夹进行处理,并且将结果保存在输出文件夹中

6.aapt s[ingleCrunch] [-v] -i input-file -o outputfile

官方解释:

Do PNG preprocessing on a single file

预处理一个文件

AAPT源码解析

首先下载Android源码

Android Source

�我这边下载的是Android 6.0的源码

AAPT代码地址:***/frameworks/tools/aapt/目录下。

我们这里以一个命令来跟踪源码的流程,即用aapt是如何构建一个R.java的,命令格式如下:

 


aapt package –m –J <R.java目录> -S <res目录> -I <android.jar目录> -M <AndroidManifest.xml目录>

实践:

 


aapt package -m -J  /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/ -S /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/res/ -I /Users/zfxu/Library/Android/sdk/platforms/android-25/android.jar -M /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/src/main/AndroidManifest.xml

运行该命令后,在配置的R.java目录下 会生成一个自己app包名的目录,里面会生成R.java文件,如下:

 

R.java里会生成这样的索引ID类,都是以0x7f开头

 

public final class R {public static final class attr {}public static final class color {public static final int colorAccent=0x7f040002;public static final int colorPrimary=0x7f040000;public static final int colorPrimaryDark=0x7f040001;}public static final class layout {public static final int activity_main=0x7f030000;}public static final class mipmap {public static final int ic_launcher=0x7f020000;public static final int ic_launcher_round=0x7f020001;}public static final class string {public static final int app_name=0x7f050000;}
}

好,既然我们知道了输入以及输出,那让我们来分析这块的代码。

PS:由于某些函数较长,不会贴出所有的源码

  1. 入口 /frameworks/base/tools/aapt/Main.cpp

 

int main(int argc, char* const argv[]) {***else if (argv[1][0] == 'p')bundle.setCommand(kCommandPackage);***while (argc && argv[0][0] == '-') {//通过case比较,去除命令中所有的参数,并且放进bundle中/* flag(s) found */const char* cp = argv[0] +1;while (*cp != '\0') {***switch (*cp) {case 'M':argc--;argv++;if (!argc) {fprintf(stderr, "ERROR: No argument supplied for '-M' option\n");wantUsage = true;goto bail;}//这个仅仅是把传进来的地址坐下系统路径分割线的转换convertPath(argv[0]);bundle.setAndroidManifestFile(argv[0]);break;}***}}***result = handleCommand(&bundle);***
}

当通过所有的匹配规则后,该函数实际调用是 handleCommand(&bundle)。 至于执行什么命令说白了也是命令指定的,-p 设置的command参数是kCommandPackage。

  1. 分发指令 /frameworks/base/tools/aapt/Main.cpp

 

 int handleCommand(Bundle* bundle){switch (bundle->getCommand()) {case kCommandVersion:      return doVersion(bundle);case kCommandList:         return doList(bundle);case kCommandDump:         return doDump(bundle);case kCommandAdd:          return doAdd(bundle);case kCommandRemove:       return doRemove(bundle);case kCommandPackage:      return doPackage(bundle);case kCommandCrunch:       return doCrunch(bundle);case kCommandSingleCrunch: return doSingleCrunch(bundle);case kCommandDaemon:       return runInDaemonMode(bundle);default:fprintf(stderr, "%s: requested command not yet supported\n", gProgName);return 1;}
}
  1. 处理package指令 /frameworks/base/tools/aapt/Command.cpp

 

int doPackage(Bundle* bundle) {const char* outputAPKFile;int retVal = 1;status_t err;sp<AaptAssets> assets;int N;FILE* fp;String8 dependencyFile;sp<ApkBuilder> builder;sp<WeakResourceFilter> configFilter = new WeakResourceFilter();//见注释3-1err = configFilter->parse(bundle->getConfigurations());if (err != NO_ERROR) {goto bail;}//资源本地化相关的配置,具体什么含义也没有理解清楚if (configFilter->containsPseudo()) {bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);}if (configFilter->containsPseudoBidi()) {bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);}//校验命令中是否传入正确的参数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();// 如果输出文件存在,但是是不合格的,则直接报错结束,如果不存在,则新建空文件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();// 设置res和asset的成员,仅仅是外层new一个对象赋值给AaptAssetsif (bundle->getGenDependencies()) {sp<FilePathStore> resPathStore = new FilePathStore;assets->setFullResPaths(resPathStore);sp<FilePathStore> assetPathStore = new FilePathStore;assets->setFullAssetPaths(assetPathStore);}//调用AaptAssets类的成员函数slurpFromArgs将AndroidManifest.xml文件,目录assets和res下的资源目录和资源文件收录起来保存到AaptAssets中的//成员变量中err = assets->slurpFromArgs(bundle);if (err < 0) {goto bail;}//如果命令中指定需要详细日志输出,这里会打印所有的资源信息if (bundle->getVerbose()) {assets->print(String8());}// Create the ApkBuilder, which will collect the compiled files// to write to the final APK (or sets of APKs if we are building// a Split APK.//new一个ApkBuilder对象,如果需要生成多个apk,则需要将上层的配置写入改对象中builder = new ApkBuilder(configFilter);// If we are generating a Split APK, find out which configurations to split on.if (bundle->getSplitConfigurations().size() > 0) {const Vector<String8>& splitStrs = bundle->getSplitConfigurations();const size_t numSplits = splitStrs.size();for (size_t i = 0; i < numSplits; i++) {std::set<ConfigDescription> configs;if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) {fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string());goto bail;}err = builder->createSplitForConfigs(configs);if (err != NO_ERROR) {goto bail;}}}// If they asked for any fileAs that need to be compiled, do so.//这是最核心的一步,编译资源(res和asset)。这个成功后,下面的步骤就仅仅是写输出文件了if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {err = buildResources(bundle, assets, builder);if (err != 0) {goto bail;}}// At this point we've read everything and processed everything.  From here// on out it's just writing output files.if (SourcePos::hasErrors()) {goto bail;}// Update symbols with information about which ones are needed as Java symbols.assets->applyJavaSymbols();if (SourcePos::hasErrors()) {goto bail;}// If we've been asked to generate a dependency file, do that hereif (bundle->getGenDependencies()) {// If this is the packaging step, generate the dependency file next to// the output apk (e.g. bin/resources.ap_.d)if (outputAPKFile) {dependencyFile = String8(outputAPKFile);// Add the .d extension to the dependency file.dependencyFile.append(".d");} else {// Else if this is the R.java dependency generation step,// generate the dependency file in the R.java package subdirectory// e.g. gen/com/foo/app/R.java.ddependencyFile = String8(bundle->getRClassDir());dependencyFile.appendPath("R.java.d");}// Make sure we have a clean dependency file to start withfp = fopen(dependencyFile, "w");fclose(fp);}// Write out R.java constantsif (!assets->havePrivateSymbols()) {if (bundle->getCustomPackage() == NULL) {// Write the R.java file into the appropriate class directory// e.g. gen/com/foo/app/R.javaerr = writeResourceSymbols(bundle, assets, assets->getPackage(), true,bundle->getBuildSharedLibrary());} else {const String8 customPkg(bundle->getCustomPackage());err = writeResourceSymbols(bundle, assets, customPkg, true,bundle->getBuildSharedLibrary());}if (err < 0) {goto bail;}// If we have library files, we're going to write our R.java file into// the appropriate class directory for those libraries as well.// e.g. gen/com/foo/app/lib/R.javaif (bundle->getExtraPackages() != NULL) {// Split on colonString8 libs(bundle->getExtraPackages());char* packageString = strtok(libs.lockBuffer(libs.length()), ":");while (packageString != NULL) {// Write the R.java file out with the correct package nameerr = writeResourceSymbols(bundle, assets, String8(packageString), true,bundle->getBuildSharedLibrary());if (err < 0) {goto bail;}packageString = strtok(NULL, ":");}libs.unlockBuffer();}} else {err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false);if (err < 0) {goto bail;}err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false);if (err < 0) {goto bail;}}// Write out the ProGuard fileerr = writeProguardFile(bundle, assets);if (err < 0) {goto bail;}// Write the apkif (outputAPKFile) {// Gather all resources and add them to the APK Builder. The builder will then// figure out which Split they belong in.err = addResourcesToBuilder(assets, builder);if (err != NO_ERROR) {goto bail;}const Vector<sp<ApkSplit> >& splits = builder->getSplits();const size_t numSplits = splits.size();for (size_t i = 0; i < numSplits; i++) {const sp<ApkSplit>& split = splits[i];String8 outputPath = buildApkName(String8(outputAPKFile), split);err = writeAPK(bundle, outputPath, split);if (err != NO_ERROR) {fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string());goto bail;}}}// If we've been asked to generate a dependency file, we need to finish up here.// the writeResourceSymbols and writeAPK functions have already written the target// half of the dependency file, now we need to write the prerequisites. (files that// the R.java file or .ap_ file depend on)if (bundle->getGenDependencies()) {// Now that writeResourceSymbols or writeAPK has taken care of writing// the targets to our dependency file, we'll write the prereqsfp = fopen(dependencyFile, "a+");fprintf(fp, " : ");bool includeRaw = (outputAPKFile != NULL);err = writeDependencyPreReqs(bundle, assets, fp, includeRaw);// Also manually add the AndroidManifeset since it's not under res/ or assets/// and therefore was not added to our pathstores during slurpingfprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());fclose(fp);}retVal = 0;
bail:if (SourcePos::hasErrors()) {SourcePos::printErrors(stderr);}return retVal;
}

注释:

  • 3-1:这个过程主要是对资源配置信息进行校验,Android应用程序资源的组织方式有18个维度,包括mcc(移动国家代码)、mnc(移动网络代码)、local(语言区域)等。改代码的主要实现是在 /framewors/base/tools/aapt/AaptConfig.cpp 里的parse方法。解析完成的数据,会丢给WeakResourceFilter类中的一个向量集合成员mConfigs。关于这块的详细解释,可以参考官网或者老罗的博客,地址如下:

    https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources

    Android资源管理框架(Asset Manager)简要介绍和学习计划

  1. 编译res和xml资源 /frameworks/base/tools/aapt/Resource.cpp

ps:改函数较长,截取部分代码分步解析

 

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;}......
}

首先解析manifest文件,调用的是parsePackage函数,解析之前,manifest被封装成一个AaptGroup对象。

 

static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,const sp<AaptGroup>& grp)
{if (grp->getFiles().size() != 1) {fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",grp->getFiles().valueAt(0)->getPrintableSource().string());}sp<AaptFile> file = grp->getFiles().valueAt(0);ResXMLTree block;status_t err = parseXMLResource(file, &block);if (err != NO_ERROR) {return err;}......省略代码return NO_ERROR;
}

没有具体细看里面的代码,说下具体思路,通过传进来的形参AaptGroup拿到具体的AaptFile对象。在调用公共类的parseXmlResource解析xml文件得到具体数据后,存放在对象ResXmlTree中。parseXMLResource函数在类 frameworks/base/tools/aapt/XMLNode.cpp 中。有兴趣的可以自己去读下,这里就不贴了。解析玩manifest.xml后,我们继续buildResources的分析。

 

ResourceTable::PackageType packageType = ResourceTable::App;......省略的代码if (bundle->getBuildSharedLibrary()) {packageType = ResourceTable::SharedLibrary;} else if (bundle->getExtending()) {packageType = ResourceTable::System;} else if (!bundle->getFeatureOfPackage().isEmpty()) {packageType = ResourceTable::AppFeature;}ResourceTable table(bundle, String16(assets->getPackage()), packageType);err = table.addIncludedResources(bundle, assets);if (err != NO_ERROR) {return err;}...省略的代码

这段代码的目的主要是收集当前编译的资源需要依赖的的资源并且存放在ResourceTable这个数据结构中。这边简单介绍一下ResourceTable这个数据结构,首先我们得知道R.java里面的资源标识id的构成,比方说 0x7f040002 其中0x7f表示是packageID,也就是上面的packageType,它是一个命名空间,限定资源的来源,7f表明是当前应用程序的资源,系统的资源是以0x01开头。04 表示TypeID。资源的类型animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。最后四位是EntryID,指的是每一个资源在起对应的TypID中出现的顺序。

 

 

而ResouceTable里面存储的最核心的元素就是这个id的区分。

收集完成当前应用依赖的资源以后,就要编译当前应用自己的资源。这里由于代码太过于复杂,本人也没有完全看懂,就不贴了,逻辑基本上就是通过命令的输出,一个个的编译资源文件和png。然后存储在一个xml文件中,为后面生成R.java文件中做准备。实际上前面也有提到,所有的资源都会存在ResouceTable这个数据结构中,做完编译工作以后,只需要去遍历这个向量表,然后对里面的packageID,typeID,EvtryID进行拼接,就可以得到我们所熟悉的 0x7f040002这种资源ID。ResouceTable的构造函数也可以看出来里面的过程:

 

ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type): mAssetsPackage(assetsPackage), mPackageType(type), mTypeIdOffset(0), mNumLocal(0), mBundle(bundle) {ssize_t packageId = -1;switch (mPackageType) {case App:case AppFeature:packageId = 0x7f;break;case System:packageId = 0x01;break;case SharedLibrary:packageId = 0x00;break;default:assert(0);break;}sp<Package> package = new Package(mAssetsPackage, packageId);mPackages.add(assetsPackage, package);mOrderedPackages.add(package);// Every resource table always has one first entry, the bag attributes.const SourcePos unknown(String8("????"), 0);getType(mAssetsPackage, String16("attr"), unknown);
}
  1. 完成上述的编译资源的工作以后,细心的读者就会发现,对于manifest.xml一直都是读取里面的配置信息,并没有编译,所以最后一步就是把manifest.xml编译成二进制文件。这个就不贴出源码了。

  2. 最后一步,将上述的编译结果输出到R.java和Apk中。其中还会输出混淆文件,java符号表等。

 

...省略代码
// Write out R.java constantsif (!assets->havePrivateSymbols()) {if (bundle->getCustomPackage() == NULL) {// Write the R.java file into the appropriate class directory// e.g. gen/com/foo/app/R.javaerr = writeResourceSymbols(bundle, assets, assets->getPackage(), true,bundle->getBuildSharedLibrary());} else {const String8 customPkg(bundle->getCustomPackage());err = writeResourceSymbols(bundle, assets, customPkg, true,bundle->getBuildSharedLibrary());}if (err < 0) {goto bail;}// If we have library files, we're going to write our R.java file into// the appropriate class directory for those libraries as well.// e.g. gen/com/foo/app/lib/R.javaif (bundle->getExtraPackages() != NULL) {// Split on colonString8 libs(bundle->getExtraPackages());char* packageString = strtok(libs.lockBuffer(libs.length()), ":");while (packageString != NULL) {// Write the R.java file out with the correct package nameerr = writeResourceSymbols(bundle, assets, String8(packageString), true,bundle->getBuildSharedLibrary());if (err < 0) {goto bail;}packageString = strtok(NULL, ":");}libs.unlockBuffer();}} else {err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false);if (err < 0) {goto bail;}err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false);if (err < 0) {goto bail;}}
...省略代码

综上所述,分析完成了了apk资源编译的过程,由于本人c++功底不佳,有的东西也只是靠猜测来完成,基本上能够理清楚大体的逻辑。如果想更详细的内容,可以自己参考源码。网上有篇博客,有点乱,但是很细,可以看下 http://www.cnblogs.com/dyllove98/p/3144950.html

AAPT命令修改,完成修改资源ID

在第三节我们讲AAPT是如何编译资源并且生成R.java文件的,也提到R.java中资源ID的含义,在组件化框架中,由于组件和宿主分开编译,为了防止组件的资源ID和宿主的资源ID冲突,所以就需要修改AAPT源码。基本思路就是每个组件分配一个不一样的ID,宿主的ID是以0x7f开头,组件的ID是0x**开头,这样就避免冲突。

 

可以看出如果不修改AAPT源码重新构建,就会导致组件之间或者组件与宿主之间的ID冲突。所以就会有如下模型:

既然分析AAPT的编译过程,那思路就很清晰了,在命令中添加一个自定义的ID,然后在代码中拿到这个ID,拼接的时候替换上即可。当然这只是最简单的,而实际情况呢,比方说宿主里有一部分资源是其他组件公用的,如何保证这部分资源和id和组件本身的id不会发生冲突呢?又如何在我们工程里自动化这套自定义的aapt从而代替系统标准的aapt呢?下篇博客,我会详细分析。



作者:徐正峰
链接:https://www.jianshu.com/p/8d691b6bf8b4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


http://chatgpt.dhexx.cn/article/9miFf0Wn.shtml

相关文章

41.Android aapt工具

41.Android aapt工具 Android aapt工具 aapt 介绍aapt 环境配置aapt listaapt dumpaapt package其他命令 aapt 介绍 可参考官方文档&#xff1a; http://www.androidcn.net/wiki/index.php/Reference/aapt aapt stands for Android Asset Packaging Tool and is included in …

[Android]aapt使用小结

aapt命令小结 aapt即Android Asset Packaging Tool.本文小结了一下该工具的用法。 1. aapt l[ist] [-v] [-a] file.{zip,jar,apk} List contents of Zip-compatible archive. 1.1 列出压缩文件目录 aapt l <file_path.apk> 参数: -v:会以table的形式输出目录,ta…

[aapt包管理]aapt查询Apk最低支持的Android版本

向电视盒子中安装DSvideo_TV.apk时&#xff0c;报盒子版本过低(16年的盒子比较老旧了)&#xff0c;因此想查一下DSvideo_TV.apk支持的最低Android版本&#xff0c;从而针对性的进行盒子更新。 1、如何获取APK支持的最低Android版本&#xff1f; 第一时间想到的是Android SDK提…

Android中aapt使用详解

aapt是Android sdk中提供的一个小工具&#xff0c;功能十分强大。appt&#xff1a;Android Asset Packaging Tool &#xff0c;在sdk的build-tools下&#xff0c;这个工具可以查看, 创建, 更新ZIP格式的文档附件(zip, jar, apk). 也可将资源文件编译成二进制文件。我们可能没有…

aapt命令详解

aapt是android自动打包工具之一。 环境配置&#xff1a; aapt一般在android sdk的build-tools中对应文件夹里面&#xff0c;将其路径添加到环境变量中&#xff0c;即可在命令行中直接使用aapt命令&#xff1b; aapt参数详解 aapt 命令后可直接查看参数详解 aapt l[ist] [-v…

aapt 命令查看apk包名(aapt命令大全)

1 aapt是什么&#xff1a; aapt即Android Asset Packaging Tool&#xff0c;在SDK的build-tools目录下。该工具可以查看&#xff0c;创建&#xff0c; 更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件&#xff0c;尽管你可能没有直接使用过aapt工具&…

aapt命令的使用

一.背景 1.最初使用aapt是为了做安卓应用的安全性测试。对于普通用户权限的应用我们通常使用的方法是在shell下run-as ,如返回值为run-as: Package ‘’ is not debuggable,我们就认为测试通过。在我们的安卓平台从7.x升为9.x以后&#xff0c;以后我们测试的应用改为系统权限&a…

Word插入公式自动编号+交叉引用

针对于word自带的公式编辑器 一、公式编号 1.插入公式 ‘“alt”“” &#xff0c;弹出插入公式&#xff0c;此处推荐SimpleTex可直接截图识别公式&#xff0c;直接复制进word&#xff0c;亲测好用。 2. 编号 2.1 公式后添加“#”&#xff0c;光标放在"#"后。 2.2…

批量删除引用#批量删除交叉引用

批量删除引用 原文如下&#xff1a; 批量删除引用操作 1&#xff09;ctrH进行替换操作 2&#xff09;特殊格式选中“尾注标记” 3&#xff09;然后被替换的内容就会变^e 4&#xff09;全部替换后如图 批量删除交叉引用 如上图还存在交叉引用没有被删除 更新域之后 替换…

在毕业设计论文Word中交叉引用参考文献

前言&#xff1a;临近毕业季&#xff0c;撰写毕业设计论文是非常关键的一步&#xff0c;往往毕业论文需要引用大量参考文献才能论证自己的研究内容&#xff0c;且参考文献需要交叉引用。 在学位论文中引用参考文献时&#xff0c;应在引出处的右上方用方括号标注阿拉伯数字编排…

论文写作:word连续交叉引用

文章目录 一、问题背景二、步骤 一、问题背景 在写作得时候&#xff0c;使用word的 “交叉引用”功能可以形成超链接格式的标号。但是交叉引用每次只能选择一篇论文&#xff0c;在连续选择多篇论文的时候&#xff0c;就是 “[1][2][3]” 而不是 “[1-3]” 这样的格式。 如图&a…

word交叉引用多个参考文献

1、对参考文献进行编号 2、引用–>交叉引用&#xff0c;插入多个参考文献 3、选中参考文献&#xff0c;右击选择切换域代码&#xff0c;得到&#xff1a; 4、在第一篇文献后添加 \#“[0”&#xff0c;最后一篇添加 \#“0]”&#xff0c;位于中间的文献添加 #“”&#xff0c…

Word交叉引用怎么才能只引用编号项

需求&#xff1a; 如上图&#xff0c;题注标签和引用的标签不一致&#xff0c;但 Word2016 只有【仅标签和编号】选项&#xff0c;没有仅标签选项。 解决方法&#xff1a; &#xff08;1&#xff09;插入题注>勾选【从题注中排除标签】 &#xff08;2&#xff09;在文中插…

word交叉引用的理解

交叉引用&#xff0c;就类似一个超链接&#xff0c;其状态跟自动生成的目录一样&#xff0c;只要按住ctrl键&#xff0c;单击后即可切换到引用的地方。 引用的类型包括对标题的引用&#xff0c;对图Figure 对表Table的引用等。 存在一个问题&#xff1a;自定义的标题&#xff…

Word 参考文献管理 - 交叉引用 + 批量修改上标

Word参考文献管理 - 交叉引用 批量修改上标 引言一、参考文献编号设置二、交叉引用三、域代码修改四、批量修改上标 引言 感觉Word参考文献管理不如Latex方便&#xff0c;但是有些论文必须要用word写… Word管理参考文献的三种方法1 详细总结Word交叉引用的流程如下&#xff…

参考文献的交叉引用

文章目录 插入参考文献寻找参考文献参考文献顺序更新 我们在写论文的时候需要引用参考文献&#xff0c;一般我们使用的是交叉引用&#xff0c;下面我详细的讲述一下如果在参考文献处加入交叉引用。 插入参考文献 寻找参考文献 参考文献顺序更新 像上图 我们发现参考文献的顺序…

word里面做交叉引用办法

1 列举自己的引用文献&#xff0c;右键选中《项目符号和编号》 2 选择想要的格式 圈1两侧加[] 3 引用-交叉引用 4 在需要插入的位置选择对应文献即可 记得一定要选《段落编号》 备注快捷键&#xff1a; 上角标快捷键是shiftctrlj加号 下角标快捷键是ctrlj加号

【参考文献交叉引用教程】

Word2021 参考文献交叉引用教程 0 前言1 格式分类2 格式设置方法2.1 引用单个参考文献2.2 引用两个连续的参考文献2.3 引用三个及以上连续的参考文献2.4 引用三个及以上非连续的参考文献 0 前言 对于多个参考文献引用的&#xff0c;简单来说&#xff0c;就是将引用切换到代码域…

word域代码交叉引用合并

word域代码交叉引用 一. 两个引用的情况二. 多个引用三、注意事项 一. 两个引用的情况 一般插入两个交叉引用之后的效果是这样的[1][5]&#xff0c;如果想变成[1,5]&#xff0c;就要用到域代码&#xff0c;步骤如下&#xff1a; 右键点击“切换域代码” 然后会变成这样&#xf…