日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Android AAPT詳解 轉(zhuǎn)

 ala咪s 2017-09-15

目錄

  • 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文件和第三方j(luò)ar或者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é)果是什么具體的意思,猜測應(yīng)該是序列化的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文件中看到的資源編號是對應(yīng)起來的。

  • 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信息??唇Y(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:包括一個或多個設(shè)備資源,由逗號分隔;
  • -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:應(yīng)用程序版本號
  • –app-version-name TEXT:應(yīng)該程序版本名字;
  • –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

預(yù)處理一個文件

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 設(shè)置的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();
    
    // 設(shè)置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表明是當前應(yīng)用程序的資源,系統(tǒng)的資源是以0x01開頭。04 表示TypeID。資源的類型animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。最后四位是EntryID,指的是每一個資源在起對應(yīng)的TypID中出現(xiàn)的順序。




而ResouceTable里面存儲的最核心的元素就是這個id的區(qū)分。

收集完成當前應(yīng)用依賴的資源以后,就要編譯當前應(yīng)用自己的資源。這里由于代碼太過于復雜,本人也沒有完全看懂,就不貼了,邏輯基本上就是通過命令的輸出,一個個的編譯資源文件和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呢?下篇博客,我會詳細分析。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多