解决方案

Android 打包流程之aapt打包资源文件

seo靠我 2023-09-25 03:21:19

上一篇:Android打包流程之资源管理

Android应用最终是以apk的形式放在手机上安装并运行的,而负责将资源文件和代码进行打包的工具就叫appt,全称Android Asset PackaginSEO靠我g Tool,翻译过来就是Android资源打包工具,是Android打包流程中不可或缺的一环。虽然build-tools中都会有一个aapt.exe负责打包apk,但底层还是通过执行aapt命令的方SEO靠我式来进行操作,所以这里需要了解一下aapt的相关命令,有助于更好的理解打包的流程。

Android打包流程简述

先来看张官方的打包流程图:

首先将资源文件(res目录下)通过aapt工具打包成R.java类SEO靠我(资源索引)和.arsc资源文件。倘若工程中又aidl,则通过aidl工具将aidl打包成java接口类。R.java、aidl生成的java文件和项目中的源代码混合编译成.class文件class文SEO靠我件和第三方jar或者library通过dx工具打包成dex文件。dx工具的主要作用是将java字节码转换成Dalvik字节码,在此过程中会压缩常量池,消除一些冗余信息等。通过apkbuilder工具将SEO靠我所有没有编译的资源,.arsc资源,.dex文件打包到一个完成apk文件中。生成的apk通过Jarsinger工具,利用relese或debug下配置的keystore文件进行签名,得到签名后的apkSEO靠我文件。通过zipAlign工具对签名后的apk进行对齐处理。即将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。减少运行时内存的使用。

这就是AndSEO靠我roid打包成apk的流程,而资源的打包则涉及到许多aapt命令。

AAPT命令详解

1、aapt l[ist] [-v] [-a] file.{zip,jar,apk}列出(zip、jar、apk)等类SEO靠我型压缩包中的所有文件列表,示例:

aapt list app-debug.apk#list可以简写为l,如下: aapt l app-debug.apk

列出app-debug.apk中所SEO靠我有的文件列表:

打印出来的内容很长,在命令行中不方便观察,我们可以将输出的内容存放到.txt文件中便于查看。示例:

aapt list app-debug.apk>a.txt

把输出的内容存入a.txt文件SEO靠我中。

aapt list后面还可以加-v和-a。例如加-v参数: aapt list -v app-debug.apk>a.txt

也会列出apk中所有的文件列表,只不过更加详细:

其中各字段代表的含义如下:SEO靠我

Length:文件的长度。

Method:数据压缩算法,有Deflate和Stored两种。知道是用来压缩文件的就行。

Ratio:压缩率。

Size:文件压缩后节省的大小。跟压缩率有关。Size=(1-压SEO靠我缩率)*Length。

Date:日期。

Time:时间。

CRC-32:循环冗余校验,是一种加密算法。

Name:文件名称。

aapt list后面加-a参数:

aapt list -a app-debug.aSEO靠我pk>a.txt

列出apk中所有的文件列表、资源id、各限定符下资源文件对应的id、AndroidManifest文件内容。

看下右边的滚动条,发现这个输出的内容不要太多,不要太详细。最底下列出了AndSEO靠我roid Manifest文件的内容。

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

通过参SEO靠我数配置列出apk中各种详细信息,例如apk的权限、字符、资源等等。

主要有以下几种参数:

strings

列出apk中的所有字符资源(包括不同语言限定符):示例: aapt dump strSEO靠我ings app-debug.apk>a.txt

结果:

这里有乱码,有其它国家语言符号。大家可以自行试一试。

badging

Print the label and icon for the app decSEO靠我lared in APK。实际内容不止label和icon。还有包名、versionCode、versionName、compileSdkVersion、targetSdkVersion等等信息,这里SEO靠我截取一小部分:

permissions

列出apk所用到的所有权限。示例: aapt dump permissions app-debug.apk

结果会列出apk所需要的所有权限,这里不贴图了。

resourSEO靠我ces。

输出apk中所有的资源信息,包括用户添加的资源、不同限定符下的系统资源等等,示例: aapt dump resources app-debug.apk>a.txt

结果:

confSEO靠我igurations

列出apk中所有的资源目录,注意,只是目录,不包含任何文件内容,资源目录对应不同的限定符。 aapt dump configurations app-debug.aSEO靠我pk

关于限定符这篇文章有写到:限定符,结果如下:

5.xmltree

打印出指定xml文件的文档树结构。例如打印出项目中res/layout/activity_main.xml的树形结构,则可以这样写:aSEO靠我apt d xmltree app-debug.apk res/layout/activity_main.xml

