目錄
- AAPT解釋位隶,作用
- AAPT基本命令
- AAPT編譯資源源碼解析
- AAPT打包和系統(tǒng)不一致的資源ID
AAPT是什么
AAPT - Android Asset Packaging Tool
看全稱设易,就可知道AAPT是Android資源打包工具。?講這個之前,是有必要簡單說下Android是如何構(gòu)建一個APK的射沟。
上圖是Google官方發(fā)布的一張非常經(jīng)典的Apk打包流程圖。?
流程概述:
- 工程的資源文件(res文件夾下的文件)渗蟹,通過AAPT打包成R.java類(資源索引表)僧叉,以及.arsc資源文件
- 如果有aidl,通過aidl工具哥谷,打包成java接口類
- R.java和aidl.java通過java編譯成想要的.class文件岸夯。
- 源碼class文件和第三方jar或者library通過dx工具打包成dex文件。dx工具的主要作用是將java字節(jié)碼轉(zhuǎn)換成Dalvik字節(jié)碼们妥,在此過程中會壓縮常量池猜扮,消除一些冗余信息等。
- apkbuilder工具會將所有沒有編譯的資源监婶,.arsc資源旅赢,.dex文件打包到一個完成apk文件中中。
- 簽名惑惶,5中完成apk通過配置的簽名文件(debug和release都有)煮盼,jarsigner工具會對齊簽名。得到一個簽名后的apk,signed.apk
- zipAlign工具對6中的signed.apk進行對齊處理带污,所謂對齊僵控,主要過程是將APK包中所有的資源文件距離文件起始偏移為4字節(jié)整數(shù)倍,這樣通過內(nèi)存映射訪問apk文件時的速度會更快刮刑。對齊的作用主要是為了減少運行時內(nèi)存的使用喉祭。
總結(jié):
- 輸入: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的地址配置好環(huán)境變量后藐唠,在終端中輸入 aapt v 會得到aapt版本信息,如下:
再輸入 aapt 命令會列出所有的aapt命令集合鹉究,下面我們一條條來使用并且分析其作用:
1.aapt l[ist] [-v] [-a] file.{zip,jar,apk}
作用:列出壓縮文件(zip,jar,apk)中的目錄內(nèi)容宇立。
例如:
aapt l /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
結(jié)果如下:
可以看出來不加任何參數(shù),aapt只是簡單的羅列壓縮文件中每一項的內(nèi)容自赔。
aapt l -v /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
結(jié)果如下:
從圖中可以看出妈嘹,加上-v后,輸出的內(nèi)容很詳細绍妨,并且以列表的形式標識出很多參數(shù)润脸,其中表目有:
Length:原始文件的長度
Date:日期
Time:時間
Name:名稱
Method:壓縮方法,Deflate及Stored兩種他去,即該Zip目錄采用的算法是壓縮模式還是存儲模式毙驯;可以看出resources.arsc、*.png采用壓縮模式灾测,而其它采用壓縮模式爆价。
Ratio:壓縮率
Size:這個是壓縮省掉的大小,即如果壓縮率是xx%媳搪。那Size是原始長度*(1-xx%)铭段。
CRC-32:循環(huán)冗余校驗。這個計算是有特定的算法的蛾号。
-
offset:zipfile中偏移量的意思
aapt l -a /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
結(jié)果如下:
-a表示會詳細輸出壓縮文件中所有目錄的內(nèi)容稠项,詳細到什么程度的涯雅,可以看上圖鲜结,上圖截取的只是很小的一部分,這部分是manifest.xml文件的所有數(shù)據(jù)活逆,可以看出來基本上所有的manifest信息都列了出來精刷。
2.aapt d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]
作用:通過參數(shù)配置可以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
結(jié)果:
不太了解這個結(jié)果是什么具體的意思蔗候,猜測應該是序列化的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
結(jié)果:
查看APK中的配置信息,包括包名锈遥,versionCode,versionName,platformBuildVersionName(編譯的時候系統(tǒng)添加的字段纫事,相當targetSdkVersionCode的描述)等勘畔。同事該命令還列出了manifest.xml的部分信息,包括啟動界面丽惶,manifest里配置的label炫七,icon等信息。還有分辨率钾唬,時區(qū)万哪,uses-feature等信息。
- permissions 官方解釋:Print the permissions from the APK
較簡單抡秆,輸出APK中使用到的權(quán)限信息奕巍。
- resources 官方解釋:
Print the resource table from the APK
aapt dump resources /Users/zfxu/work/androidstudio_workspace/AAPTDemo/app/build/outputs/apk/app-debug.apk
結(jié)果:
輸出了apk中所有的資源信息,從這里也可以看出來aapt打包時也包含了android系統(tǒng)很多資源儒士。并且這里也發(fā)現(xiàn)的止,系統(tǒng)通過標準的aapt構(gòu)建出來的資源絕大部分的資源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
結(jié)果:
可以看出該命令輸出了apk所有的資源目錄冲杀,僅僅是目錄,里面有語言睹酌,分辨率权谁,夜間模式相關(guān)的數(shù)據(jù)。
- 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
結(jié)果:
該命令直接反編譯除了apk中某一個xml布局文件的組織結(jié)構(gòu)憋沿。命令需要兩個參數(shù) 第一是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
結(jié)果:
從字面解釋旺芽,輸出xml文件中所有的string信息》模看結(jié)果采章,實際上并沒看出來什么特殊的,也并不是簡單的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:覆蓋現(xiàn)有的文件命令,加上后編譯生成直接覆蓋目前已經(jīng)存在的R.java;
- -m:使生成的包的目錄放在-J參數(shù)指定的目錄;
- -u:更新現(xiàn)有的包 u = update;
- -v:詳細輸出,加上此命令會在控制臺輸出每一個資源文件信息,R.java生成后還有注釋砸民。
- -x:創(chuàng)建擴展資源ID;
- -z:需要本地化的資源屬性標記定位抵怎。
- -M:AndroidManifest.xml的路徑
- -0:指定一個額外的擴展. apk文件將不會存儲壓縮
- -g:制定像素迫使圖形的灰度
- -j:指定包含一個jar或zip文件包,這個命令很特別
- –debug-mode:指定的是調(diào)試模式下的編譯資源;
- –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打包的關(guān)鍵步驟之一秒际,還有一個步驟就是把資源文件編譯成R.java悬赏,供程序調(diào)用狡汉。命令如下:
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.
對多個或者單個資源文件夾進行處理进萄,并且將結(jié)果保存在輸出文件夾中
6.aapt s[ingleCrunch] [-v] -i input-file -o outputfile
官方解釋:
Do PNG preprocessing on a single file
預處理一個文件
AAPT源碼解析
首先下載Android源碼
?我這邊下載的是Android 6.0的源碼
AAPT代碼地址:***/frameworks/tools/aapt/目錄下捻脖。
我們這里以一個命令來跟蹤源碼的流程,即用aapt是如何構(gòu)建一個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:由于某些函數(shù)較長,不會貼出所有的源碼
- 入口 /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比較筐赔,去除命令中所有的參數(shù)铣猩,并且放進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;
}
//這個僅僅是把傳進來的地址坐下系統(tǒng)路徑分割線的轉(zhuǎn)換
convertPath(argv[0]);
bundle.setAndroidManifestFile(argv[0]);
break;
}
***
}
}
***
result = handleCommand(&bundle);
***
}
當通過所有的匹配規(guī)則后,該函數(shù)實際調(diào)用是 handleCommand(&bundle)茴丰。 至于執(zhí)行什么命令說白了也是命令指定的达皿,-p 設置的command參數(shù)是kCommandPackage。
- 分發(fā)指令 /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;
}
}
- 處理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-1
err = configFilter->parse(bundle->getConfigurations());
if (err != NO_ERROR) {
goto bail;
}
//資源本地化相關(guān)的配置贿肩,具體什么含義也沒有理解清楚
if (configFilter->containsPseudo()) {
bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED);
}
if (configFilter->containsPseudoBidi()) {
bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI);
}
//校驗命令中是否傳入正確的參數(shù)
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();
// 如果輸出文件存在峦椰,但是是不合格的,則直接報錯結(jié)束汰规,如果不存在汤功,則新建空文件
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一個對象賦值給AaptAssets
if (bundle->getGenDependencies()) {
sp<FilePathStore> resPathStore = new FilePathStore;
assets->setFullResPaths(resPathStore);
sp<FilePathStore> assetPathStore = new FilePathStore;
assets->setFullAssetPaths(assetPathStore);
}
//調(diào)用AaptAssets類的成員函數(shù)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 here
if (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.d
dependencyFile = String8(bundle->getRClassDir());
dependencyFile.appendPath("R.java.d");
}
// Make sure we have a clean dependency file to start with
fp = fopen(dependencyFile, "w");
fclose(fp);
}
// Write out R.java constants
if (!assets->havePrivateSymbols()) {
if (bundle->getCustomPackage() == NULL) {
// Write the R.java file into the appropriate class directory
// e.g. gen/com/foo/app/R.java
err = 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.java
if (bundle->getExtraPackages() != NULL) {
// Split on colon
String8 libs(bundle->getExtraPackages());
char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
while (packageString != NULL) {
// Write the R.java file out with the correct package name
err = 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 file
err = writeProguardFile(bundle, assets);
if (err < 0) {
goto bail;
}
// Write the apk
if (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 prereqs
fp = 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 slurping
fprintf(fp, "%s \\\n", bundle->getAndroidManifestFile());
fclose(fp);
}
retVal = 0;
bail:
if (SourcePos::hasErrors()) {
SourcePos::printErrors(stderr);
}
return retVal;
}
注釋:
-
3-1:這個過程主要是對資源配置信息進行校驗在抛,Android應用程序資源的組織方式有18個維度,包括mcc(移動國家代碼)萧恕、mnc(移動網(wǎng)絡代碼)刚梭、local(語言區(qū)域)等肠阱。改代碼的主要實現(xiàn)是在 /framewors/base/tools/aapt/AaptConfig.cpp 里的parse方法。解析完成的數(shù)據(jù)朴读,會丟給WeakResourceFilter類中的一個向量集合成員mConfigs屹徘。關(guān)于這塊的詳細解釋,可以參考官網(wǎng)或者老羅的博客衅金,地址如下:
https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
- 編譯res和xml資源 /frameworks/base/tools/aapt/Resource.cpp
ps:改函數(shù)較長噪伊,截取部分代碼分步解析
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文件,調(diào)用的是parsePackage函數(shù)氮唯,解析之前鉴吹,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對象。在調(diào)用公共類的parseXmlResource解析xml文件得到具體數(shù)據(jù)后瞒渠,存放在對象ResXmlTree中良蒸。parseXMLResource函數(shù)在類 frameworks/base/tools/aapt/XMLNode.cpp 中。有興趣的可以自己去讀下伍玖,這里就不貼了嫩痰。解析玩manifest.xml后,我們繼續(xù)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這個數(shù)據(jù)結(jié)構(gòu)中始赎。這邊簡單介紹一下ResourceTable這個數(shù)據(jù)結(jié)構(gòu),首先我們得知道R.java里面的資源標識id的構(gòu)成仔燕,比方說 0x7f040002 其中0x7f表示是packageID造垛,也就是上面的packageType,它是一個命名空間,限定資源的來源晰搀,7f表明是當前應用程序的資源五辽,系統(tǒng)的資源是以0x01開頭。04 表示TypeID外恕。資源的類型animator杆逗、anim、color鳞疲、drawable罪郊、layout、menu尚洽、raw悔橄、string和xml等等若干種,每一種都會被賦予一個ID。最后四位是EntryID癣疟,指的是每一個資源在起對應的TypID中出現(xiàn)的順序挣柬。
而ResouceTable里面存儲的最核心的元素就是這個id的區(qū)分。
收集完成當前應用依賴的資源以后睛挚,就要編譯當前應用自己的資源邪蛔。這里由于代碼太過于復雜,本人也沒有完全看懂扎狱,就不貼了侧到,邏輯基本上就是通過命令的輸出,一個個的編譯資源文件和png淤击。然后存儲在一個xml文件中床牧,為后面生成R.java文件中做準備。實際上前面也有提到遭贸,所有的資源都會存在ResouceTable這個數(shù)據(jù)結(jié)構(gòu)中戈咳,做完編譯工作以后,只需要去遍歷這個向量表壕吹,然后對里面的packageID著蛙,typeID,EvtryID進行拼接耳贬,就可以得到我們所熟悉的 0x7f040002這種資源ID踏堡。ResouceTable的構(gòu)造函數(shù)也可以看出來里面的過程:
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);
}
完成上述的編譯資源的工作以后,細心的讀者就會發(fā)現(xiàn)咒劲,對于manifest.xml一直都是讀取里面的配置信息顷蟆,并沒有編譯,所以最后一步就是把manifest.xml編譯成二進制文件腐魂。這個就不貼出源碼了帐偎。
最后一步,將上述的編譯結(jié)果輸出到R.java和Apk中蛔屹。其中還會輸出混淆文件削樊,java符號表等。
...省略代碼
// Write out R.java constants
if (!assets->havePrivateSymbols()) {
if (bundle->getCustomPackage() == NULL) {
// Write the R.java file into the appropriate class directory
// e.g. gen/com/foo/app/R.java
err = 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.java
if (bundle->getExtraPackages() != NULL) {
// Split on colon
String8 libs(bundle->getExtraPackages());
char* packageString = strtok(libs.lockBuffer(libs.length()), ":");
while (packageString != NULL) {
// Write the R.java file out with the correct package name
err = 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++功底不佳,有的東西也只是靠猜測來完成育叁,基本上能夠理清楚大體的邏輯迅脐。如果想更詳細的內(nèi)容,可以自己參考源碼豪嗽。網(wǎng)上有篇博客谴蔑,有點亂豌骏,但是很細,可以看下 http://www.cnblogs.com/dyllove98/p/3144950.html
AAPT命令修改树碱,完成修改資源ID
在第三節(jié)我們講AAPT是如何編譯資源并且生成R.java文件的肯适,也提到R.java中資源ID的含義变秦,在組件化框架中成榜,由于組件和宿主分開編譯,為了防止組件的資源ID和宿主的資源ID沖突蹦玫,所以就需要修改AAPT源碼赎婚。基本思路就是每個組件分配一個不一樣的ID樱溉,宿主的ID是以0x7f開頭挣输,組件的ID是0x**開頭,這樣就避免沖突福贞。
可以看出如果不修改AAPT源碼重新構(gòu)建撩嚼,就會導致組件之間或者組件與宿主之間的ID沖突。所以就會有如下模型:
既然分析AAPT的編譯過程挖帘,那思路就很清晰了完丽,在命令中添加一個自定義的ID,然后在代碼中拿到這個ID拇舀,拼接的時候替換上即可逻族。當然這只是最簡單的,而實際情況呢骄崩,比方說宿主里有一部分資源是其他組件公用的聘鳞,如何保證這部分資源和id和組件本身的id不會發(fā)生沖突呢?又如何在我們工程里自動化這套自定義的aapt從而代替系統(tǒng)標準的aapt呢要拂?下篇博客抠璃,我會詳細分析。