Android AAPT詳解

目錄

  • AAPT解釋位隶,作用
  • AAPT基本命令
  • AAPT編譯資源源碼解析
  • AAPT打包和系統(tǒng)不一致的資源ID

AAPT是什么

AAPT - Android Asset Packaging Tool

看全稱设易,就可知道AAPT是Android資源打包工具。?講這個之前,是有必要簡單說下Android是如何構(gòu)建一個APK的射沟。


上圖是Google官方發(fā)布的一張非常經(jīng)典的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字節(jié)碼轉(zhuǎn)換成Dalvik字節(jié)碼们妥,在此過程中會壓縮常量池猜扮,消除一些冗余信息等。
  5. apkbuilder工具會將所有沒有編譯的資源监婶,.arsc資源旅赢,.dex文件打包到一個完成apk文件中中。
  6. 簽名惑惶,5中完成apk通過配置的簽名文件(debug和release都有)煮盼,jarsigner工具會對齊簽名。得到一個簽名后的apk,signed.apk
  7. 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 Source

?我這邊下載的是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ù)較長,不會貼出所有的源碼

  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比較筐赔,去除命令中所有的參數(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。

  1. 分發(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;
    }
}
  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;
}

注釋:

  1. 編譯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);
}
  1. 完成上述的編譯資源的工作以后,細心的讀者就會發(fā)現(xiàn)咒劲,對于manifest.xml一直都是讀取里面的配置信息顷蟆,并沒有編譯,所以最后一步就是把manifest.xml編譯成二進制文件腐魂。這個就不貼出源碼了帐偎。

  2. 最后一步,將上述的編譯結(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呢要拂?下篇博客抠璃,我會詳細分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脱惰,一起剝皮案震驚了整個濱河市鸡典,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枪芒,老刑警劉巖彻况,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舅踪,居然都是意外死亡纽甘,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門抽碌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悍赢,“玉大人决瞳,你說我怎么就攤上這事∽笕ǎ” “怎么了皮胡?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赏迟。 經(jīng)常有香客問我屡贺,道長,這世上最難降的妖魔是什么锌杀? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任甩栈,我火速辦了婚禮,結(jié)果婚禮上糕再,老公的妹妹穿的比我還像新娘量没。我一直安慰自己,他們只是感情好突想,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布殴蹄。 她就那樣靜靜地躺著,像睡著了一般猾担。 火紅的嫁衣襯著肌膚如雪袭灯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天垒探,我揣著相機與錄音妓蛮,去河邊找鬼。 笑死圾叼,一個胖子當著我的面吹牛蛤克,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夷蚊,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼构挤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惕鼓?” 一聲冷哼從身側(cè)響起筋现,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箱歧,沒想到半個月后矾飞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡呀邢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年洒沦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片价淌。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡申眼,死狀恐怖瞒津,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情括尸,我是刑警寧澤巷蚪,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站濒翻,受9級特大地震影響屁柏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肴焊,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一前联、第九天 我趴在偏房一處隱蔽的房頂上張望功戚。 院中可真熱鬧娶眷,春花似錦、人聲如沸啸臀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乘粒。三九已至豌注,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灯萍,已是汗流浹背轧铁。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旦棉,地道東北人齿风。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像绑洛,于是被迫代替她去往敵國和親救斑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容