打印结果如下:

从上到下列出了activity_main.xml的文档树结构。

6.xmlsSEO靠我trings

列出给出的xml文件中所包含的控件名、控件的属性名和属性值等等。示例:aapt d xmlstrings app-debug.apk res/layout/activity_main.xmSEO靠我l

结果:

3、aapt p[ackage] 负责打包资源文件的命令,这个命令是aapt中最核心、最复杂的命令:

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

好吧,来看下每个参数啥意义:

-d:包含一个或多个设备资源,由逗号分割。 -f:强制覆盖现有文件。 -m:生成包的目录在-j参SEO靠我数指定的目录。 -u:更新现有的包。 -v:详细输出,加上此命令会在控制台输出每一个资源文件信息,R.java生成后还有注释。 -x:创建拓展资源id。SEO靠我 -z:需要本地化的资源属性标记定位。 -M:指定AndroidManifest文件的路径。 -0:指定一个附加扩展名,对于该扩展名,此类文件将不会压缩SEO靠我存储在.apk中。空字符串表示不压缩所有文件。 -g:强制图片灰度,灰度值默认为0。 -jar:指定要包含的类的jar或zip文件。 --debug-mSEO靠我ode:设置调试模式,即在AndroidManifest中加入 android:debuggable="true" --min-sdk-version VAL:指定最小SDK版本 如是SEO靠我7以上 则默认编译资源的格式是 utf-8 --target-sdk-version VAL:在AndroidManifest中指定目标编译版本sdk。 --app-vSEO靠我ersion VAL:指定app的版本号。 --app-version-name TEXT:指定app的版本名。 --custom-package VAL:生成R.jaSEO靠我va到不同的包。 --rename-manifest-package PACKAGE:修改manifest中的应用包名。 --rename-instrumentatioSEO靠我n-target-package PACKAGE:重写指定包名的选项 --utf16:将资源的默认编码更改为UTF-16。在api在7或者更高时有用,资源的默认编码是utf-8。 SEO靠我 --auto-add-overlay:自动添加资源覆盖。 --max-res-version VAL:最大资源版本,忽略高于给定值的版本化资源目录。 -I SEO靠我base-package:指定的SDK版本中android.jar的路径。 -A asset-source-dir:指定Assets文件夹路径。 -G class-liSEO靠我st-file:指定用于输出混淆选项的文件。 -P public-definitions-file:指定的输出公共资源,可以指定一个文件 让资源ID输出到上面。 -D SEO靠我main-dex-class-list-file:指定主DEX的混淆选项输出的文件。 -S resource-sources:指定资源目录,一般是res。 -F apkSEO靠我-file:指定把资源输出到 apk文件中。 -J R-file-dir指定R.java的输出路径。 -c CONFIGS:指定资源有哪些限定符,中间以逗号分割,如enSEO靠我、port、land、en_US。 --preferred-density DENSITY:指定设备的屏幕密度,不符合此密度的资源将会被删除。 --split CONFSEO靠我IGS:分包构建apk,可以和主apk一起加载。 --output-text-symbols DIR:在指定的文件夹下生成一个包含R.java中的资源标识符的文本文件。

这命令复杂的分分SEO靠我钟让人抓狂-。-

但其实只要知道这个命令是做什么的就好,它就是用来将项目中的所有资源文件打包,经过我的实践,其最小执行单元如下:aapt package -S [res文件夹路径] -M [AndroiSEO靠我dManifest.xml文件路径] -I [sdk中android.jar的路径] -F [xxx.apk]

意思就是最起码要指定这四个值,才能执行aapt package命令。于是我这里执行了一个命SEO靠我令。

aapt package -S res -M E:\AndroidProjects\Dagger2\app\src\main\AndroidManifest.xml -I D:\Android\aSEO靠我ndroid-sdk-windows\platforms\android-28\android.jar -F out.apk

结果:

然后就报了这个错误,说是资源找不到,可用as打开工程,这些资源明明都能SEO靠我找到。一度陷入绝望,后来实在没办法,把这些资源删了,AndroidManifest.xml文件中删除这一行代码:android:theme="@style/TheTheme",于是才可以执行成功。至于SEO靠我加上这三个资源、指定主题为什么会报错,我在网上找了许多资料都没有找到这个问题的症结所在,若是有哪位大神知道这个问题的解决办法,还请在底下留言告诉我,不胜感激~我真的很想知道他喵的到底是为什么-。-

好了SEO靠我,至于打包后就是一个out.apk压缩文件,我们解压后,目录如下:

res文件夹里面是我们的资源目录:

至于resources.arsc就是资源打成的包了,将资源文件打包成.arsc的文件,就是我们上面流SEO靠我程图的第一步。以后打包apk的时候会将这个文件也一同打包进去。还有一个AndroidManifest.xml,打开后一堆看不懂的:

这里的文件内容做了处理,防止别人窥探到其中的代码。至于其它可以接的参数SEO靠我,这里只演示一个:

--rename-manifest-package PACKAGE:修改manifest中的应用包名。

好吧,然后我们在原来的命令上接入该参数:

aapt package -S res SEO靠我-M E:\AndroidProjects\Dagger2\app\src\main\AndroidManifest.xml -I D:\Android\android-sdk-windows\plaSEO靠我tforms\android-28\android.jar -F out.apk --rename-manifest-package com.aapt.demo

在刚才命令的最后面加入–rename-mSEO靠我anifest-package com.aapt.demo,把AndroidManifest.xml中的包名改成com.aapt.demo,打包完成后解压并查看AndroidManifest文件:

虽然SEO靠我大部分是乱码,可是我们还是能看到包名已经改成了com.aapt.demo。至于接其它的参数,笔者这里没有一个个试,其实常用的就那些,其他的都是辅助参数。

4、aapt r[emove] [-v] filSEO靠我e.{zip,jar,apk} file1 [file2 …] 用于移除打包好的apk中的文件。例如移除打包好的apk中的AndroidManifest.xml文件:

aapt r out.apk AndSEO靠我roidManifest.xml

这里移除了out.apk中的AndroidManifest.xml文件,然后执行如下命令:

aapt d xmmtree out.apk AndroidManifest.SEO靠我xml

然后会出现如下错误:

提示该文件找不到,可见确实文件是被删除了。可是这里奇怪的是,解压apk,发现AndroidManifest.xml文件依旧在,但执行命令的时候就提示找不到。什么原因,暂时还不SEO靠我知道,有知道的可以告诉我。

5、 aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 …]添加文件到打包好的apk中。示例:将a.txt、b.txt添加到打包SEO靠我好的apk中:

aapt a out.apk a.txt b.txt

多个文件中间以空格分割就好,然后看下解压apk,看其中的目录:

将a.txt和b.txt成功加入到apk中了。

6、 aapt c[runSEO靠我ch] [-v] -S resource-sources … -C output-folder …做PNG文件的预处理,并将结果存储到一个文件夹中。示例,把res目录下的图片预处理,并存储到任意路径pSEO靠我ictures中:

aapt c -S res -C E:\AndroidProjects\Dagger2\app\src\main\pictures

执行后结果如下:

表示执行成功,然后我们看下pictuSEO靠我res路径中的文件:

好吧,没什么可说的。

7、aapt s[ingleCrunch] [-v] -i input-file -o outputfile 对单个PNG文件进行预处理,并输出到指定文件:

aapSEO靠我t s -v -i[需要处理的图片文件路径] -o[处理完成后存储的图片文件路径]

示例:

aapt s -v -i E:\AndroidProjects\Dagger2\app\src\main\resSEO靠我\mipmap-hdpi\ic_launcher.png -o E:\AndroidProjects\Dagger2\app\src\main\pictures\a.png

这里预先在pictures文SEO靠我件夹中新建a.txt文件,然后把后缀名改成.png,这时候a.png是无法打开的,执行此命令后,a.png就可以打开了吗,显示的图像就是res中的hdpi目录下的ic_launcher.png。

8、 SEO靠我aapt v[ersion] 没什么好说的,显示aapt的版本

aapt的命令差不多就这么几种。在命令行中执行aapt命令,就可以查看aapt详细的用法。有兴趣的小伙伴可以自行试一试哈~

AAPT源码分析

SEO靠我于源码,这里只是看一个脉络。需要下载Android系统源码并解压,下载地址:各版本系统源码下载。我这里下载的是6.0的源码。关于aapt部分的源码在frameworks\base\tools\aaptSEO靠我文件夹下。其入口在Main.cpp中main方法中。main方法共有近500行,这里只贴一部分来分析:

int main(int argc, char* const argv[]) {cSEO靠我har *prog = argv[0];//Bundle对象用来存储输入的操作类型和相关的参数。Bundle bundle;bool wantUsage = false;int result = 1;SEO靠我 // pessimistically assume an error.int tolerance = 0;/* default to compression */bundle.setCompressSEO靠我ionMethod(ZipEntry::kCompressDeflated);if (argc < 2) {wantUsage = true;goto bail;}....//argv[] 一行aapSEO靠我t命令被分割成字符串数组。//这边判断该数组的第二个元素的第1个字符,如aapt package,argv[1][0]获取到的就是p。else if (argv[1][0] == p)//设置执行类型SEO靠我为打包。bundle.setCommand(kCommandPackage);....argc -= 2;argv += 2;/** Pull out flags. We support "-fv" SEO靠我and "-f -v".*/while (argc && argv[0][0] == -) {/* flag(s) found */const char* cp = argv[0] +1;while SEO靠我(*cp != \0) {switch (*cp) {......//收集appt命令输入的参数,这些参数以"-"开头。case -:if (strcmp(cp, "-debug-mode") == SEO靠我0) {//例如这里就获取到了-debug-mode,然后设置Bundle的debugMode为true。bundle.setDebugMode(true);} .....cp += strlen(cSEO靠我p) - 1;break;default:fprintf(stderr, "ERROR: Unknown flag -%c\n", *cp);wantUsage = true;goto bail;}cSEO靠我p++;}argc--;argv++;}/** Were past the flags. The rest all goes straight in.*/bundle.setFileSpec(argvSEO靠我, argc);//执行最终的命令,并得到结果result = handleCommand(&bundle);bail:if (wantUsage) {usage();result = 2;}//prSEO靠我intf("--> returning %d\n", result);return result; }

源码中比较详细,这里举个例子。整行aapt命令会被分割成字符串数组。然后去字符串数SEO靠我组的第二个的第一个字符,如aapt package…,得到p,代表这是aapt打包命令。Bundle对象用以存储输入的操作类型和相关的参数,供后面执行命令详细操作时使用。当然,命令解析错误时,会通过gSEO靠我oto跳转到bail代码块,比如:

default:fprintf(stderr, "ERROR: Unknown flag -%c\n", *cp);wantUsage = true;goto baiSEO靠我l;

bail代码块:

bail:if (wantUsage) {usage();result = 2;}//printf("--> returning %d\n", result);return resSEO靠我ult; }

好吧,会调用usage()函数,这个函数会打印出aapt的用法文档。

void usage(void) {fprintf(stderr, "Android ASEO靠我sset Packaging Tool\n\n");fprintf(stderr, "Usage:\n");fprintf(stderr," %s l[ist] [-v] [-a] file.{zipSEO靠我,jar,apk}\n"" List contents of Zip-compatible archive.\n\n", gProgName);fprintf(stderr," %s d[ump] [SEO靠我--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]\n"" strings Print the contents oSEO靠我f the resource table string pool in the APK.\n"" badging Print the label and icon for the app declarSEO靠我ed in APK.\n"" permissions Print the permissions from the APK.\n"" resources Print the resource tablSEO靠我e from the APK.\n"" configurations Print the configurations in the APK.\n"" xmltree Print the compilSEO靠我ed xmls in the given assets.\n"" xmlstrings Print the strings of the given compiled xml assets.\n\n"SEO靠我, gProgName);fprintf(stderr," %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\nSEO靠我"" [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"" [--debug-mode] [--min-sdk-veSEO靠我rsion VAL] [--target-sdk-version VAL] \\\n"" [--app-version VAL] [--app-version-name TEXT] [--customSEO靠我-package VAL] \\\n"" [--rename-manifest-package PACKAGE] \\\n"" [--rename-instrumentation-target-pacSEO靠我kage PACKAGE] \\\n"" [--utf16] [--auto-add-overlay] \\\n"" [--max-res-version VAL] \\\n"" [-I base-pSEO靠我ackage [-I base-package ...]] \\\n"" [-A asset-source-dir] [-G class-list-file] [-P public-definitioSEO靠我ns-file] \\\n"" [-S resource-sources [-S resource-sources ...]] \\\n"" [-F apk-file] [-J R-file-dir]SEO靠我 \\\n"" [--product product1,product2,...] \\\n"" [-c CONFIGS] [--preferred-density DENSITY] \\\n"" [SEO靠我--split CONFIGS [--split CONFIGS]] \\\n"" [--feature-of package [--feature-after package]] \\\n"" [rSEO靠我aw-files-dir [raw-files-dir] ...] \\\n"" [--output-text-symbols DIR]\n"

这是代码,看下我们执行aapt时的输出:

是不是就是这个?好SEO靠我吧,当命令校验成功,会执行handleCommand方法,并传入一个Bundle对象。看下handleCommand方法的代码:

/** Dispatch the command.*/ SEO靠我int handleCommand(Bundle* bundle) {//printf("--- command %d (verbose=%d force=%d):\n",// bunSEO靠我dle->getCommand(), bundle->getVerbose(), bundle->getForce());//for (int i = 0; i < bundle->getFileSpSEO靠我ecCount(); i++)// printf(" %d: %s\n", i, bundle->getFileSpecEntry(i));switch (bundle->getCommand()) SEO靠我{case kCommandVersion: return doVersion(bundle);case kCommandList: return doList(bundle);case kCommaSEO靠我ndDump: return doDump(bundle);case kCommandAdd: return doAdd(bundle);case kCommandRemove: return doRSEO靠我emove(bundle);case kCommandPackage: return doPackage(bundle);case kCommandCrunch: return doCrunch(buSEO靠我ndle);case kCommandSingleCrunch: return doSingleCrunch(bundle);case kCommandDaemon: return runInDaemSEO靠我onMode(bundle);default:fprintf(stderr, "%s: requested command not yet supported\n", gProgName);returSEO靠我n 1;} }

也没什么,就判断是哪种命令,比如package、dump、add、remove,然后再去做具体的操作,如doPackage、doDump、doRemove等等。不过这些方SEO靠我法是通过外部引用的,其真正的代码在同目录下的Command.cpp文件中,这个c文件实现了所有的aapt命令的具体代码。这里举一个稍微简单的例子doRemove看一下:

/** Delete filesSEO靠我 from an existing archive.*/ int doRemove(Bundle* bundle) {//命令行举例:aapt r out.apk AnSEO靠我droidManifest.xmlZipFile* zip = NULL;status_t result = UNKNOWN_ERROR;const char* zipFileName;if (bunSEO靠我dle->getFileSpecCount() < 1) {//如果没有指定压缩包名称,提示必须指定压缩包名称fprintf(stderr, "ERROR: must specify zip fileSEO靠我 name\n");goto bail;}//获取到压缩文件名称。[out.apk,AndroidManifest.xml]数组中的第一个。zipFileName = bundle->getFileSSEO靠我pecEntry(0);//[out.apk,AndroidManifest.xml]数组大小小于2,说明没有要移除的文件。if (bundle->getFileSpecCount() < 2) {fSEO靠我printf(stderr, "NOTE: nothing to do\n");goto bail;}//类似于java中的打开输入输出流。zip = openReadWrite(zipFileNamSEO靠我e, false);if (zip == NULL) {//打开文件失败。fprintf(stderr, "ERROR: failed opening Zip archive %s\n",zipFilSEO靠我eName);goto bail;}//索引从1开始,可以有多个要删除的文件。for (int i = 1; i < bundle->getFileSpecCount(); i++) {//获取要删除SEO靠我的文件名。const char* fileName = bundle->getFileSpecEntry(i);ZipEntry* entry;//获取要删除的文件。entry = zip->getESEO靠我ntryByName(fileName);if (entry == NULL) {//文件找不到printf(" %s NOT FOUND\n", fileName);continue;}//找到该文SEO靠我件则删除该文件。result = zip->remove(entry);if (result != NO_ERROR) {fprintf(stderr, "Unable to delete %s frSEO靠我om %s\n",bundle->getFileSpecEntry(i), zipFileName);goto bail;}}//相当于java中输入输出流的刷新。zip->flush();bail:SEO靠我delete zip;return (result != NO_ERROR); }

首先会判断命令中有没有指定压缩包,如果没有,会报错。有指定压缩包才会去获取命令中压缩包的名称。如果没有SEO靠我指定要删除的文件,会提示没有指定要删除的文件。否则打开文件,在打开文件成功的情况下,循环遍历要删除的文件,依次把该文件从压缩包中删除,全部操作完成后刷新压缩包中的内容。

以上就是执行aapt removSEO靠我e命令底层所执行的详细代码了,说到底aapt所有的命令最底层都是在操作文件。只是google把这些复杂的文件操作封装成命令工具供我们使用。这里的源码分析也只是看一个大体的结构,更具体的实现,各位小伙伴SEO靠我有兴趣可以去翻一翻源码啊,不一定要精通c++,有一点基础或者其它语言基础也能看得懂大概的流程,除非涉及到修改然后重新编译成定制化的aapt命令,那又是另外一回事了。

好了,关于aapt命令就写到这里了,SEO靠我知道怎么用,底层大概是怎么实现的就可以了,有深厚c++功底的同学可以自己尝试改一改。文章中若是有不足或错误,看到的小伙伴可以在底下留言告诉我,小菜鸟不胜感激~

“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2