apk資源打包過程分析

本文參考了羅大仙還有一個未知名網友

Android應用資源的分類

  • assets
  • res
    • animator
    • anim
    • color
    • drawable
    • layout
    • menu
    • raw:它們和assets類資源一樣,都是原裝不動地打包在apk文件中的,不過它們會被賦予資源ID
      java
      Resources res = getResources();
      InputStream is = res .openRawResource(R.raw.filename);
    • values
    • xml

這些文件都會被打包到apk中

image

中間 02 所在位置值代表資源ID對應的資源的類型,分別是:
- 02:drawable
- 03:layout
- 04:values
- 05:xml
- 06:raw
- 07:color
- 08:menu

如果要訪問assets中的文件則需要如下寫法:

AssetManager am= getAssets();    
InputStream is = assset.open("filename");

上面文件夾中的文件除了raw類型資源以及Bitmap文件的drawable類型資源之外其他都是xml格式文件,打包過程中都會被編譯成二進制格式的XML文件這些二進制格式的XML文件分別有一個字符串資源池,用來保存文件中引用到的每一個字符串,包括XML元素標籤、屬性名稱、屬性值,以及其它的一切文本值所使用到的字符串。這樣原來在文本格式的XML文件中的每一個放置字符串的地方在二進制格式的XML文件中都被替換成一個索引到字符串資源池的整數值。

image

Android資源打包工具aapt在編譯和打包資源的過程中,會執行以下兩個額外的操作:
- 1. 賦予每一個非assets資源一個ID值,這些ID值以常量的形式定義在一個R.java文件中。
- 2. 生成一個resources.arsc文件,用來描述那些具有ID值的資源的配置信息,它的內容就相當於是一個資源索引表。

首先Android資源打包工具在編譯應用程序資源之前,會創建一個資源表。資源表對應ResourceTable對象描述,當應用程序資源編譯完成之後,它就會包含所有資源的信息。Android資源打包工具就可以根據它的內容來生成資源索引表文件resources.arsc。

image

之後就可以根據資源賦值的內容生成resources.arsc。

理解ResourceTable

ResourceTable用來總體描述一個資源表

  • mAssetsPackage : 當前正在編譯的資源的包名稱
  • mPackages : 表示當前正在編譯的資源包,每一個包都用一個Package對象來描述。
  • mOrderedPackages : 有序包
  • mAssets : 表示當前編譯的資源目錄,它指向的是一個AaptAssets對象
  • mName : 表示包的名稱
  • mTypes : 表示包含的資源的類型,每一個類型都用一個Type對象來描述。資源的類型就是指animimator、anim、color、drawable、layout、menu和values等
  • mOrderedTypes : 有序的mTypes

Type類用來描述一個資源類型

  • mName : 表示資源類型名稱。
  • mConfigs : 表示包含的資源配置項列表,每一個配置項列表都包含了一系列同名的資源,使用一個ConfigList來描述。
  • mOrderedConfigs : 有序的ConfigList
  • mUniqueConfigs : 表示包含的不同資源配置信息的個數

ConfigList用來描述一個資源配置項列表

  • mName : 表示資源項名稱,也稱爲Entry Name。
  • mEntries : 表示包含的資源項,每一個資源項都用一個Entry對象來描述,並且以一個對應的ConfigDescription爲Key保存在一個DefaultKeyedVector中。

Entry類用來描述一個資源項
- mName : 表示資源名稱。
- mItem : 表示資源數據,用一個Item對象來描述

Item類用來描述一個資源項數據
- value : 表示資源項的原始值,它是一個字符串。
- parsedValue : 表示資源項原始值經過解析後得到的結構化的資源值,使用一個Res_Value對象來描述。

image

AaptAssets
- mPackage : 表示當前正在編譯的資源的包名稱
- mRes : 表示所包含的資源類型集,每一個資源類型都使用一個ResourceTypeSet來描述,並且以Type Name爲Key保存在一個KeyedVector中。
- mHaveIncludedAssets : 表示是否有引用包。
- mIncludedAssets : 指向的是一個AssetManager,用來解析引用包。引用包都是一些預編譯好的資源包,它們需要通過AssetManager來解析。
- mOverlay : 表示當前正在編譯的資源的重疊包。

ResourceTypeSet描述的是一個類型爲AaptGroup的KeyedVector

AaptFile每一個資源文件都是用一個AaptFile對象來描述的

  • mPath : 表示資源文件路徑
  • mGroupEntry : 表示資源文件對應的配置信息,使用一個AaptGroupEntry對象來描述
  • mResourceType : 表示資源類型名稱。
  • mData : 表示資源文件編譯後得到的二進制數據
  • mDataSize : 表示資源文件編譯後得到的二進制數據的大小。

AaptGroupEntry其中成員變量對應十八維度

資源打包過程

舉例項目結構如下:

project
  --AndroidManifest.xml
  --res
    --drawable-ldpi
      --icon.png
    --drawable-mdpi
      --icon.png
    --drawable-hdpi
      --icon.png
    --layout
      --main.xml
      --sub.xml
    --values
      --strings.xml

核心項目是由doPackage此方法開始位置在Main.cpp中

前期的準備工作:

  • 檢查aapt打包時的參數是否都存在
  • 得到最終將資源打包輸出到的APK名稱
  • 檢查該文件是否存在不存在則創建,並確定其實常規文件
  • 創建一個AaptAssets對象

    assets = new AaptAssets();

    然後正式進入打包過程

打包過程如下:

收錄AndroidManifest.xml文件目錄assets和res下的資源目錄和資源文件

1. 解析AndroidManifest.xml

解析目的:獲得要編譯資源的應用程序的包名稱,拿到包名就可以創建ResourceTable對象
int doPackage(Bundle* bundle)
{
    sp<AaptAssets> assets;
    ...
    assets = new AaptAssets();
    ...
    //將AndroidManifest.xml文件目錄assets和res下的資源目錄和資源文件收錄起來保存到AaptAssets中的成員變量中
    err = assets->slurpFromArgs(bundle);
    ...
}
ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
{
    int count;
    int totalCount = 0;
    FileType type;
    // 獲取res目錄的路徑
    const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
    const size_t dirCount =resDirs.size();
    sp<AaptAssets> current = this;
    //獲取bundle內所保存的aapt的命令選項個數,即要完成的功能個數
    const int N = bundle->getFileSpecCount();

    /*
     * 如果bundle中指定了AndroidManifest.xml文件,則首先包含它 
     */
    if (bundle->getAndroidManifestFile() != NULL) {
        // place at root of zip.
        String8 srcFile(bundle->getAndroidManifestFile());


        //每向AaptAssets的對象中添加一個資源文件或者一個資源目錄都要新建一個
        //AaptGroupEntry的空對象並將其添加到一個類型爲SortedVector的AaptAssets的成員變量mGroupEntries中
        //在這裏調用addFile函數是將AndroidManifest.xml文件添加到成員變量mFiles中去.
        addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),NULL, String8());//**

        // 每添加一個資源就加1統計一次
        totalCount++;
    }

    /*
     * If a directory of custom assets was supplied, slurp 'em up.
     * 判斷是否指定了assets文件夾,如果指定則解析它 
     */
    const Vector<const char*>& assetDirs = bundle->getAssetSourceDirs();//獲取目錄名稱
    const int AN = assetDirs.size();
    for (int i = 0; i < AN; i++) {
        FileType type = getFileType(assetDirs[i]);//獲取目錄類型
        if (type == kFileTypeNonexistent) {
            fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDirs[i]);
            return UNKNOWN_ERROR;
        }
        if (type != kFileTypeDirectory) {
            fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDirs[i]);
            return UNKNOWN_ERROR;
        }

        String8 assetRoot(assetDirs[i]);
        //創建一個名爲”assets”的AaptDir對象
        sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));//assets
        AaptGroupEntry group;
        //收錄目錄“assets”下的資源文件,並返回資源文件個數 
        count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
                                            String8(), mFullAssetPaths, true);
        if (count < 0) {
            totalCount = count;
            goto bail;
        }
        if (count > 0) {
            mGroupEntries.add(group);
        }
        totalCount += count;

        if (bundle->getVerbose()) {
            printf("Found %d custom asset file%s in %s\n",
                   count, (count==1) ? "" : "s", assetDirs[i]);
        }
    }

    /*
     * If a directory of resource-specific assets was supplied, slurp 'em up.
     * 收錄指定的res資源目錄下的資源文件 
     */
    for (size_t i=0; i<dirCount; i++) {
        const char *res = resDirs[i];
        if (res) {
            type = getFileType(res);//獲取文件類型
            if (type == kFileTypeNonexistent) {
                fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
                return UNKNOWN_ERROR;
            }
            if (type == kFileTypeDirectory) {

                //如果指定了多個res資源目錄文件, 則爲其創建多個AaptAssets
                //類來分別收錄這些目錄中的信息,並將其設置賦值給當前AaptAssets對象的成員變量mOverlay

                if (i>0) {
                    sp<AaptAssets> nextOverlay = new AaptAssets();
                    current->setOverlay(nextOverlay);
                    current = nextOverlay;
                    current->setFullResPaths(mFullResPaths);
                }
                //調用成員函數slurpResourceTree來收錄res目錄下的資源文件
                count = current->slurpResourceTree(bundle, String8(res));
                if (i > 0 && count > 0) {
                  count = current->filter(bundle);
                }

                if (count < 0) {
                    totalCount = count;
                    goto bail;
                }
                totalCount += count;//統計資源文件個數
            }
            else {
                fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
                return UNKNOWN_ERROR;
            }
        }

    }
    /*
     * Now do any additional raw files.
     * 接着收錄剩餘的指定的資源文件
     */
    for (int arg=0; arg<N; arg++) {
        const char* assetDir = bundle->getFileSpecEntry(arg);

        FileType type = getFileType(assetDir);
        if (type == kFileTypeNonexistent) {
            fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
            return UNKNOWN_ERROR;
        }
        if (type != kFileTypeDirectory) {
            fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
            return UNKNOWN_ERROR;
        }

        String8 assetRoot(assetDir);

        if (bundle->getVerbose())
            printf("Processing raw dir '%s'\n", (const char*) assetDir);

        /*
         * Do a recursive traversal of subdir tree.  We don't make any
         * guarantees about ordering, so we're okay with an inorder search
         * using whatever order the OS happens to hand back to us.
         */
        count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
        if (count < 0) {
            /* failure; report error and remove archive */
            totalCount = count;
            goto bail;
        }
        totalCount += count;

        if (bundle->getVerbose())
            printf("Found %d asset file%s in %s\n",
                   count, (count==1) ? "" : "s", assetDir);
    }

    count = validate();
    if (count != NO_ERROR) {
        totalCount = count;
        goto bail;
    }

    count = filter(bundle);
    if (count != NO_ERROR) {
        totalCount = count;
        goto bail;
    }

bail:
    return totalCount;
}

這裏面的邏輯是:
- 獲取res目錄的路徑
- 獲取AndroidManifest.xml文件路徑
- 將AndroidManifest.xml文件添加到AaptAssets對象的成員變量mFiles中&添加到AaptGroupEntry的空對象並將其添加到一個類型爲SortedVector的AaptAssets的成員變量mGroupEntries中
- 判斷是否指定了assets文件夾,如果指定則解析它
- 收錄指定的res資源目錄下的資源文件
- 接着收錄剩餘的指定的資源文件

sp<AaptFile> AaptAssets::addFile(
        const String8& filePath, const AaptGroupEntry& entry,
        const String8& srcDir, sp<AaptGroup>* outGroup,
        const String8& resType)
{
    sp<AaptDir> dir = this;//AaptAssets類繼承了一個AaptDir類
    sp<AaptGroup> group;
    sp<AaptFile> file;
    String8 root, remain(filePath), partialPath;
    while (remain.length() > 0) {
        //獲取remain所描述文件的工作目錄,如果其僅僅指定了文件名則返回文件名,如果文件名前添加了路徑,則返回最上層的目錄名
        //例如,remain = “AndroidManifest.xml”,則root=“AndroidManifest.xml”, remain = “”; 
        //如果remain=“/rootpath/subpath/AndroidManifest.xml”,則,root=“rootpath”, remain=”subpath/AndroidManifest.xml”
        root = remain.walkPath(&remain);
        partialPath.appendPath(root);

        const String8 rootStr(root);
        //在這裏remain.length()返回0 
        if (remain.length() == 0) {
            //添加資源文件到mFiles中去
            //dir指向當前AaptAssets對象,其調用getFiles返回類型爲
            //DefaultKeyVector<String8, sp<AaptGroup>>成員變量mFiles,判斷其內部
            //是否包含了名稱爲rootStr的AaptGroup對象,並返回其位置值

            ssize_t i = dir->getFiles().indexOfKey(rootStr);

            //如果返回的位置值>=0表示mFiles中已經包含了這個名爲rootStr的
            //AaptGroup對象,則將group指向該對象, 否則新建一個名稱爲rootStr
            //的AaptGroup對象並添加到mFiles中去

            if (i >= 0) {
                group = dir->getFiles().valueAt(i);
            } else {
                group = new AaptGroup(rootStr, filePath);
                status_t res = dir->addFile(rootStr, group);
                if (res != NO_ERROR) {
                    return NULL;
                }
            }
            // 新建一個AaptFile對象指向需要添加的源文件, 並將該AaptFile對象
            // 添加到類型爲DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >的
            // AaptGroup的成員變量 mFiles中去
            file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
            status_t res = group->addFile(file);
            if (res != NO_ERROR) {
                return NULL;
            }
            break;

        } else {
            //添加資源目錄到mDirs中去
            //dir指向當前AaptAssets對象,其調用getDirs返回類型爲
            //DefaultKeyVector<String8, sp<AaptDir>>成員變量mDirs,判斷其內部
            //是否包含了名稱爲rootStr的AaptDir對象,並返回其位置值
            ssize_t i = dir->getDirs().indexOfKey(rootStr);
            //如果返回的位置值>=0表示mDirs中已經包含了這個名爲rootStr的
            //AaptDir對象,則將dir指向該對象,否則新建一個名稱爲rootStr的AaptDir對象並添加到mDirs中去
            if (i >= 0) {
                dir = dir->getDirs().valueAt(i);
            } else {
                sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
                status_t res = dir->addDir(rootStr, subdir);
                if (res != NO_ERROR) {
                    return NULL;
                }
                dir = subdir;
            }
        }
    }

    //將一個空的AaptGroupEntry對象添加到mGroupEntries中去,其是一個SortedVector
    mGroupEntries.add(entry);
    if (outGroup) *outGroup = group;
    return file;
}

這裏面核心的邏輯是:
- 傳入AndroidManifest.xml相關路徑
- 將相關路徑進行拆分
- 封裝成一個group = new AaptGroup(rootStr, filePath);對象
- 將AaptGroup對象添加到AaptAssets的成員變量mFiles中
- 新建一個AaptFile對象,參數指向入AndroidManifest.xml相關路徑
- 將AaptFile添加到AaptGroup的mFiles中

解析assets中的文件

String8 assetRoot(assetDirs[i]);
//創建一個名爲”assets”的AaptDir對象
sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));//assets
AaptGroupEntry group;
//收錄目錄“assets”下的資源文件,並返回資源文件個數 
count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
                                    String8(), mFullAssetPaths, true);
//收錄路徑名爲srcDir目錄下的所有資源文件,並將對應目錄下的文件名都保存到fullResPaths中去
ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
                                    const AaptGroupEntry& kind,
                                    const String8& resType,
                                    sp<FilePathStore>& fullResPaths,
                                    const bool overwrite)
{
    //接着調用父類中的AaptDir的成員函數slurpFullTree收錄srcDir中的資源文件 
    ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths, overwrite);
    if (res > 0) {
        //如果收錄的資源個數>0,則將其歸爲一類,爲這類資源文件創建一個對應
        //AaptGroupEntry對象並添加到對應的成員變量mGroupEntries中去
        mGroupEntries.add(kind);
    }

    return res;
}

最後都要添加到AaptAssets成員變量mGroupEntries中去。

ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
                            const AaptGroupEntry& kind, const String8& resType,
                            sp<FilePathStore>& fullResPaths, const bool overwrite)
{
    Vector<String8> fileNames;
    {
        DIR* dir = NULL;
        // 首先打開將要收錄的資源文件所在的源目錄 
        dir = opendir(srcDir.string());
        if (dir == NULL) {
            fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
            return UNKNOWN_ERROR;
        }

        /*
         * Slurp the filenames out of the directory.
         * 遍歷srcDir目錄下的每一個資源文件,將其添加到AaptAssets的成員變量
         * mFullAssetPaths中,其繼承了一個Vector<String8> 
         */
        while (1) {
            struct dirent* entry;

            entry = readdir(dir);
            if (entry == NULL)
                break;

            if (isHidden(srcDir.string(), entry->d_name))
                continue;

            String8 name(entry->d_name);
            fileNames.add(name);
            // Add fully qualified path for dependency purposes
            // if we're collecting them
            // 按照全部路徑將資源文件添加到fullResPaths中去
            if (fullResPaths != NULL) {
                fullResPaths->add(srcDir.appendPathCopy(name));
            }
        }
        closedir(dir);
    }

    ssize_t count = 0;

    /*
     * Stash away the files and recursively descend into subdirectories.
     * 遞歸解析srcDir下的子目錄中的資源文件,直到收錄完所有的目錄中的資源文件爲止
     */
    const size_t N = fileNames.size();
    size_t i;
    for (i = 0; i < N; i++) {
        String8 pathName(srcDir);
        FileType type;

        pathName.appendPath(fileNames[i].string());
        type = getFileType(pathName.string());

        //如果是資源子目錄,並且其尚未收錄在mDirs中,則爲其創建一個
        //AaptDir對象,繼續遞歸遍歷其中的資源文件及目錄
        if (type == kFileTypeDirectory) {
            sp<AaptDir> subdir;
            bool notAdded = false;
            if (mDirs.indexOfKey(fileNames[i]) >= 0) {
                subdir = mDirs.valueFor(fileNames[i]);
            } else {
                subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
                notAdded = true;
            }
            ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
                                                resType, fullResPaths, overwrite);
            if (res < NO_ERROR) {
                return res;
            }
            if (res > 0 && notAdded) {
                mDirs.add(fileNames[i], subdir);//將資源目錄添加到mDirs變量中
            }
            count += res;


        // 如果其爲一個資源文件,則爲其創建一個指定的AaptFile變量
        //併爲其創建一個對應的AaptGroup變量, 將這個AaptGroup變量添加
        //到mFiles變量中,然後將AaptFile變量添加到AaptGroup中去
        } else if (type == kFileTypeRegular) {
            sp<AaptFile> file = new AaptFile(pathName, kind, resType);
            status_t err = addLeafFile(fileNames[i], file, overwrite);
            if (err != NO_ERROR) {
                return err;
            }
        //返回總的資源文件個數
            count++;

        } else {
            if (bundle->getVerbose())
                printf("   (ignoring non-file/dir '%s')\n", pathName.string());
        }
    }

    return count;
}
  • 遍歷assets中的所有文件和目錄將所有絕對路徑添加到fullResPaths中這個對應的是AaptAssets的成員變量mFullAssetPaths
  • 將資源目錄添加到成員變量mDirs中DefaultKeyedVector<String8, sp<AaptDir> > mDirs;
  • 如果是一個資源文件則爲其創建一個指定的AaptFile變量併爲其創建一個對應AaptGroup變量,將這個AaptGroup對象添加到mFiles變量中,然後將AaptFile變量添加到AaptGroup中去

收錄res下文件:

/*
 * If a directory of resource-specific assets was supplied, slurp 'em up.
 * 收錄指定的res資源目錄下的資源文件 
 */
for (size_t i=0; i<dirCount; i++) {
    const char *res = resDirs[i];
    if (res) {
        type = getFileType(res);//獲取文件類型
        if (type == kFileTypeNonexistent) {
            fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
            return UNKNOWN_ERROR;
        }
        if (type == kFileTypeDirectory) {

            //如果指定了多個res資源目錄文件, 則爲其創建多個AaptAssets
            //類來分別收錄這些目錄中的信息,並將其設置賦值給當前AaptAssets對象的成員變量mOverlay

            if (i>0) {
                sp<AaptAssets> nextOverlay = new AaptAssets();
                current->setOverlay(nextOverlay);
                current = nextOverlay;
                current->setFullResPaths(mFullResPaths);
            }
            //調用成員函數slurpResourceTree來收錄res目錄下的資源文件
            count = current->slurpResourceTree(bundle, String8(res));
            if (i > 0 && count > 0) {
              count = current->filter(bundle);
            }

            if (count < 0) {
                totalCount = count;
                goto bail;
            }
            totalCount += count;//統計資源文件個數
        }
        else {
            fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
            return UNKNOWN_ERROR;
        }
    }

}
  • 如果有多個res目錄就遍歷多個,爲每一個res目錄都產生一個AaptAssets並將這個對象的引用設置給第一次創建的AaptAssets的setOverlay方法中。挨個調用slurpResourceTree進行添加路徑

這樣就形成了一個AaptAssets的鏈表結構。

到目前爲止我們就將AndroidManifest.xml文件目錄assets和res下的資源目錄和資源文件收錄起來保存到AaptAssets中的成員變量中並且形成了一個AaptAssets鏈表(如果有多個res目錄的話)

編譯res目錄下資源文件以及AndroidManifest.xml文件

err = buildResources(bundle, assets, builder);
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.

    //首先從assets中獲取AndroidManifest.xml文件的信息
    //AndroidManifest.xml文件信息是保存在assets的成員變量mFiles中的,
    //但是其被封裝成一個AaptFile類對象保存在AaptGroup對象最終再保存到mFiles中的
    sp<AaptGroup> androidManifestFile =
            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
    if (androidManifestFile == NULL) {
        fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n");
        return UNKNOWN_ERROR;
    }
    //解析AndroidManifest.xml文件
    status_t err = parsePackage(bundle, assets, androidManifestFile);
    if (err != NO_ERROR) {
        return err;
    }

    if (kIsDebug) {
        printf("Creating resources for package %s\n", assets->getPackage().string());
    }

    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 ,在上面解析AndroidManifest.xml中解析的包名
    ResourceTable table(bundle, String16(assets->getPackage()), packageType);

    //添加被引用資源包,比如系統的那些android:命名空間下的資源也就是android.jar
    err = table.addIncludedResources(bundle, assets);
    if (err != NO_ERROR) {
        return err;
    }

    if (kIsDebug) {
        printf("Found %d included resource packages\n", (int)table.size());
    }

    // Standard flags for compiled XML and optional UTF-8 encoding
    //設置編譯XML文件的選項爲標準和UTF-8的編碼方式
    int xmlFlags = XML_COMPILE_STANDARD_RESOURCE;

    /* Only enable UTF-8 if the caller of aapt didn't specifically
     * request UTF-16 encoding and the parameters of this package
     * allow UTF-8 to be used.
     */
    if (!bundle->getUTF16StringsOption()) {
        xmlFlags |= XML_COMPILE_UTF8;
    }

    // --------------------------------------------------------------
    // First, gather all resource information.
    // --------------------------------------------------------------

    // resType -> leafName -> group
    KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
            new KeyedVector<String8, sp<ResourceTypeSet> >;

    //調用collect_files將前面收集到assets中的各類資源文件重新收集到resources中來   
    collect_files(assets, resources);
    //定義收集各類資源文件的容器
    sp<ResourceTypeSet> drawables;
    sp<ResourceTypeSet> layouts;
    sp<ResourceTypeSet> anims;
    sp<ResourceTypeSet> animators;
    sp<ResourceTypeSet> interpolators;
    sp<ResourceTypeSet> transitions;
    sp<ResourceTypeSet> xmls;
    sp<ResourceTypeSet> raws;
    sp<ResourceTypeSet> colors;
    sp<ResourceTypeSet> menus;
    sp<ResourceTypeSet> mipmaps;
    //將保存到resources中的各類文件的Set保存到我們上述定義的Set中去
    ASSIGN_IT(drawable);
    ASSIGN_IT(layout);
    ASSIGN_IT(anim);
    ASSIGN_IT(animator);
    ASSIGN_IT(interpolator);
    ASSIGN_IT(transition);
    ASSIGN_IT(xml);
    ASSIGN_IT(raw);
    ASSIGN_IT(color);
    ASSIGN_IT(menu);
    ASSIGN_IT(mipmap);
    //設置assets的資源爲resources中保存的
    assets->setResources(resources);
    // now go through any resource overlays and collect their files
    //判斷當前應用程序是否有overlay的資源,有的話將assets中保存的資源設置爲overlay中
    sp<AaptAssets> current = assets->getOverlay();
    while(current.get()) {
        KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
                new KeyedVector<String8, sp<ResourceTypeSet> >;
        current->setResources(resources);
        collect_files(current, resources);
        current = current->getOverlay();
    }
    // apply the overlay files to the base set
    //如果有overlay資源則使用overlay資源替換現有資源
    if (!applyFileOverlay(bundle, assets, &drawables, "drawable") ||
            !applyFileOverlay(bundle, assets, &layouts, "layout") ||
            !applyFileOverlay(bundle, assets, &anims, "anim") ||
            !applyFileOverlay(bundle, assets, &animators, "animator") ||
            !applyFileOverlay(bundle, assets, &interpolators, "interpolator") ||
            !applyFileOverlay(bundle, assets, &transitions, "transition") ||
            !applyFileOverlay(bundle, assets, &xmls, "xml") ||
            !applyFileOverlay(bundle, assets, &raws, "raw") ||
            !applyFileOverlay(bundle, assets, &colors, "color") ||
            !applyFileOverlay(bundle, assets, &menus, "menu") ||
            !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
        return UNKNOWN_ERROR;
    }

    bool hasErrors = false;
    //如果當前應用程序有drawables資源,則首先調用preProcessImages函數預處理
    //圖像,然後調用makeFileResources函數處理drawables中的資源
    if (drawables != NULL) {
        if (bundle->getOutputAPKFile() != NULL) {
            //預處理圖像, 目前只支持處理png格式圖像
            err = preProcessImages(bundle, assets, drawables, "drawable");
        }
        if (err == NO_ERROR) {
            //處理drawables中的資源
            //我們分析如何將收集到一個AaptAsset中的資源文件信息分類重新由函數makeFileResources組織到一個ResourceTable對象
            //中去,這些資源文件的信息最終組織在Package, Type, Entry, Item中,Package代表當前編譯APK的包信息,
            //Type類保存資源類型信息, Entry代表保存資源文件,Item保存文件中屬性信息. Package包含Type, Type包含Entry,
            //Entry包含Item.
            err = makeFileResources(bundle, assets, &table, drawables, "drawable");
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        } else {
            hasErrors = true;
        }
    }

    if (mipmaps != NULL) {
        if (bundle->getOutputAPKFile() != NULL) {
            err = preProcessImages(bundle, assets, mipmaps, "mipmap");
        }
        if (err == NO_ERROR) {
            err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap");
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        } else {
            hasErrors = true;
        }
    }

    if (layouts != NULL) {
        err = makeFileResources(bundle, assets, &table, layouts, "layout");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (anims != NULL) {
        err = makeFileResources(bundle, assets, &table, anims, "anim");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (animators != NULL) {
        err = makeFileResources(bundle, assets, &table, animators, "animator");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (transitions != NULL) {
        err = makeFileResources(bundle, assets, &table, transitions, "transition");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (interpolators != NULL) {
        err = makeFileResources(bundle, assets, &table, interpolators, "interpolator");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (xmls != NULL) {
        err = makeFileResources(bundle, assets, &table, xmls, "xml");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (raws != NULL) {
        err = makeFileResources(bundle, assets, &table, raws, "raw");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    // compile resources
    current = assets;
    while(current.get()) {
        KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
                current->getResources();

        ssize_t index = resources->indexOfKey(String8("values"));
        if (index >= 0) {
            ResourceDirIterator it(resources->valueAt(index), String8("values"));
            ssize_t res;
            while ((res=it.next()) == NO_ERROR) {
                sp<AaptFile> file = it.getFile();
                //對於values則是由這個獨立的函數進行組織的,將解析完的數據保存在變量table中
                res = compileResourceFile(bundle, assets, file, it.getParams(), 
                                          (current!=assets), &table);
                if (res != NO_ERROR) {
                    hasErrors = true;
                }
            }
        }
        current = current->getOverlay();
    }


    if (colors != NULL) {
        err = makeFileResources(bundle, assets, &table, colors, "color");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    if (menus != NULL) {
        err = makeFileResources(bundle, assets, &table, menus, "menu");
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }

    // --------------------------------------------------------------------
    // Assignment of resource IDs and initial generation of resource table.
    // --------------------------------------------------------------------


    //到目前爲止上面的工作我們將當前正在編譯的應用程序所依賴的所有資源文件信息(包括系統android.jar中的和
    //應用程序自身的被收集到一個AaptAsset類對象中的)都收集到了一個ResourceTable對象中去了,
    //接下來buildResources函數的工作是爲這些資源文件中的各種屬性分配資源ID


    //下面我們就開始分配Bag資源ID
    // 調用ResourceTable類的成員函數assignResourceIds分配bag資源ID信息
    if (table.hasResources()) {
        err = table.assignResourceIds();
        if (err < NO_ERROR) {
            return err;
        }
    }

    // --------------------------------------------------------------
    // Finally, we can now we can compile XML files, which may reference
    // resources.
    // --------------------------------------------------------------

    // 最後我們將要編譯XML文件,這樣我們就能引用資源
    if (layouts != NULL) {
        ResourceDirIterator it(layouts, String8("layout"));
        while ((err=it.next()) == NO_ERROR) {
            String8 src = it.getFile()->getPrintableSource();
            //對於對於anim, animator, interpolator, xml, color, menu, drawable中的xml文件都是通過compileXmlFile函數進行編譯的.
            //在這裏面用XMLNode::assignResourceIds裏面給每個屬性賦值
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err == NO_ERROR) {
                ResXMLTree block;
                //將編譯後的信息組織到ResXMLTree中去 
                block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
                //檢驗分配的ID是否正確
                checkForIds(src, block);
            } else {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (anims != NULL) {
        ResourceDirIterator it(anims, String8("anim"));
        while ((err=it.next()) == NO_ERROR) {
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (animators != NULL) {
        ResourceDirIterator it(animators, String8("animator"));
        while ((err=it.next()) == NO_ERROR) {
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (interpolators != NULL) {
        ResourceDirIterator it(interpolators, String8("interpolator"));
        while ((err=it.next()) == NO_ERROR) {
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (transitions != NULL) {
        ResourceDirIterator it(transitions, String8("transition"));
        while ((err=it.next()) == NO_ERROR) {
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (xmls != NULL) {
        ResourceDirIterator it(xmls, String8("xml"));
        while ((err=it.next()) == NO_ERROR) {
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (drawables != NULL) {
        ResourceDirIterator it(drawables, String8("drawable"));
        while ((err=it.next()) == NO_ERROR) {
            err = postProcessImage(bundle, assets, &table, it.getFile());
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (colors != NULL) {
        ResourceDirIterator it(colors, String8("color"));
        while ((err=it.next()) == NO_ERROR) {
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err != NO_ERROR) {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    if (menus != NULL) {
        ResourceDirIterator it(menus, String8("menu"));
        while ((err=it.next()) == NO_ERROR) {
            String8 src = it.getFile()->getPrintableSource();
            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
                    it.getFile(), &table, xmlFlags);
            if (err == NO_ERROR) {
                ResXMLTree block;
                block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
                checkForIds(src, block);
            } else {
                hasErrors = true;
            }
        }

        if (err < NO_ERROR) {
            hasErrors = true;
        }
        err = NO_ERROR;
    }

    // Now compile any generated resources.
    std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
    while (!workQueue.empty()) {
        CompileResourceWorkItem& workItem = workQueue.front();
        err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
        if (err == NO_ERROR) {
            assets->addResource(workItem.resPath.getPathLeaf(),
                    workItem.resPath,
                    workItem.file,
                    workItem.file->getResourceType());
        } else {
            hasErrors = true;
        }
        workQueue.pop();
    }

    if (table.validateLocalizations()) {
        hasErrors = true;
    }

    if (hasErrors) {
        return UNKNOWN_ERROR;
    }

    // If we're not overriding the platform build versions,
    // extract them from the platform APK.
    if (packageType != ResourceTable::System &&
            (bundle->getPlatformBuildVersionCode() == "" ||
            bundle->getPlatformBuildVersionName() == "")) {
        err = extractPlatformBuildVersion(assets->getAssetManager(), bundle);
        if (err != NO_ERROR) {
            return UNKNOWN_ERROR;
        }
    }

    //下面這些代碼是產生經過flatten的AndroidManifest.xml文件

    // 取出AndroidManifest.xml文件
    const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
    String8 manifestPath(manifestFile->getPrintableSource());

    // Generate final compiled manifest file.
    //清空manifestFile所指向的AndroidManfiest.xml的信息,然後重新解析
    manifestFile->clearData();
    sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
    if (manifestTree == NULL) {
        return UNKNOWN_ERROR;
    }
    //檢測是否AndroidManifest.xml中是否有overlay資源,如果有就將現有資源替換
    err = massageManifest(bundle, manifestTree);
    if (err < NO_ERROR) {
        return err;
    }
    //編譯AndroidManifest.xml文件
    err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table);
    if (err < NO_ERROR) {
        return err;
    }

    if (table.modifyForCompat(bundle) != NO_ERROR) {
        return UNKNOWN_ERROR;
    }

    //block.restart();
    //printXMLBlock(&block);

    // --------------------------------------------------------------
    // Generate the final resource table.
    // Re-flatten because we may have added new resource IDs
    // --------------------------------------------------------------


    ResTable finalResTable;
    sp<AaptFile> resFile;



    if (table.hasResources()) {
        //生成資源符號表
        sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R"));
        err = table.addSymbols(symbols, bundle->getSkipSymbolsWithoutDefaultLocalization());
        if (err < NO_ERROR) {
            return err;
        }

        KeyedVector<Symbol, Vector<SymbolDefinition> > densityVaryingResources;
        if (builder->getSplits().size() > 1) {
            // Only look for density varying resources if we're generating
            // splits.
            table.getDensityVaryingResources(densityVaryingResources);
        }

        Vector<sp<ApkSplit> >& splits = builder->getSplits();
        const size_t numSplits = splits.size();
        for (size_t i = 0; i < numSplits; i++) {
            sp<ApkSplit>& split = splits.editItemAt(i);
            // 生成資源索引表
            sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"),
                    AaptGroupEntry(), String8());
            //ResourceTable::flatten用於生成資源索引表resources.arsc
            err = table.flatten(bundle, split->getResourceFilter(),
                    flattenedTable, split->isBase());
            if (err != NO_ERROR) {
                fprintf(stderr, "Failed to generate resource table for split '%s'\n",
                        split->getPrintableName().string());
                return err;
            }
            split->addEntry(String8("resources.arsc"), flattenedTable);

            if (split->isBase()) {
                resFile = flattenedTable;
                err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize());
                if (err != NO_ERROR) {
                    fprintf(stderr, "Generated resource table is corrupt.\n");
                    return err;
                }
            } else {
                ResTable resTable;
                err = resTable.add(flattenedTable->getData(), flattenedTable->getSize());
                if (err != NO_ERROR) {
                    fprintf(stderr, "Generated resource table for split '%s' is corrupt.\n",
                            split->getPrintableName().string());
                    return err;
                }

                bool hasError = false;
                const std::set<ConfigDescription>& splitConfigs = split->getConfigs();
                for (std::set<ConfigDescription>::const_iterator iter = splitConfigs.begin();
                        iter != splitConfigs.end();
                        ++iter) {
                    const ConfigDescription& config = *iter;
                    if (AaptConfig::isDensityOnly(config)) {
                        // Each density only split must contain all
                        // density only resources.
                        Res_value val;
                        resTable.setParameters(&config);
                        const size_t densityVaryingResourceCount = densityVaryingResources.size();
                        for (size_t k = 0; k < densityVaryingResourceCount; k++) {
                            const Symbol& symbol = densityVaryingResources.keyAt(k);
                            ssize_t block = resTable.getResource(symbol.id, &val, true);
                            if (block < 0) {
                                // Maybe it's in the base?
                                finalResTable.setParameters(&config);
                                block = finalResTable.getResource(symbol.id, &val, true);
                            }

                            if (block < 0) {
                                hasError = true;
                                SourcePos().error("%s has no definition for density split '%s'",
                                        symbol.toString().string(), config.toString().string());

                                if (bundle->getVerbose()) {
                                    const Vector<SymbolDefinition>& defs = densityVaryingResources[k];
                                    const size_t defCount = std::min(size_t(5), defs.size());
                                    for (size_t d = 0; d < defCount; d++) {
                                        const SymbolDefinition& def = defs[d];
                                        def.source.error("%s has definition for %s",
                                                symbol.toString().string(), def.config.toString().string());
                                    }

                                    if (defCount < defs.size()) {
                                        SourcePos().error("and %d more ...", (int) (defs.size() - defCount));
                                    }
                                }
                            }
                        }
                    }
                }

                if (hasError) {
                    return UNKNOWN_ERROR;
                }

                // Generate the AndroidManifest for this split.
                sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"),
                        AaptGroupEntry(), String8());
                err = generateAndroidManifestForSplit(bundle, assets, split,
                        generatedManifest, &table);
                if (err != NO_ERROR) {
                    fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n",
                            split->getPrintableName().string());
                    return err;
                }
                split->addEntry(String8("AndroidManifest.xml"), generatedManifest);
            }
        }

        if (bundle->getPublicOutputFile()) {
            FILE* fp = fopen(bundle->getPublicOutputFile(), "w+");
            if (fp == NULL) {
                fprintf(stderr, "ERROR: Unable to open public definitions output file %s: %s\n",
                        (const char*)bundle->getPublicOutputFile(), strerror(errno));
                return UNKNOWN_ERROR;
            }
            if (bundle->getVerbose()) {
                printf("  Writing public definitions to %s.\n", bundle->getPublicOutputFile());
            }
            table.writePublicDefinitions(String16(assets->getPackage()), fp);
            fclose(fp);
        }

        if (finalResTable.getTableCount() == 0 || resFile == NULL) {
            fprintf(stderr, "No resource table was generated.\n");
            return UNKNOWN_ERROR;
        }
    }

    // Perform a basic validation of the manifest file.  This time we
    // parse it with the comments intact, so that we can use them to
    // generate java docs...  so we are not going to write this one
    // back out to the final manifest data.
    sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
            manifestFile->getGroupEntry(),
            manifestFile->getResourceType());
    err = compileXmlFile(bundle, assets, String16(), manifestFile,
            outManifestFile, &table,
            XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
            | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
    if (err < NO_ERROR) {
        return err;
    }
    ResXMLTree block;
    block.setTo(outManifestFile->getData(), outManifestFile->getSize(), true);
    String16 manifest16("manifest");
    String16 permission16("permission");
    String16 permission_group16("permission-group");
    String16 uses_permission16("uses-permission");
    String16 instrumentation16("instrumentation");
    String16 application16("application");
    String16 provider16("provider");
    String16 service16("service");
    String16 receiver16("receiver");
    String16 activity16("activity");
    String16 action16("action");
    String16 category16("category");
    String16 data16("scheme");
    String16 feature_group16("feature-group");
    String16 uses_feature16("uses-feature");
    const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789";
    const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
    const char* classIdentChars = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789$";
    const char* processIdentChars = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:";
    const char* authoritiesIdentChars = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-:;";
    const char* typeIdentChars = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789:-/*+";
    const char* schemeIdentChars = "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789-";
    ResXMLTree::event_code_t code;
    sp<AaptSymbols> permissionSymbols;
    sp<AaptSymbols> permissionGroupSymbols;
    while ((code=block.next()) != ResXMLTree::END_DOCUMENT
           && code > ResXMLTree::BAD_DOCUMENT) {
        if (code == ResXMLTree::START_TAG) {
            size_t len;
            if (block.getElementNamespace(&len) != NULL) {
                continue;
            }
            if (strcmp16(block.getElementName(&len), manifest16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block, NULL, "package",
                                 packageIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "sharedUserId", packageIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), permission16.string()) == 0
                    || strcmp16(block.getElementName(&len), permission_group16.string()) == 0) {
                const bool isGroup = strcmp16(block.getElementName(&len),
                        permission_group16.string()) == 0;
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "name", isGroup ? packageIdentCharsWithTheStupid
                                 : packageIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                SourcePos srcPos(manifestPath, block.getLineNumber());
                sp<AaptSymbols> syms;
                if (!isGroup) {
                    syms = permissionSymbols;
                    if (syms == NULL) {
                        sp<AaptSymbols> symbols =
                                assets->getSymbolsFor(String8("Manifest"));
                        syms = permissionSymbols = symbols->addNestedSymbol(
                                String8("permission"), srcPos);
                    }
                } else {
                    syms = permissionGroupSymbols;
                    if (syms == NULL) {
                        sp<AaptSymbols> symbols =
                                assets->getSymbolsFor(String8("Manifest"));
                        syms = permissionGroupSymbols = symbols->addNestedSymbol(
                                String8("permission_group"), srcPos);
                    }
                }
                size_t len;
                ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name");
                const char16_t* id = block.getAttributeStringValue(index, &len);
                if (id == NULL) {
                    fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", 
                            manifestPath.string(), block.getLineNumber(),
                            String8(block.getElementName(&len)).string());
                    hasErrors = true;
                    break;
                }
                String8 idStr(id);
                char* p = idStr.lockBuffer(idStr.size());
                char* e = p + idStr.size();
                bool begins_with_digit = true;  // init to true so an empty string fails
                while (e > p) {
                    e--;
                    if (*e >= '0' && *e <= '9') {
                      begins_with_digit = true;
                      continue;
                    }
                    if ((*e >= 'a' && *e <= 'z') ||
                        (*e >= 'A' && *e <= 'Z') ||
                        (*e == '_')) {
                      begins_with_digit = false;
                      continue;
                    }
                    if (isGroup && (*e == '-')) {
                        *e = '_';
                        begins_with_digit = false;
                        continue;
                    }
                    e++;
                    break;
                }
                idStr.unlockBuffer();
                // verify that we stopped because we hit a period or
                // the beginning of the string, and that the
                // identifier didn't begin with a digit.
                if (begins_with_digit || (e != p && *(e-1) != '.')) {
                  fprintf(stderr,
                          "%s:%d: Permission name <%s> is not a valid Java symbol\n",
                          manifestPath.string(), block.getLineNumber(), idStr.string());
                  hasErrors = true;
                }
                syms->addStringSymbol(String8(e), idStr, srcPos);
                const char16_t* cmt = block.getComment(&len);
                if (cmt != NULL && *cmt != 0) {
                    //printf("Comment of %s: %s\n", String8(e).string(),
                    //        String8(cmt).string());
                    syms->appendComment(String8(e), String16(cmt), srcPos);
                } else {
                    //printf("No comment for %s\n", String8(e).string());
                }
                syms->makeSymbolPublic(String8(e), srcPos);
            } else if (strcmp16(block.getElementName(&len), uses_permission16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "name", packageIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), instrumentation16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "name", classIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "targetPackage",
                                 packageIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), application16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "name", classIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "permission",
                                 packageIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "process",
                                 processIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
                                 processIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), provider16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "name", classIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "authorities",
                                 authoritiesIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "permission",
                                 packageIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "process",
                                 processIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), service16.string()) == 0
                       || strcmp16(block.getElementName(&len), receiver16.string()) == 0
                       || strcmp16(block.getElementName(&len), activity16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block, RESOURCES_ANDROID_NAMESPACE,
                                 "name", classIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "permission",
                                 packageIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "process",
                                 processIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "taskAffinity",
                                 processIdentChars, false) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), action16.string()) == 0
                       || strcmp16(block.getElementName(&len), category16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "name",
                                 packageIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), data16.string()) == 0) {
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "mimeType",
                                 typeIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
                if (validateAttr(manifestPath, finalResTable, block,
                                 RESOURCES_ANDROID_NAMESPACE, "scheme",
                                 schemeIdentChars, true) != ATTR_OKAY) {
                    hasErrors = true;
                }
            } else if (strcmp16(block.getElementName(&len), feature_group16.string()) == 0) {
                int depth = 1;
                while ((code=block.next()) != ResXMLTree::END_DOCUMENT
                       && code > ResXMLTree::BAD_DOCUMENT) {
                    if (code == ResXMLTree::START_TAG) {
                        depth++;
                        if (strcmp16(block.getElementName(&len), uses_feature16.string()) == 0) {
                            ssize_t idx = block.indexOfAttribute(
                                    RESOURCES_ANDROID_NAMESPACE, "required");
                            if (idx < 0) {
                                continue;
                            }

                            int32_t data = block.getAttributeData(idx);
                            if (data == 0) {
                                fprintf(stderr, "%s:%d: Tag <uses-feature> can not have "
                                        "android:required=\"false\" when inside a "
                                        "<feature-group> tag.\n",
                                        manifestPath.string(), block.getLineNumber());
                                hasErrors = true;
                            }
                        }
                    } else if (code == ResXMLTree::END_TAG) {
                        depth--;
                        if (depth == 0) {
                            break;
                        }
                    }
                }
            }
        }
    }

    if (hasErrors) {
        return UNKNOWN_ERROR;
    }

    if (resFile != NULL) {
        // These resources are now considered to be a part of the included
        // resources, for others to reference.
        err = assets->addIncludedResources(resFile);
        if (err < NO_ERROR) {
            fprintf(stderr, "ERROR: Unable to parse generated resources, aborting.\n");
            return err;
        }
    }

    return err;
}
  • 首先從AaptAssets的成員變量mFiles中拿到AndroidManifest.xml路徑
  • 解析AndroidManifest.xml文件
  • 創建一個ResourceTable對象構造中傳入AndroidManifest.xml的包名
  • 添加被引用的資源包,比如android.jar中那些系統提前編譯好的資源文件
  • 設置U8編碼方式
  • 將assets各類資源文件重新收集到resources中KeyedVector<String8, sp<ResourceTypeSet> >*resources = new KeyedVector<String8, sp<ResourceTypeSet> >;
  • 定義各種資源文件容器
  • 將保存到resources中的各類文件set到我們的容器中
  • 判斷是不是Overlay鏈表存在,如果存在則遍歷將所有資源進行收集到resources中,如果有這些資源就進行替換當前set到容器中的資源。
  • 然後進行drawable,mipmap,layout等等這些文件中R文件的生成當R文件生成
  • 由於values特殊性則需要使用compileResourceFile單獨進行解析生成R文件
  • 以上所有的內容均保存在ResourceTable對象中
  • 然後我們就開始分配bag Idtable.assignResourceIds();
  • 然後編譯xml文件,由於前面收集到了資源,所以後面就可以用
  • 將AndroidManifest.xml文件進行flatten檢測AndroidManifest.xml文件是否有overlay資源如果有就覆蓋
  • 編譯AndroidManifest.xml文件
  • 生成資源符號表,生成資源索引表new AaptFile(String8("resources.arsc")
  • ResourceTable::flatten用於生成資源索引表resources.arsc

解析AndroidManifest.xml文件

status_t err = parsePackage(bundle, assets, androidManifestFile);
static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets,
    const sp<AaptGroup>& grp)
{
    //以下代碼確保只有一個AndroidManifest.xml文件
    if (grp->getFiles().size() != 1) {
        fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n",
                grp->getFiles().valueAt(0)->getPrintableSource().string());
    }
    // 取出存放AndroidManifest.xml文件信息的AaptFile對象
    sp<AaptFile> file = grp->getFiles().valueAt(0);

    //定義一個ResXMLTree對象,然後調用parseXMLResource來詳細解析AndroidManifest.xml文件
    //這個函數主要完成三個工作:
    //1. 收集file文件指向的xml文件中字符串資源信息.
    //2. 壓平該file文件只想的xml文件中資源信息
    //3. 將前兩步組織的到的資源信息最終組織到一個ResXMLTree所描述的數據結構中.

    ResXMLTree block;
    status_t err = parseXMLResource(file, &block);
    if (err != NO_ERROR) {
        return err;
    }
    //printXMLBlock(&block);

    ResXMLTree::event_code_t code;
    //下列while循環找到起開始位置
    while ((code=block.next()) != ResXMLTree::START_TAG
           && code != ResXMLTree::END_DOCUMENT
           && code != ResXMLTree::BAD_DOCUMENT) {
    }

    size_t len;
    if (code != ResXMLTree::START_TAG) {
        fprintf(stderr, "%s:%d: No start tag found\n",
                file->getPrintableSource().string(), block.getLineNumber());
        return UNKNOWN_ERROR;
    }
    //首先找到manifest標籤
    if (strcmp16(block.getElementName(&len), String16("manifest").string()) != 0) {
        fprintf(stderr, "%s:%d: Invalid start tag %s, expected <manifest>\n",
                file->getPrintableSource().string(), block.getLineNumber(),
                String8(block.getElementName(&len)).string());
        return UNKNOWN_ERROR;
    }
    //再找到pacakge標籤屬性所在block中的索引位置
    ssize_t nameIndex = block.indexOfAttribute(NULL, "package");
    if (nameIndex < 0) {
        fprintf(stderr, "%s:%d: <manifest> does not have package attribute.\n",
                file->getPrintableSource().string(), block.getLineNumber());
        return UNKNOWN_ERROR;
    }

    assets->setPackage(String8(block.getAttributeStringValue(nameIndex, &len)));

    ssize_t revisionCodeIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "revisionCode");
    if (revisionCodeIndex >= 0) {
        bundle->setRevisionCode(String8(block.getAttributeStringValue(revisionCodeIndex, &len)).string());
    }
    //獲取正在編譯的資源的包名,並設置將其保存到assets的成員變量mPackage中
    String16 uses_sdk16("uses-sdk");
    //找到SDK版本並設置minSdkVersion
    while ((code=block.next()) != ResXMLTree::END_DOCUMENT
           && code != ResXMLTree::BAD_DOCUMENT) {
        if (code == ResXMLTree::START_TAG) {
            if (strcmp16(block.getElementName(&len), uses_sdk16.string()) == 0) {
                ssize_t minSdkIndex = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE,
                                                             "minSdkVersion");
                if (minSdkIndex >= 0) {
                    const char16_t* minSdk16 = block.getAttributeStringValue(minSdkIndex, &len);
                    const char* minSdk8 = strdup(String8(minSdk16).string());
                    bundle->setManifestMinSdkVersion(minSdk8);
                }
            }
        }
    }

    return NO_ERROR;
}
  • 解析AndroidManifest.xml文件
  • 首先找到manifest標籤
  • 再找到pacakge標籤屬性所在block中的索引位置
  • 找到SDK版本並設置minSdkVersion
ResXMLTree block;
status_t err = parseXMLResource(file, &block);

其中file就是AndroidManifest.xml文件信息的AaptFile對象
block是新建的ResXMLTree

status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree,
                          bool stripAll, bool keepComments,
                          const char** cDataTags)
{
    //接着調用XMLNode的成員函數parse來解析AndroidManifest.xml文件
    sp<XMLNode> root = XMLNode::parse(file);
    if (root == NULL) {
        return UNKNOWN_ERROR;
    }
    root->removeWhitespace(stripAll, cDataTags);

    if (kIsDebug) {
        printf("Input XML from %s:\n", (const char*)file->getPrintableSource());
        root->print();
    }
    //新建一個AaptFile作爲輸出文件
    sp<AaptFile> rsc = new AaptFile(String8(), AaptGroupEntry(), String8());

    //調用flatten函數壓平AndroidManifest.xml文件, 將壓平後的xml文件信息按指定
    //格式組織在rsc的數據緩衝區中,我們在flatten函數中將各種屬性信息組織成如下方式並保存在一個AdaptFile對象中
    status_t err = root->flatten(rsc, !keepComments, false);
    if (err != NO_ERROR) {
        return err;
    }
    //保存在AdaptFile對象rsc中的數據組織到outTree中
    err = outTree->setTo(rsc->getData(), rsc->getSize(), true);
    if (err != NO_ERROR) {
        return err;
    }

    if (kIsDebug) {
        printf("Output XML:\n");
        printXMLBlock(outTree);
    }

    return NO_ERROR;
}
  • 使用XMLNode::parse(file);解析
  • 新建一個AaptFile作爲輸出文件
  • 調用flatten函數壓平AndroidManifest.xml文件, 將壓平後的xml文件信息按指定格式組織在rsc的數據緩衝區中,我們在flatten函數中將各種屬性信息組織成如下方式並保存在一個AdaptFile對象中
  • 將輸出文件AaptFile保存在AdaptFile對象rsc中的數據組織到outTree中
sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file)
{
    char buf[16384];
    //以只讀方式打開AndroidManifest.xml文件 
    int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY);
    if (fd < 0) {
        SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s",
                strerror(errno));
        return NULL;
    }
    //創建一個XML文件解析器, 該解析器是定義在expat庫中的
    //Expat 是一個用C語言開發的、用來解析XML文檔的開發庫,它最初是開源的、
    //Mozilla 項目下的一個XML解析器。採用流的方式來解析XML文件,並且基於
    //事件通知型來調用分析到的數據,並不需要把所有XML文件全部加載到內存裏,
    //這樣可以分析非常大的XML文件。


    //1.創建一個XML分析器。
    XML_Parser parser = XML_ParserCreateNS(NULL, 1);
    ParseState state;
    state.filename = file->getPrintableSource();
    state.parser = parser;
    //設置用戶數據
    XML_SetUserData(parser, &state);
    //2.第一個參數是那個Parser句柄,第二個和第三個參數則是整個Parser的核心,類型爲CallBack的函數
    XML_SetElementHandler(parser, startElement, endElement);

    /* startNamespace: 解析xmlns:android開頭的信息:
     ** 參數: prefix = android, uri= android右邊的屬性值信息"http://schemas.android.com/apk/res/android"
     ** endNamespace - 銷燬ParseState中緩存的數據
     ** 這個特殊的xmlns:android="http://schemas.android.com/apk/res/android"
     ** 屬性名和屬性值會創建一個XMLNode作爲根節點, 其也叫做命名空間
     ** 解析命名空間和標籤元素類似,就不再贅述 */
    XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);

    /* 函數是設置處理一個<>和</>之間的字段的回調 
     ** <Item>This is a normal text</Item>   
     ** 那麼字符串“This is a normal text”就稱爲一個CDATA */
    XML_SetCharacterDataHandler(parser, characterData);

    /* 處理註釋的函數 */
    XML_SetCommentHandler(parser, commentData);

    ssize_t len;
    bool done;
    do {
        len = read(fd, buf, sizeof(buf));
        done = len < (ssize_t)sizeof(buf);
        if (len < 0) {
            SourcePos(file->getSourceFile(), -1).error("Error reading file: %s\n", strerror(errno));
            close(fd);
            return NULL;
        }

        /* 第二個參數是用戶指定的Buffer指針, 
        ** 第三個是這塊Buffer中實際內容的字節數
        ** 最後參數代表是否這塊Buffer已經結束。
        ** 比如要解析的XML文件太大,但內存比較吃緊,Buffer比較小,則可以循環讀取文件,然後丟給Parser,
        ** 在文件讀取結束前,isFinal參數爲FALSE,反之爲TRUE。 */

        if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR) {
            SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error(
                    "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser)));
            close(fd);
            return NULL;
        }
    } while (!done);

    XML_ParserFree(parser);//銷燬一個解析器
    if (state.root == NULL) {
        SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing");
    }
    close(fd);
    return state.root;
}
  • 打開AndroidManifest.xml文件 &&只讀
  • 創建一個XML分析器。
  • 設置解析回調
  • 進行解析XML_Parse(parser, buf, len, done)
void XMLCALL
XMLNode::startElement(void *userData, const char *name, const char **atts)
{
    if (kIsDebugParse) {
        printf("Start Element: %s\n", name);
    }
    ParseState* st = (ParseState*)userData;
    String16 ns16, name16;
    splitName(name, &ns16, &name16);

    //爲每一個名稱爲name的標籤創建一個XMLNode對象 
    sp<XMLNode> node = XMLNode::newElement(st->filename, ns16, name16);
    //設置標籤開始的行號
    node->setStartLineNumber(XML_GetCurrentLineNumber(st->parser));
    if (st->pendingComment.size() > 0) {
        node->appendComment(st->pendingComment);
        st->pendingComment = String16();
    }
    if (st->stack.size() > 0) {
        //而name子標籤作爲name標籤XMLNode對象的一個
        //子對象保存到XMLNode的成員變量mChildren(Vector)中, 而ParseState 
        //只是用於緩存XMLNode數據信息用,緩存完成之後隨即在endElement函數中銷燬.
        st->stack.itemAt(st->stack.size()-1)->addChild(node);
    } else {
        st->root = node;//根節點是命名空間節點
    }
    st->stack.push(node);//緩存

    for (int i = 0; atts[i]; i += 2) {
        splitName(atts[i], &ns16, &name16);
        node->addAttribute(ns16, name16, String16(atts[i+1]));
    }
}

處理解析的數據
解析的規則就是:

/* startNamespace: 解析xmlns:android開頭的信息:
 ** 參數: prefix = android, uri= android右邊的屬性值信息"http://schemas.android.com/apk/res/android"
 ** endNamespace - 銷燬ParseState中緩存的數據
 ** 這個特殊的xmlns:android="http://schemas.android.com/apk/res/android"
 ** 屬性名和屬性值會創建一個XMLNode作爲根節點, 其也叫做命名空間
 ** 解析命名空間和標籤元素類似,就不再贅述 */

最後一個個XMLNode就掛載到了根節點。

我們現在將AndroidManifest.xml數據已經解析成一個個XMLNode了,現在要通過flatten進行處理,就看下面怎麼處理。

status_t XMLNode::flatten(const sp<AaptFile>& dest,
        bool stripComments, bool stripRawValues) const
{
    //創建一個字符串池StringPool變量strings 保存屬性名稱字符串
    StringPool strings(mUTF8);
    Vector<uint32_t> resids;//保存屬性名的資源ID號

    // First collect just the strings for attribute names that have a
    // resource ID assigned to them.  This ensures that the resource ID
    // array is compact, and makes it easier to deal with attribute names
    // in different namespaces (and thus with different resource IDs).



    //首先收集屬性名字符串,這些字符串有一個資源ID指向它們. 
    //這確保資源ID數組緊湊的,並且不同於命名空間的資源ID,
    //這使得處理屬性名變得更簡單
    //注意:在這裏實際上什麼工作都沒有做!!FUCK
    collect_resid_strings(&strings, &resids);

    // Next collect all remainibng strings.
    //真正的收集工作在這裏才工作,上面什麼工作都沒做還遞歸遍歷半天
    collect_strings(&strings, &resids, stripComments, stripRawValues);


    //收集到的屬性信息都保存在strings所指向的字符串資源池,現在爲這些字符串資源池中的屬性信息分配字符串塊
    sp<AaptFile> stringPool = strings.createStringBlock();

    //接着創建一個ResXMLTree_header
    ResXMLTree_header header;
    memset(&header, 0, sizeof(header));
    header.header.type = htods(RES_XML_TYPE);
    header.header.headerSize = htods(sizeof(header));

    const size_t basePos = dest->getSize();


    //在匿名AdaptFile對象dest中先寫入一個header對象用於記錄信息,接着
    //將我們上面組織好的二進制xml字符串信息內存緩衝塊中數據寫入這個
    //匿名AdaptFile對象dest中的緩衝區去
    dest->writeData(&header, sizeof(header));
    dest->writeData(stringPool->getData(), stringPool->getSize());

    // If we have resource IDs, write them.
    //如果已經分配了資源ID則先寫入一個記錄資源ID信息的ResChunk_header頭,
    //然後將資源ID的信息寫入,但是這裏尚未對任何資源分配資源ID.
    if (resids.size() > 0) {
        const size_t resIdsPos = dest->getSize();
        const size_t resIdsSize =
            sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
        ResChunk_header* idsHeader = (ResChunk_header*)
            (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
        idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
        idsHeader->headerSize = htods(sizeof(*idsHeader));
        idsHeader->size = htodl(resIdsSize);
        uint32_t* ids = (uint32_t*)(idsHeader+1);
        for (size_t i=0; i<resids.size(); i++) {
            *ids++ = htodl(resids[i]);
        }
    }
    //調用flatten_node函數繼續組織收集到的xml文件中的信息
    flatten_node(strings, dest, stripComments, stripRawValues);
    //最後,再寫入一個ResXMLTree_header標記寫入工作完成並記錄上次寫入這類
    //header到剛剛創建的header之間寫入的數據信息
    void* data = dest->editData();
    ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
    hd->header.size = htodl(dest->getSize()-basePos);

    if (kPrintStringMetrics) {
        fprintf(stderr, "**** total xml size: %zu / %zu%% strings (in %s)\n",
                dest->getSize(), (stringPool->getSize()*100)/dest->getSize(),
                dest->getPath().string());
    }

    return NO_ERROR;
}

這裏首先要了解一下概念

class XMLNode : public RefBase  
{  
    ......  

private:  
    ......  

    String16 mElementName;  //表示Xml元素標籤。
    Vector<sp<XMLNode> > mChildren;  //表示Xml元素的子元素。
    Vector<attribute_entry> mAttributes;  //表示Xml元素的屬性列表。
    ......  
    String16 mChars;  //表示Xml元素的文本內容。
    ......  
};  

Xml文件解析完成之後,就可以得到一個用來描述根節點的XMLNode,接下來就可以通過這個根節點來完成其它的編譯操作。

這一步實際上就是給每一個Xml元素的屬性名稱都賦予資源ID。例如,對於main.xml文件的根節點LinearLayout來說,就是要分別給它的屬性名稱“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”賦予一個資源ID。注意,上述這些屬性都是在系統資源包裏面定義的,因此,Android資源打包工具首先是要在系統資源包裏面找到這些名稱所對應的資源ID,然後才能賦給main.xml文件的根節點LinearLayout。對於系統資源包來說,“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”等這些屬性名稱是它定義的一系列Bag資源,在它被編譯的時候,就已經分配好資源ID了,每一個Xml文件都是從根節點開始給屬性名稱賦予資源ID,然後再給遞歸給每一個子節點的屬性名稱賦予資源ID,直到每一個節點的屬性名稱都獲得了資源ID爲止。

前面提到,android:orientation是在系統資源包定義的一個Bag資源,這個Bag資源分配有資源ID,而且會指定有元數據,也就是它可以取哪些值。對於android:orientation來說,它的合法取值就爲“horizontal”或者“vertical”。在系統資源包中,“horizontal”或者“vertical”也同樣是一個Bag資源,它們的值分別被定義爲0和1。Android資源打包工具是如何找到main.xml文件的根節點LinearLayout的屬性android:orientation的字符串值“vertical”所對應的整數值1的呢?假設在上一步中,從系統資源包找到“android:orientation”的資源ID爲0x010100c4,那麼Android資源打包工具就會通過這個資源ID找到它的元數據,也就是兩個名稱分別爲“horizontal”和“vertical”的bag,接着就根據字符串匹配到名稱“vertical”的bag,最後就可以將這個bag的值1作爲解析結果了。

注意,對於引用類型的屬性值,要進行一些額外的處理。例如,對於main.xml文件的第一個Button節點的android:id屬性值“@+id/button_start_in_process”,其中,“@”表示後面描述的屬性是引用類型的,“+”表示如果該引用不存在,那麼就新建一個,“id”表示引用的資源類型是id,“button_start_in_process”表示引用的名稱。實際上,在”id”前面,還可以指定一個包名,例如,將main.xml文件的第一個Button節點的android:id屬性值指定爲“@+[package:]id/button_start_in_process” 。如果沒有指定包名的話,那麼就會默認在當前編譯的包裏面查找button_start_in_process這個引用。由於前面指有“+”符號,因此,如果在指定的包裏面找不到button_start_in_process這個引用的話,那麼就會在該包裏面創建一個新的。無論button_start_in_process在指定的包裏面原來就存在的,還是新建的,最終Android資源打包工具都是將它的資源ID作爲解析結果。在我們這個情景中,在解析main.xml文件的兩個Button節點的android:id屬性值“@+id/button_start_in_process”和“@+id/button_start_in_new_process”時,當前正在編譯的資源包沒有包含有相應的引用的,因此,Android資源打包工具就會在當前正在編譯的資源包裏面增加兩個類型爲id的Entry,

image

此外,對於main.xml文件的兩個Button節點的android:text屬性值“@string/start_in_process”和“@string/start_in_new_process”,它們分別表示引用的是當前正在編譯的資源包的名稱分別爲“start_in_process”和“start_in_new_process”的string資源。這兩個string資源在前面的第五步操作中已經編譯過了,因此,這裏就可以直接獲得它們的資源ID。
注意,一個資源項一旦創建之後,要獲得它的資源ID是很容易的,因爲它的Package ID、Type ID和Entry ID都是已知的。

那麼我們如何處理壓平過程呢?

收集有資源ID的屬性的名稱字符串

這一步除了收集那些具有資源ID的Xml元素屬性的名稱字符串之外,還會將對應的資源ID收集起來放在一個數組中。這裏收集到的屬性名稱字符串保存在一個字符串資源池中,它們與收集到的資源ID數組是一一對應的。

collect_strings(&strings, &resids, stripComments, stripRawValues);
if (idx < 0) {
    //尚未將指定的屬性名添加到字符串資源池中,如果add函數後面跟隨
    //着描述字符串屬性的entry_style_span的Vector則將字符串屬性一併
    //加入, 並返回其在字符串資源池中位置
    idx = outPool->add(attr.name);
    if (kIsDebug) {
        printf("Adding attr %s (resid 0x%08x) to pool: idx=%zd\n",
                String8(attr.name).string(), id, SSIZE(idx));
    }
    //判斷是否爲屬性名分配過資源ID
    if (id != 0) {
        // 確保屬性名資源ID與屬性名對應 
        while ((ssize_t)outResIds->size() <= idx) {
            outResIds->add(0);
        }
        // 替換原有資源ID
        outResIds->replaceAt(id, idx);
    }
}

對於main.xml文件來說,具有資源ID的Xml元素屬性的名稱字符串有“orientation”、“layout_width”、“layout_height”、“gravity”、“id”和“text”,假設它們對應的資源ID分別爲0x010100c4、0x010100f4、0x010100f5、0x010100af、0x010100d0和0x0101014f,那麼最終得到的字符串資源池的前6個位置和資源ID數組的對應關係如圖
image

收集其它字符串

image

收集完成之後爲這些字符串資源池中的屬性信息分配字符串塊

//接着創建一個ResXMLTree_header
ResXMLTree_header header;
memset(&header, 0, sizeof(header));
header.header.type = htods(RES_XML_TYPE);
header.header.headerSize = htods(sizeof(header));

const size_t basePos = dest->getSize();


//在匿名AdaptFile對象dest中先寫入一個header對象用於記錄信息,接着
//將我們上面組織好的二進制xml字符串信息內存緩衝塊中數據寫入這個
//匿名AdaptFile對象dest中的緩衝區去
dest->writeData(&header, sizeof(header));
dest->writeData(stringPool->getData(), stringPool->getSize());

上面這塊創建一個頭部,但是並沒有寫入頭部,只是將空的頭部記錄在了AaptFile中下面就要寫入頭部信息

寫入Xml文件頭

最終編譯出來的Xml二進制文件是一系列的chunk組成的,每一個chunk都有一個頭部,用來描述chunk的元信息。同時,整個Xml二進制文件又可以看成一塊總的chunk,它有一個類型爲ResXMLTree_header的頭部。

將頭信息和塊信息寫入AaptFile中

// If we have resource IDs, write them.
//如果已經分配了資源ID則先寫入一個記錄資源ID信息的ResChunk_header頭,
//然後將資源ID的信息寫入,但是這裏尚未對任何資源分配資源ID.
if (resids.size() > 0) {
    const size_t resIdsPos = dest->getSize();
    const size_t resIdsSize =
        sizeof(ResChunk_header)+(sizeof(uint32_t)*resids.size());
    ResChunk_header* idsHeader = (ResChunk_header*)
        (((const uint8_t*)dest->editData(resIdsPos+resIdsSize))+resIdsPos);
    idsHeader->type = htods(RES_XML_RESOURCE_MAP_TYPE);
    idsHeader->headerSize = htods(sizeof(*idsHeader));
    idsHeader->size = htodl(resIdsSize);
    uint32_t* ids = (uint32_t*)(idsHeader+1);
    for (size_t i=0; i<resids.size(); i++) {
        *ids++ = htodl(resids[i]);
    }
}
    //調用flatten_node函數繼續組織收集到的xml文件中的信息
    flatten_node(strings, dest, stripComments, stripRawValues);
status_t XMLNode::flatten_node(const StringPool& strings, const sp<AaptFile>& dest,
        bool stripComments, bool stripRawValues) const
{
    ResXMLTree_node node;
    ResXMLTree_cdataExt cdataExt;
    ResXMLTree_namespaceExt namespaceExt;
    ResXMLTree_attrExt attrExt;
    const void* extData = NULL;
    size_t extSize = 0;
    ResXMLTree_attribute attr;
    bool writeCurrentNode = true;

    //NA和NC分別記錄屬性個數和子標籤個數
    const size_t NA = mAttributes.size();
    const size_t NC = mChildren.size();
    size_t i;

    LOG_ALWAYS_FATAL_IF(NA != mAttributeOrder.size(), "Attributes messed up!");

    const String16 id16("id");
    const String16 class16("class");
    const String16 style16("style");

    const type type = getType();
    // 獲取當前處理的XMLNode所記錄信息的類型初始化一個node變量和attr變量
    memset(&node, 0, sizeof(node));
    memset(&attr, 0, sizeof(attr));
    node.header.headerSize = htods(sizeof(node));
    node.lineNumber = htodl(getStartLineNumber());
    if (!stripComments) {

        //返回註釋字符串在StringPool 的成員變量mValues中的位置
        node.comment.index = htodl(
            mComment.size() > 0 ? strings.offsetForString(mComment) : -1);
        //if (mComment.size() > 0) {
        //  printf("Flattening comment: %s\n", String8(mComment).string());
        //}
    } else {
        node.comment.index = htodl((uint32_t)-1);
    }
    if (type == TYPE_ELEMENT) {
        //設置該node類型
        node.header.type = htods(RES_XML_START_ELEMENT_TYPE);
        //使用attrExt記錄一個attribute額外的信息
        extData = &attrExt;
        extSize = sizeof(attrExt);
        memset(&attrExt, 0, sizeof(attrExt));
        if (mNamespaceUri.size() > 0) {
            attrExt.ns.index = htodl(strings.offsetForString(mNamespaceUri));
        } else {
            attrExt.ns.index = htodl((uint32_t)-1);
        }
        attrExt.name.index = htodl(strings.offsetForString(mElementName));
        attrExt.attributeStart = htods(sizeof(attrExt));
        attrExt.attributeSize = htods(sizeof(attr));
        attrExt.attributeCount = htods(NA);
        attrExt.idIndex = htods(0);
        attrExt.classIndex = htods(0);
        attrExt.styleIndex = htods(0);
        for (i=0; i<NA; i++) {
            ssize_t idx = mAttributeOrder.valueAt(i);
            const attribute_entry& ae = mAttributes.itemAt(idx);
            if (ae.ns.size() == 0) {
                if (ae.name == id16) {
                    attrExt.idIndex = htods(i+1);
                } else if (ae.name == class16) {
                    attrExt.classIndex = htods(i+1);
                } else if (ae.name == style16) {
                    attrExt.styleIndex = htods(i+1);
                }
            }
        }
    } else if (type == TYPE_NAMESPACE) {
        if (mNamespaceUri == RESOURCES_TOOLS_NAMESPACE) {
            writeCurrentNode = false;
        } else {
            node.header.type = htods(RES_XML_START_NAMESPACE_TYPE);
            extData = &namespaceExt;
            extSize = sizeof(namespaceExt);
            memset(&namespaceExt, 0, sizeof(namespaceExt));
            if (mNamespacePrefix.size() > 0) {
                namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
            } else {
                namespaceExt.prefix.index = htodl((uint32_t)-1);
            }
            namespaceExt.prefix.index = htodl(strings.offsetForString(mNamespacePrefix));
            namespaceExt.uri.index = htodl(strings.offsetForString(mNamespaceUri));
        }
        LOG_ALWAYS_FATAL_IF(NA != 0, "Namespace nodes can't have attributes!");
    } else if (type == TYPE_CDATA) {
        node.header.type = htods(RES_XML_CDATA_TYPE);
        extData = &cdataExt;
        extSize = sizeof(cdataExt);
        memset(&cdataExt, 0, sizeof(cdataExt));
        cdataExt.data.index = htodl(strings.offsetForString(mChars));
        cdataExt.typedData.size = htods(sizeof(cdataExt.typedData));
        cdataExt.typedData.res0 = 0;
        cdataExt.typedData.dataType = mCharsValue.dataType;
        cdataExt.typedData.data = htodl(mCharsValue.data);
        LOG_ALWAYS_FATAL_IF(NA != 0, "CDATA nodes can't have attributes!");
    }

    node.header.size = htodl(sizeof(node) + extSize + (sizeof(attr)*NA));
    //初始化完成後將這個node和extData寫入dest所記錄的緩衝區中
    if (writeCurrentNode) {
        dest->writeData(&node, sizeof(node));
        if (extSize > 0) {
            dest->writeData(extData, extSize);
        }
    }
    /* 將一個標籤的沒一個屬性創建一個ResXMLAttribute變量然後按照指定的順序
    ** 組織在dest所描述的緩衝區中, 注意:字符串信息被替換成其在StringPool
    ** 中成員變量中的位置值 */

    for (i=0; i<NA; i++) {
        ssize_t idx = mAttributeOrder.valueAt(i);
        const attribute_entry& ae = mAttributes.itemAt(idx);
        if (ae.ns.size() > 0) {
            attr.ns.index = htodl(strings.offsetForString(ae.ns));
        } else {
            attr.ns.index = htodl((uint32_t)-1);
        }
        attr.name.index = htodl(ae.namePoolIdx);

        if (!stripRawValues || ae.needStringValue()) {
            attr.rawValue.index = htodl(strings.offsetForString(ae.string));
        } else {
            attr.rawValue.index = htodl((uint32_t)-1);
        }
        attr.typedValue.size = htods(sizeof(attr.typedValue));
        if (ae.value.dataType == Res_value::TYPE_NULL
                || ae.value.dataType == Res_value::TYPE_STRING) {
            attr.typedValue.res0 = 0;
            attr.typedValue.dataType = Res_value::TYPE_STRING;
            attr.typedValue.data = htodl(strings.offsetForString(ae.string));
        } else {
            attr.typedValue.res0 = 0;
            attr.typedValue.dataType = ae.value.dataType;
            attr.typedValue.data = htodl(ae.value.data);
        }
        dest->writeData(&attr, sizeof(attr));
    }

    for (i=0; i<NC; i++) {
        status_t err = mChildren.itemAt(i)->flatten_node(strings, dest,
                stripComments, stripRawValues);
        if (err != NO_ERROR) {
            return err;
        }
    }
    //寫入標記數據寫入完成header
    if (type == TYPE_ELEMENT) {
        ResXMLTree_endElementExt endElementExt;
        memset(&endElementExt, 0, sizeof(endElementExt));
        node.header.type = htods(RES_XML_END_ELEMENT_TYPE);
        node.header.size = htodl(sizeof(node)+sizeof(endElementExt));
        node.lineNumber = htodl(getEndLineNumber());
        node.comment.index = htodl((uint32_t)-1);
        endElementExt.ns.index = attrExt.ns.index;
        endElementExt.name.index = attrExt.name.index;
        dest->writeData(&node, sizeof(node));
        dest->writeData(&endElementExt, sizeof(endElementExt));
    } else if (type == TYPE_NAMESPACE) {
        if (writeCurrentNode) {
            node.header.type = htods(RES_XML_END_NAMESPACE_TYPE);
            node.lineNumber = htodl(getEndLineNumber());
            node.comment.index = htodl((uint32_t)-1);
            node.header.size = htodl(sizeof(node)+extSize);
            dest->writeData(&node, sizeof(node));
            dest->writeData(extData, extSize);
        }
    }

    return NO_ERROR;
}

最後,再寫入一個ResXMLTree_header標記寫入工作完成並記錄上次寫入這類
//header到剛剛創建的header之間寫入的數據信息
void* data = dest->editData();
ResXMLTree_header* hd = (ResXMLTree_header*)(((uint8_t*)data)+basePos);
hd->header.size = htodl(dest->getSize()-basePos);

經過上面的步驟ResXMLTree就是已經得到了所有資源的數據。到此爲止解析AndroidManifest.xml文件的工作就完成了。

下面就是創建ResourceTable,創建ResourceTable的目的是將AaptAssets中解析的數據進行轉移。

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

這段代碼是根據bundle的信息判斷是系統的packageType還是app的還是庫的。

創建一個ResourceTable

ResourceTable table(bundle, String16(assets->getPackage()), packageType);

然後就是添加資源包比如android.jar那些

status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets)
{
    //將當前包所依賴系統的android.jar包路徑信息添加到assets的成員變量mIncludedAssets中
    status_t err = assets->buildIncludedResources(bundle);//AaptAssets.cpp
    if (err != NO_ERROR) {
        return err;
    }

    mAssets = assets;// 將ResourceTable類的成員變量mAssets指向assets
    //getIncludedResources:獲取一個ResTable對象用於描述當前APK所引用的android.jar包中的資源信息
    //getIncludedResources在AaptAssets中
    mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);

    const String8& featureAfter = bundle->getFeatureAfterPackage();
    if (!featureAfter.isEmpty()) {
        AssetManager featureAssetManager;
        if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
            fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
                    featureAfter.string());
            return UNKNOWN_ERROR;
        }

        const ResTable& featureTable = featureAssetManager.getResources(false);
        mTypeIdOffset = std::max(mTypeIdOffset,
                findLargestTypeIdForPackage(featureTable, mAssetsPackage)); 
    }

    return NO_ERROR;
}

首先獲取android.jar的路徑

status_t AaptAssets::buildIncludedResources(Bundle* bundle)
{
    if (mHaveIncludedAssets) {
        return NO_ERROR;
    }

    // Add in all includes.
    // 首先獲取我們使用-I選項所制定的android.jar包路徑信息
    const Vector<String8>& includes = bundle->getPackageIncludes();
    const size_t packageIncludeCount = includes.size();

    //將指定的所有android.jar的路徑信息添加到當前對象成員變量
    //mIncludedAssets中, mIncludedAssets的成員變量是一個AssetManager對象
    for (size_t i = 0; i < packageIncludeCount; i++) {
        if (bundle->getVerbose()) {
            printf("Including resources from package: %s\n", includes[i].string());
        }
        //最終調用AssetManager對象的addAssetPath將路徑添加到其成員變量mAssetPaths中
        if (!mIncludedAssets.addAssetPath(includes[i], NULL)) {//AssetManager.cpp
            fprintf(stderr, "ERROR: Asset package include '%s' not found.\n",
                    includes[i].string());
            return UNKNOWN_ERROR;
        }
    }

    const String8& featureOfBase = bundle->getFeatureOfPackage();
    if (!featureOfBase.isEmpty()) {
        if (bundle->getVerbose()) {
            printf("Including base feature resources from package: %s\n",
                    featureOfBase.string());
        }

        if (!mIncludedAssets.addAssetPath(featureOfBase, NULL)) {
            fprintf(stderr, "ERROR: base feature package '%s' not found.\n",
                    featureOfBase.string());
            return UNKNOWN_ERROR;
        }
    }

    mHaveIncludedAssets = true;

    return NO_ERROR;
}

然後添加路徑下的信息

const ResTable& AaptAssets::getIncludedResources() const
{
    //mIncludedAssets是一個AssetManager類
    return mIncludedAssets.getResources(false);
}

路徑在成員變量mIncludedAsse中保存。

const ResTable& AssetManager::getResources(bool required) const
{
    const ResTable* rt = getResTable(required);//getResTable函數是一個Singleton
    return *rt;
}
const ResTable* AssetManager::getResTable(bool required) const
{
    ResTable* rt = mResources;
    if (rt) {
        return rt;
    }

    // Iterate through all asset packages, collecting resources from each.

    AutoMutex _l(mLock);

    if (mResources != NULL) {
        return mResources;
    }

    if (required) {
        LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
    }

    //加載一些chache文件
    if (mCacheMode != CACHE_OFF && !mCacheValid) {
        const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
    }

    mResources = new ResTable();
    updateResourceParamsLocked();//更新mResources的參數

    bool onlyEmptyResources = true;

    //逐個掃描所包含的android.jar文件路徑
    const size_t N = mAssetPaths.size();
    for (size_t i=0; i<N; i++) {
        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
        onlyEmptyResources = onlyEmptyResources && empty;
    }

    if (required && onlyEmptyResources) {
        ALOGW("Unable to find resources file resources.arsc");
        delete mResources;
        mResources = NULL;
    }

    return mResources;
}

挨個掃描所android.jar包中的文件進行如下操作:

bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    // skip those ap's that correspond to system overlays
    if (ap.isSystemOverlay) {
        return true;
    }

    Asset* ass = NULL;
    ResTable* sharedRes = NULL;
    bool shared = true;
    bool onlyEmptyResources = true;
    MY_TRACE_BEGIN(ap.path.string());
    Asset* idmap = openIdmapLocked(ap);
    size_t nextEntryIdx = mResources->getTableCount();
    ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
    //判斷ap所指向的文件類型
    if (ap.type != kFileTypeDirectory) {
        // 如果ap所指向的文件類型不是目錄文件
        if (nextEntryIdx == 0) {
            // The first item is typically the framework resources,
            // which we want to avoid parsing every time.

            //爲第一個指向的android.jar包創建一個對應的SharedZip對象,並將其成員變量mResourceTable返回
            sharedRes = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTable(ap.path);
            //在這裏返回NULL,表示尚未爲該android.jar創建過一個ResTable對象
            if (sharedRes != NULL) {
                // skip ahead the number of system overlay packages preloaded
                //返回對應android.jar包的SharedZip中的mResourceTableAsset對象
                nextEntryIdx = sharedRes->getTableCount();
            }
        }
        if (sharedRes == NULL) {
            ass = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTableAsset(ap.path);
            // 在這裏返回的ass對象爲NULL,表示尚未爲該android.jar創建過一個Asset對象
            if (ass == NULL) {
                ALOGV("loading resource table %s\n", ap.path.string());

                //爲該android.jar包創建一個Asset類對象,AssetManager::openNonAssetInPathLocked
                ass = const_cast<AssetManager*>(this)->
                    openNonAssetInPathLocked("resources.arsc",
                                             Asset::ACCESS_BUFFER,
                                             ap);
                if (ass != NULL && ass != kExcludedAsset) {
                    /* 到這裏我們就爲一個android.jar包創建了一個Asset對象
                    ** 並將其保存到與之對應的SharedZip類對象的
                    ** mResourceTableAsset中 */
                    ass = const_cast<AssetManager*>(this)->
                        mZipSet.setZipResourceTableAsset(ap.path, ass);
                }
            }
            //爲android.jar包創建一個與之對應的ResTable類對象,並將其保存
            //到與之對應的SharedZip的成員變量mResourceTable中
            if (nextEntryIdx == 0 && ass != NULL) {
                // If this is the first resource table in the asset
                // manager, then we are going to cache it so that we
                // can quickly copy it out for others.
                ALOGV("Creating shared resources for %s", ap.path.string());
                sharedRes = new ResTable();
                sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef HAVE_ANDROID_OS
                const char* data = getenv("ANDROID_DATA");
                LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
                String8 overlaysListPath(data);
                overlaysListPath.appendPath(kResourceCache);
                overlaysListPath.appendPath("overlays.list");
                addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.setZipResourceTable(ap.path, sharedRes);
            }
        }
    } else {
    //如果ap指向的是一個目錄文件,則執行如下代碼
        ALOGV("loading resource table %s\n", ap.path.string());
        ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                                     Asset::ACCESS_BUFFER,
                                     ap);
        shared = false;
    }

    //完成了爲一個android.jar包創建一個與之對應的Asset和ResTable對象之後
    //則新建一個ResTable對象用於初始化mResources成員變量
    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
        ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
        if (sharedRes != NULL) {
            ALOGV("Copying existing resources for %s", ap.path.string());
            mResources->add(sharedRes);
        } else {
            ALOGV("Parsing resources for %s", ap.path.string());
            mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
        }
        onlyEmptyResources = false;

        if (!shared) {
            delete ass;
        }
    } else {
        ALOGV("Installing empty resources in to table %p\n", mResources);
        mResources->addEmpty(nextEntryIdx + 1);
    }

    if (idmap != NULL) {
        delete idmap;
    }
    MY_TRACE_END();

    return onlyEmptyResources;
}
  • 爲第一個指向的android.jar創建一個SharedZip對象
  • 爲該android.jar包創建一個Asset類對象並將其保存到與之對應的SharedZip類對象的mResourceTableAsset中

此時mResources(ResTable)就與android.jar對應起來,將ResTable進行返回

Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
    const asset_path& ap)
{
    Asset* pAsset = NULL;

    /* look at the filesystem on disk */
    if (ap.type == kFileTypeDirectory) {
        String8 path(ap.path);
        path.appendPath(fileName);

        pAsset = openAssetFromFileLocked(path, mode);

        if (pAsset == NULL) {
            /* try again, this time with ".gz" */
            path.append(".gz");
            pAsset = openAssetFromFileLocked(path, mode);
        }

        if (pAsset != NULL) {
            //printf("FOUND NA '%s' on disk\n", fileName);
            pAsset->setAssetSource(path);
        }

    /* look inside the zip file */
    } else {
        String8 path(fileName);

        /* check the appropriate Zip file */
        // 返回與fileName對應的SharedZip對象的成員變量mZipFile 
        ZipFileRO* pZip = getZipFileLocked(ap);
        if (pZip != NULL) {
            //printf("GOT zip, checking NA '%s'\n", (const char*) path);
            //尋找一個與android.jar包對應的ZipEntryRO項目條目
            ZipEntryRO entry = pZip->findEntryByName(path.string());
            if (entry != NULL) {
                //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
                //使用一個ZIP壓縮包的ZIPEntryRO項目條目創建一個新的
                //Asset對象,如果這個條目沒有被壓縮,我們可能想要創建
                //或者共享一片共享內存
                pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
                pZip->releaseEntry(entry);
            }
        }

        if (pAsset != NULL) {
            /* create a "source" name, for debug/display */
        //將pAsset的成員變量mAssetSource設置爲android.jar:/resources.arsc
            pAsset->setAssetSource(
                    createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
                                                String8(fileName)));
        }
    }

    return pAsset;
}

返回與ZipFileRO* pZip = getZipFileLocked(ap);對應的SharedZip,這裏傳入的名字是resources.arsc

使用一個ZIP壓縮包的ZIPEntryRO項目條目創建一個新的Asset對象返回

下面我們就從引用資源包開始詳細說明過程

2. 添加被引用資源包

Android系統定義了一套通用資源,這些資源可以被應用程序引用。比如LinearLayout的android:orientation屬性的值爲“vertical”時,這個“vertical”實際上就是在系統資源包裏面定義的一個值。

Android系統編譯時生成out/target/common/obj/APPS/framework-res_intermediates/package-export.apk文件這個資源會在應用程序中引用到。

所以我們應用程序會引用到兩種包:
- 被引用的系統資源包
- 當前正在編譯的應用程序資源包

我們通過資源ID引用包裏面的資源,資源ID是四個字節的無符號整數
- 最高字節表示Package ID
- 次高字節表示Type ID
- 最低兩字節表示Entry ID

Package ID:

所有位於[0x01, 0x7f]之間的Package ID都是合法的,其中系統資源用0x01表示,應用資源用0x7f表示

Type ID:

Type ID指資源的類型ID,資源的類型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID

Entry ID:

Entry ID是指每一個資源在其所屬的資源類型中所出現的次序。注意,不同類型的資源的Entry ID有可能是相同的,但是由於它們的類型不同,我們仍然可以通過其資源ID來區別開來。

3. 收集資源文件

編譯應用程序之前創建一個AaptAssets對象,這些需要編譯的資源文件就保存在AaptAssets類的成員變量mRes中。

class AaptAssets : public AaptDir  
{  
    ......  

private:  
    ......  

    KeyedVector<String8, sp<ResourceTypeSet> >* mRes;  
}; 

其中KeyedVector:
- key : 資源的類型名稱
- value : 描述的是一個類型爲AaptGroup的KeyedVector這個KeyedVector是以AaptGroup Name爲Key的AaptGroup類描述的是一組同名的資源,類似於前面所描述的ConfigList,它有一個重要的成員變量mFiles,裏面保存的就是一系列同名的資源文件。每一個資源文件都是用一個AaptFile對象來描述的,並且以一個AaptGroupEntry爲Key保存在一個DefaultKeyedVector中

現在情景說明:
說明一:

類型爲drawable的ResourceTypeSet只有一個AaptGroup,它的名稱爲icon.png。

這個AaptGroup包含了三個文件,分別是
- res/drawable-ldpi/icon.png
- res/drawable-mdpi/icon.png
- res/drawable-hdpi/icon.png

每一個文件都用一個AaptFile來描述,並且都對應有一個AaptGroupEntry。

一個AaptGroupEntry描述的都是不同的資源配置信息,即它們所描述的屏幕密度分別是ldpi、mdpi和hdpi

KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
    ResourceTypeSet
        AaptGroup
            name = icon.png
            value :
                AaptFile : res/drawable-ldpi/icon.png
                    AaptGroupEntry : ldpi,mdpi,hdpi
                AaptFile : res/drawable-ldpi/icon.png
                    AaptGroupEntry : ldpi,mdpi,hdpi
                AaptFile : res/drawable-ldpi/icon.png
                    AaptGroupEntry : ldpi,mdpi,hdpi

說明二:

類型爲layout的ResourceTypeSet有兩個AaptGroup它們的名稱分別爲main.xml和sub.xml

這兩個AaptGroup分別只包含一個AaptFile分別是res/layout/main.xml和res/layout/sub.xml。這兩個AaptFile同樣是分別對應有一個AaptGroupEntry,不過這兩個AaptGroupEntry描述的資源配置信息都是屬於default的。

KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
    ResourceTypeSet
        AaptGroup
            name = sub.xml
            value :
                AaptFile : res/layout/sub.xml
                    AaptGroupEntry : default
        AaptGroup
            name : main.xml
            value :
                AaptFile : res/layout/main.xml
                    AaptGroupEntry : default          

說明三:
類型爲values的ResourceTypeSet只有一個AaptGroup它的名稱爲strings.xml這個AaptGroup只包含了一個AaptFile,即res/values/strings.xml。這個AaptFile也對應有一個AaptGroupEntry,這個AaptGroupEntry描述的資源配置信息也是屬於default的。

KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
    ResourceTypeSet
        AaptGroup
            name = strings.xml
            value :
                AaptFile : res/values/strings.xml
                    AaptGroupEntry : default
 // resType -> leafName -> group
    KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
            new KeyedVector<String8, sp<ResourceTypeSet> >;

    //調用collect_files將前面收集到assets中的各類資源文件重新收集到resources中來   
    collect_files(assets, resources);
    //定義收集各類資源文件的容器
    sp<ResourceTypeSet> drawables;
    sp<ResourceTypeSet> layouts;
    sp<ResourceTypeSet> anims;
    sp<ResourceTypeSet> animators;
    sp<ResourceTypeSet> interpolators;
    sp<ResourceTypeSet> transitions;
    sp<ResourceTypeSet> xmls;
    sp<ResourceTypeSet> raws;
    sp<ResourceTypeSet> colors;
    sp<ResourceTypeSet> menus;
    sp<ResourceTypeSet> mipmaps;
    //將保存到resources中的各類文件的Set保存到我們上述定義的Set中去
    ASSIGN_IT(drawable);
    ASSIGN_IT(layout);
    ASSIGN_IT(anim);
    ASSIGN_IT(animator);
    ASSIGN_IT(interpolator);
    ASSIGN_IT(transition);
    ASSIGN_IT(xml);
    ASSIGN_IT(raw);
    ASSIGN_IT(color);
    ASSIGN_IT(menu);
    ASSIGN_IT(mipmap);
    //設置assets的資源爲resources中保存的
    assets->setResources(resources);

這段代碼就是收集到assets中的各類資源文件重新收集到resources中來

//按類別將保存在assets中的資源文件進行歸類處理
static void collect_files(const sp<AaptDir>& dir,
        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
    const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
    int N = groups.size();//獲取資源文件夾下的資源文件個數,逐個掃描
    for (int i=0; i<N; i++) {
        String8 leafName = groups.keyAt(i);
        const sp<AaptGroup>& group = groups.valueAt(i);

        const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
                = group->getFiles();

        if (files.size() == 0) {
            continue;
        }
        //按照資源文件的類型新建一個對應的ResourceTypeSet容器保存各類文件, 最終保存到resource中去
        String8 resType = files.valueAt(0)->getResourceType();

        ssize_t index = resources->indexOfKey(resType);

        //如果index小於0表示還未爲resType所描述的類型資源文件創建一個對應的
        // Set對象,於是爲其新建一個
        if (index < 0) {
            sp<ResourceTypeSet> set = new ResourceTypeSet();
            if (kIsDebug) {
                printf("Creating new resource type set for leaf %s with group %s (%p)\n",
                        leafName.string(), group->getPath().string(), group.get());
            }
            set->add(leafName, group);
            resources->add(resType, set);
        } else {
            sp<ResourceTypeSet> set = resources->valueAt(index);
            index = set->indexOfKey(leafName);
            if (index < 0) {
                if (kIsDebug) {
                    printf("Adding to resource type set for leaf %s group %s (%p)\n",
                            leafName.string(), group->getPath().string(), group.get());
                }
                set->add(leafName, group);
            } else {
                sp<AaptGroup> existingGroup = set->valueAt(index);
                if (kIsDebug) {
                    printf("Extending to resource type set for leaf %s group %s (%p)\n",
                            leafName.string(), group->getPath().string(), group.get());
                }
                for (size_t j=0; j<files.size(); j++) {
                    if (kIsDebug) {
                        printf("Adding file %s in group %s resType %s\n",
                                files.valueAt(j)->getSourceFile().string(),
                                files.keyAt(j).toDirName(String8()).string(),
                                resType.string());
                    }
                    existingGroup->addFile(files.valueAt(j));
                }
            }
        }
    }
}

下面使用到了宏函數

#define ASSIGN_IT(n) \
        do { \
            ssize_t index = resources->indexOfKey(String8(#n)); \
            if (index >= 0) { \
                n ## s = resources->valueAt(index); \
            } \
        } while (0)

比如替換後:

#define ASSIGN_IT(drawable) 
        do { 
            ssize_t index = resources->indexOfKey(String8(drawable)); 
            if (index >= 0) { 
                drawables = resources->valueAt(index);
            }
        } while (0)

其中drawables是我們sp drawables;這裏定義的。

也就是將resources中的資源進行分類到每個容器裏面

下面就是單獨處理了。

4. 將收集到的資源增加到資源表

對應前面那些資源全部收集到了AaptAssets對象中,此時需要將這些資源同時增加到一個資源表中去,就是增加到前面所創建的一個ResourceTable對象中去。最後根據ResourceTable來生成資源索引表,即生成resources.arsc文件。

這一步不包括values,這個需要經過編譯之後才添加到資源表中。

根據前面在ResourceTable類中,每一個資源都是分別用一個Entry對象來描述的這些Entry分別按照下面這些進行保存
- Pacakge
- Type
- ConfigList

比如:

PacakgeName = “shy.luo.activity”

那麼在ResourceTable類的成員變量mPackages和mOrderedPackages中就會分別保存有一個名稱爲“shy.luo.activity”的Package.

class ResourceTable : public ResTable::Accessor  
{  
    ......  

private:  
    ......  

    DefaultKeyedVector<String16, sp<Package> > mPackages;  
    Vector<sp<Package> > mOrderedPackages;  

    ......  
};  

其中Package分別包含有drawable和layout兩種類型的資源,每一種類型使用一個Type對象來描述。

Package
    Type : drawable
        ConfigList
            name : icon.png
            value:
                Entry : res/drawable-ldip/icon.png
                    ConfigDescription : ldpi
                Entry : res/drawable-mdip/icon.png
                    ConfigDescription : mdpi
                Entry : res/drawable-hdip/icon.png
                    ConfigDescription : hdpi
    Type:layout
        ConfigList
            name : main.xml
            value:
                Entry : res/layout/main.xml
                    ConfigDescription : default
        ConfigList
            name : sub.xml
            value:
                Entry : res/layout/main.xml   
                    ConfigDescription : default

image

5.編譯values類資源

類型爲values的資源描述的都是一些簡單的值,如數組、顏色、尺寸、字符串和樣式值等,這些資源是在編譯的過程中進行收集的。

比如這個文件:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <string name="app_name">Activity</string>  
    <string name="sub_activity">Sub Activity</string>  
    <string name="start_in_process">Start sub-activity in process</string>  
    <string name="start_in_new_process">Start sub-activity in new process</string>  
    <string name="finish">Finish activity</string>  
</resources> 

這個文件經過編譯之後,資源表就多了一個名稱爲string的Type,這個Type有五個ConfigList。這五個ConfigList的名稱分別爲“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”和“finish”,每一個ConfigList又分別含有一個Entry。


Package
Type : string
ConfigList
name : app_name.png
value:
Entry : Activity
ConfigDescription : default
ConfigList
name : sub_activity.png
value:
Entry : Sub Activity
ConfigDescription : default
ConfigList
name : start_in_process.png
value:
Entry : Start sub-activity in process
ConfigDescription : default
ConfigList
name : start_in_new_process.png
value:
Entry : Start sub-activity in new process
ConfigDescription : default
ConfigList
name : finish.png
value:
Entry : Finish activity
ConfigDescription : default

image

 static status_t makeFileResources(Bundle* bundle, const sp<AaptAssets>& assets,
                                  ResourceTable* table,
                                  const sp<ResourceTypeSet>& set,
                                  const char* resType)
{
    String8 type8(resType);
    String16 type16(resType);

    bool hasErrors = false;

    ResourceDirIterator it(set, String8(resType));
    ssize_t res;
    //環逐個取出set中保存的文件進行處理 
    while ((res=it.next()) == NO_ERROR) {
        if (bundle->getVerbose()) {
            printf("    (new resource id %s from %s)\n",
                   it.getBaseName().string(), it.getFile()->getPrintableSource().string());
        }
        String16 baseName(it.getBaseName());
        const char16_t* str = baseName.string();
        const char16_t* const end = str + baseName.size();
        //判斷命名是否規範
        while (str < end) {
            if (!((*str >= 'a' && *str <= 'z')
                    || (*str >= '0' && *str <= '9')
                    || *str == '_' || *str == '.')) {
                fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
                        it.getPath().string());
                hasErrors = true;
            }
            str++;
        }
        String8 resPath = it.getPath();//獲取資源文件名稱
        resPath.convertToResPath();
        //將一個資源文件封裝爲一個Entry添加到ResourceTable中去
        table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
                        type16,
                        baseName,
                        String16(resPath),
                        NULL,
                        &it.getParams());
        //將該資源文件信息添加到AaptAsset對象assets中去
        assets->addResource(it.getLeafName(), resPath, it.getFile(), type8);
    }

    return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR;
}
 //將一個名稱爲name,類型爲type,所屬包爲package的資源文件組織成一個Entry具體實現如下所示:
status_t ResourceTable::addEntry(const SourcePos& sourcePos,
                                 const String16& package,
                                 const String16& type,
                                 const String16& name,
                                 const String16& value,
                                 const Vector<StringPool::entry_style_span>* style,
                                 const ResTable_config* params,
                                 const bool doSetIndex,
                                 const int32_t format,
                                 const bool overwrite)
{
    uint32_t rid = mAssets->getIncludedResources()
        .identifierForName(name.string(), name.size(),
                           type.string(), type.size(),
                           package.string(), package.size());
    if (rid != 0) {
        sourcePos.error("Resource entry %s/%s is already defined in package %s.",
                String8(type).string(), String8(name).string(), String8(package).string());
        return UNKNOWN_ERROR;
    }
    /* Entry類用來描述一個資源項,它的重要成員變量的含義如下所示:

      --mName:表示資源名稱。

      --mItem:表示資源數據,用一個Item對象來描述。

      Item類用來描述一個資源項數據,它的重要成員變量的含義如下所示:

      --value:表示資源項的原始值,它是一個字符串。

      --parsedValue:表示資源項原始值經過解析後得到的結構化的資源值,使用一個Res_Value對象來描述。例如,一個整數類型的資源項的原始值爲“12345”,經過解析後,就得到一個大小爲12345的整數類型的資源項。*/


    /* 首先調用getEntry函數獲取一個描述名稱爲name的資源文件的Entry類對象
    ** 其所在包爲package, 其類型使用type描述 */
    sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite,
                           params, doSetIndex);
    if (e == NULL) {
        return UNKNOWN_ERROR;
    }
    //獲取了一個描述名稱爲name資源文件的Entry對象之後,把其相關信息組織成一個Item對象然後添加到Entry中
    status_t err = e->setItem(sourcePos, value, style, format, overwrite);
    if (err == NO_ERROR) {
        mNumLocal++;
    }
    return err;
}
 sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package,
                                                 const String16& type,
                                                 const String16& name,
                                                 const SourcePos& sourcePos,
                                                 bool overlay,
                                                 const ResTable_config* config,
                                                 bool doSetIndex)
{
    /* Type類用來描述一個資源類型,它的重要成員變量的含義如下所示:

         --mName:表示資源類型名稱。

         --mConfigs:表示包含的資源配置項列表,每一個配置項列表都包含了一系列同名的資源,使用一個ConfigList來描述。例如,假設有main.xml和sub.xml兩個layout類型的資源,那麼main.xml和sub.xml都分別對應有一個ConfigList。

         --mOrderedConfigs:和mConfigs一樣,也是表示包含的資源配置項,不過它們是以Entry ID從小到大的順序保存在一個Vector裏面的,而mConfigs是以Entry Name來Key的DefaultKeyedVector。

         --mUniqueConfigs:表示包含的不同資源配置信息的個數。我們可以將mConfigs和mOrderedConfigs看作是按照名稱的不同來劃分資源項,而將mUniqueConfigs看作是按照配置信息的不同來劃分資源項。

    */
    /* ConfigList用來描述一個資源配置項列表,它的重要成員變量的含義如下所示:

      --mName:表示資源項名稱,也稱爲Entry Name。

      --mEntries: 表示包含的資源項,每一個資源項都用一個Entry對象來描述,並且以一個對應的ConfigDescription爲Key保存在一個 DefaultKeyedVector中。例如,假設有一個名稱爲icon.png的drawable資源,有三種不同的配置,分別是ldpi、mdpi 和hdpi,那麼以icon.png爲名稱的資源就對應有三個項。

*/


    //調用getType函數來獲取一個Type對象t用來描述資源類型
    sp<Type> t = getType(package, type, sourcePos, doSetIndex);
    if (t == NULL) {
        return NULL;
    }
    return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
}

將數據轉移到了table之後,就開始分配id

6.給Bag資源分配ID

先分配bag id

 err = table.assignResourceIds();
 status_t ResourceTable::assignResourceIds()
{
    const size_t N = mOrderedPackages.size();
    size_t pi;
    status_t firstError = NO_ERROR;

    // First generate all bag attributes and assign indices.
    // 首先取出當前編譯應用程序資源所依賴的的包個數,並分別爲包中的資源分配資源ID, 
    //在這裏這兩個包分別是: android.jar 和 com.example.helloworldactivity.
    for (pi=0; pi<N; pi++) {
        sp<Package> p = mOrderedPackages.itemAt(pi);
        if (p == NULL || p->getTypes().size() == 0) {
            // Empty, skip!
            continue;
        }

        if (mPackageType == System) {
            p->movePrivateAttrs();
        }

        // This has no sense for packages being built as AppFeature (aka with a non-zero offset).
        /* 如果爲Package對象p中的Type設定了public屬性id,那麼調用
        ** applyPublicTypeOrder函數將p中成員變量mOrderedTypes中的Type按照id
        ** 由小到大的順序排列
        **
        ** 例如, 我們在values/public.xml中如下定義:
        ** <?xml version="1.0" encoding="utf-8"?>
        ** <resources>
        <public type="string" name="show" id="0x7f030001" />
        <public type="style" name="AppTheme" id="0x7f040001" />
        ** </resources>
        ** 那麼type爲string和style的在mOrderedTypes中的位置是在2,3
        ** 位置處,就是將3和4進行減1操作而,第0,1兩個位置保留.
        */
        status_t err = p->applyPublicTypeOrder();
        if (err != NO_ERROR && firstError == NO_ERROR) {
            firstError = err;
        }

        // Generate attributes...
        //按照Type-->ConfigList-->Entry的順序依次將所有的Entry調用函數 generateAttributes生成一個屬性信息 
        const size_t N = p->getOrderedTypes().size();
        size_t ti;
        for (ti=0; ti<N; ti++) {
            sp<Type> t = p->getOrderedTypes().itemAt(ti);
            if (t == NULL) {
                continue;
            }
            const size_t N = t->getOrderedConfigs().size();
            for (size_t ci=0; ci<N; ci++) {
                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
                if (c == NULL) {
                    continue;
                }
                const size_t N = c->getEntries().size();
                for (size_t ei=0; ei<N; ei++) {
                    sp<Entry> e = c->getEntries().valueAt(ei);
                    if (e == NULL) {
                        continue;
                    }
                    //generateAttributes函數用於將保存到mBag中的信息取出,如果
                    //其是一個id屬性,並且在table中沒有對應的bag或者entry則
                    //創建一個entry添加進table中 
                    status_t err = e->generateAttributes(this, p->getName());
                    if (err != NO_ERROR && firstError == NO_ERROR) {
                        firstError = err;
                    }
                }
            }
        }

        uint32_t typeIdOffset = 0;
        if (mPackageType == AppFeature && p->getName() == mAssetsPackage) {
            typeIdOffset = mTypeIdOffset;
        }

        const SourcePos unknown(String8("????"), 0);
        sp<Type> attr = p->getType(String16("attr"), unknown);

        // Assign indices...
        const size_t typeCount = p->getOrderedTypes().size();
        for (size_t ti = 0; ti < typeCount; ti++) {
            sp<Type> t = p->getOrderedTypes().itemAt(ti);
            if (t == NULL) {
                continue;
            }
            // 類似的,我們如果爲某類Type對象指定了public的IDS信息,我們就同上
            // 將Type中的ConfigList對象按照id值從小到大排列在mOrderedConfigs中去

            err = t->applyPublicEntryOrder();
            if (err != NO_ERROR && firstError == NO_ERROR) {
                firstError = err;
            }

            const size_t N = t->getOrderedConfigs().size();
            t->setIndex(ti + 1 + typeIdOffset);

            LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t,
                                "First type is not attr!");

            for (size_t ei=0; ei<N; ei++) {
                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ei);
                if (c == NULL) {
                    continue;
                }
                c->setEntryIndex(ei);
            }
        }


        // Assign resource IDs to keys in bags...
        //分配bags id
        for (size_t ti = 0; ti < typeCount; ti++) {
            sp<Type> t = p->getOrderedTypes().itemAt(ti);
            if (t == NULL) {
                continue;
            }

            const size_t N = t->getOrderedConfigs().size();
            for (size_t ci=0; ci<N; ci++) {
                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
                if (c == NULL) {
                    continue;
                }
                //printf("Ordered config #%d: %p\n", ci, c.get());
                const size_t N = c->getEntries().size();
                for (size_t ei=0; ei<N; ei++) {
                    sp<Entry> e = c->getEntries().valueAt(ei);
                    if (e == NULL) {
                        continue;
                    }
                    // 逐個取出每一個資源屬性調用Entry的assignResourceIds爲其分配屬性ID
                    //對應ResourceTable::Entry::assignResourceIds
                    status_t err = e->assignResourceIds(this, p->getName());
                    if (err != NO_ERROR && firstError == NO_ERROR) {
                        firstError = err;
                    }
                }
            }
        }
    }
    return firstError;
}

其他的一些資源引用比如style,array,bag等

這些資源會給自己定義一些專用的值,這些帶有專用值的資源就統稱爲Bag資源。例如,Android系統提供的android:orientation屬性的取值範圍爲{“vertical”、“horizontal”},就相當於是定義了vertical和horizontal兩個Bag。

在繼續編譯其它非values的資源之前,我們需要給之前收集到的Bag資源分配資源ID,因爲它們可能會被其它非values類資源引用到。假設在res/values目錄下,有一個attrs.xml文件,它的內容如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
<attr name="custom_orientation">  
    <enum name="custom_vertical" value="0" />  
    <enum name="custom_horizontal" value="1" />  
</attr>  
</resources>  

image

7.編譯Xml資源文件

前面的6部都是爲了後面第七步做準備

比如:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" 
    android:gravity="center">
    <Button 
        android:id="@+id/button_start_in_process"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/start_in_process" >
    </Button>
    <Button 
        android:id="@+id/button_start_in_new_process"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/start_in_new_process" >
    </Button>
</LinearLayout>

過程如下:

image

(1).解析xml

解析Xml文件是爲了可以在內存中用一系列樹形結構的XMLNode來表示它。

 class XMLNode : public RefBase  
{  
    ......  

private:  
    ......  

    String16 mElementName;//表示Xml元素標籤
    Vector<sp<XMLNode> > mChildren;//表示Xml元素的文本內容
    Vector<attribute_entry> mAttributes;//表示Xml元素的屬性列表
    ......  
    String16 mChars;//表示Xml元素的子元素
    ......  
}; 

xml解析完了之後就可以得到一個用來描述根節點的XMLNode,下來就是用這個節點完成其他編譯工作。

(2).賦予屬性名稱資源ID

然後分配layout等等那些ID&解析

 status_t compileXmlFile(const Bundle* bundle,
                        const sp<AaptAssets>& assets,
                        const String16& resourceName,
                        const sp<XMLNode>& root,
                        const sp<AaptFile>& target,
                        ResourceTable* table,
                        int options)
{
    // 首先去除空格
    if ((options&XML_COMPILE_STRIP_WHITESPACE) != 0) {
        root->removeWhitespace(true, NULL);
    } else  if ((options&XML_COMPILE_COMPACT_WHITESPACE) != 0) {
        root->removeWhitespace(false, NULL);
    }
    ///* 設定編碼格式 */
    if ((options&XML_COMPILE_UTF8) != 0) {
        root->setUTF8(true);
    }

    bool hasErrors = false;
    /* 如果尚未對解析到root數據結構中的屬性分配資源ID則調用
    ** root的成員函數分配資源id, 給屬性分配資源ID原理類似於上
    ** 述給Bag資源分配ID */
    if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
        status_t err = root->assignResourceIds(assets, table);
        if (err != NO_ERROR) {
            hasErrors = true;
        }
    }
    /* parseValues函數用於獲取當前資源屬性所在的行號等信息將其保存到table
    ** 中,並將字符串資源信息替換成對應的類型值 */
    status_t err = root->parseValues(assets, table);
    if (err != NO_ERROR) {
        hasErrors = true;
    }

    if (hasErrors) {
        return UNKNOWN_ERROR;
    }

    if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) {
        return UNKNOWN_ERROR;
    }

    if (kIsDebug) {
        printf("Input XML Resource:\n");
        root->print();
    }
    err = root->flatten(target,
            (options&XML_COMPILE_STRIP_COMMENTS) != 0,
            (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
    if (err != NO_ERROR) {
        return err;
    }

    if (kIsDebug) {
        printf("Output XML Resource:\n");
        ResXMLTree tree;
        tree.setTo(target->getData(), target->getSize());
        printXMLBlock(&tree);
    }

    target->setCompressionMethod(ZipEntry::kCompressDeflated);

    return err;
}

其中核心代碼是:
cpp
status_t err = root->assignResourceIds(assets, table);

給每一個xml元素屬性名賦值資源ID,例如對於main.xml根節點LinearLayout它的屬性名就是android:orientationandroid:layout_widthandroid:layout_heightandroid:gravity每一個屬性名都對應在系統資源包裏面有對應的Bag資源,在編譯好的時候就已經分配了ID

每一個Xml文件都是從根節點開始給屬性名稱賦予資源ID,然後再給遞歸給每一個子節點的屬性名稱賦予資源ID,直到每一個節點的屬性名稱都獲得了資源ID爲止

(3).解析屬性值

前面給屬性名賦予資源ID,對於LinearLayout來說現在已經有了android:orientation資源ID,但是沒有值,這一步就是將值進行解析,在系統資源包中,“horizontal”或者“vertical”也同樣是一個Bag資源,它們的值分別被定義爲0和1。

現在綜合說下2,3兩個步驟。假設在上一步中,從系統資源包找到“android:orientation”的資源ID爲0x010100c4,那麼Android資源打包工具就會通過這個資源ID找到它的元數據,也就是兩個名稱分別爲“horizontal”和“vertical”的bag,接着就根據字符串匹配到名稱“vertical”的bag,最後就可以將這個bag的值1作爲解析結果了。

對於引用類型特殊處理

對於main.xml文件的第一個Button節點的android:id屬性值“@+id/button_start_in_process”,其中,“@”表示後面描述的屬性是引用類型的,“+”表示如果該引用不存在,那麼就新建一個,“id”表示引用的資源類型是id,“button_start_in_process”表示引用的名稱。實際上,在”id”前面,還可以指定一個包名,例如,將main.xml文件的第一個Button節點的android:id屬性值指定爲“@+[package:]id/button_start_in_process” 。如果沒有指定包名的話,那麼就會默認在當前編譯的包裏面查找button_start_in_process這個引用。由於前面指有“+”符號,因此,如果在指定的包裏面找不到button_start_in_process這個引用的話,那麼就會在該包裏面創建一個新的。無論button_start_in_process在指定的包裏面原來就存在的,還是新建的,最終Android資源打包工具都是將它的資源ID作爲解析結果。

在我們這個情景中,在解析main.xml文件的兩個Button節點的android:id屬性值“@+id/button_start_in_process”和“@+id/button_start_in_new_process”時,當前正在編譯的資源包沒有包含有相應的引用的,因此,Android資源打包工具就會在當前正在編譯的資源包裏面增加兩個類型爲id的Entry

image

對於對於main.xml文件的兩個Button節點的android:text屬性值“@string/start_in_process”和“@string/start_in_new_process”它們分別表示引用的是當前正在編譯的資源包的名稱分別爲“start_in_process”和“start_in_new_process”的string資源。這兩個string資源在前面的第五步操作中已經編譯過了,因此,這裏就可以直接獲得它們的資源ID。

一個資源項一旦創建之後,要獲得它的資源ID是很容易的,因爲它的Package ID、Type ID和Entry ID都是已知的。

(4).壓平Xml文件

下面就是將xml文件從文本格式轉化成二進制

    err = root->flatten(target,
            (options&XML_COMPILE_STRIP_COMMENTS) != 0,
            (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);

Step 1. 收集有資源ID的屬性的名稱字符串:

這一步除了收集那些具有資源ID的Xml元素屬性的名稱字符串之外,還會將對應的資源ID收集起來放在一個數組中。這裏收集到的屬性名稱字符串保存在一個字符串資源池中,它們與收集到的資源ID數組是一一對應的。

比如:

對於main.xml對應關係生成的結果就如下:

- 0 1 2 3 4 5
String Poll orientation layout_width layout_height gravity id text
Resource ID Array 0x010100c4 0x010100f4 0x010100f5 0x010100af 0x010100d0 0x0101014f

Step 2. 收集其它字符串

這一步不會重複收集第一步的那些字符串

對於main.xml來說:

image

最後編譯出來的結果是一個個chunk,每一個chunk都有一個頭部用來描述chunk的元信息。同時,整個Xml二進制文件又可以看成一塊總的chunk,它有一個類型爲ResXMLTree_header的頭部

ResXMLTree_header定義在文件frameworks/base/include/utils/ResourceTypes.h中

/**
 * Header that appears at the front of every data chunk in a resource.
 */
struct ResChunk_header
{
    // Type identifier for this chunk.  The meaning of this value depends
    // on the containing chunk.
    uint16_t type;

    // Size of the chunk header (in bytes).  Adding this value to
    // the address of the chunk allows you to find its associated data
    // (if any).
    uint16_t headerSize;

    // Total size of this chunk (in bytes).  This is the chunkSize plus
    // the size of any data associated with the chunk.  Adding this value
    // to the chunk allows you to completely skip its contents (including
    // any child chunks).  If this value is the same as chunkSize, there is
    // no data associated with the chunk.
    uint32_t size;
};

/**
 * XML tree header.  This appears at the front of an XML tree,
 * describing its content.  It is followed by a flat array of
 * ResXMLTree_node structures; the hierarchy of the XML document
 * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
 * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
 */
struct ResXMLTree_header
{
    struct ResChunk_header header;
};

對於ResXMLTree_header頭部來說,內嵌在它裏面的ResChunk_header的成員變量的值如下所示:

  • type:等於RES_XML_TYPE,描述這是一個Xml文件頭部。
  • headerSize:等於sizeof(ResXMLTree_header),表示頭部的大小。
  • size:等於整個二進制Xml文件的大小,包括頭部headerSize的大小。

Step 4. 寫入字符串資源池

對於main.xml來說,依次寫入的字符串爲
“orientation”、“layout_width”、“layout_height”、“gravity”、“id”、”text”、”android”、“http://schemas.android.com/apk/res/android”、“LinearLayout”和“Button”。之所以要嚴格按照這個順序來寫入,是因爲接下來要將前面Step 1收集到的資源ID數組也寫入到二進制格式的Xml文件中去,並且要保持這個資源ID數組與字符串資源池前六個字符串的對應關係。

寫入的字符串池chunk同樣也是具有一個頭部的,這個頭部的類型爲ResStringPool_header

frameworks/base/include/utils/ResourceTypes.h

/**
 * Definition for a pool of strings.  The data of this chunk is an
 * array of uint32_t providing indices into the pool, relative to
 * stringsStart.  At stringsStart are all of the UTF-16 strings
 * concatenated together; each starts with a uint16_t of the string's
 * length and each ends with a 0x0000 terminator.  If a string is >
 * 32767 characters, the high bit of the length is set meaning to take
 * those 15 bits as a high word and it will be followed by another
 * uint16_t containing the low word.
 *
 * If styleCount is not zero, then immediately following the array of
 * uint32_t indices into the string table is another array of indices
 * into a style table starting at stylesStart.  Each entry in the
 * style table is an array of ResStringPool_span structures.
 */
struct ResStringPool_header
{
    struct ResChunk_header header;

    // Number of strings in this pool (number of uint32_t indices that follow
    // in the data).
    uint32_t stringCount;//等於字符串的數量。

    // Number of style span arrays in the pool (number of uint32_t indices
    // follow the string indices).
    uint32_t styleCount;//等於字符串的樣式的數量。

    // Flags.
    enum {
        // If set, the string index is sorted by the string values (based
        // on strcmp16()).
        SORTED_FLAG = 1<<0,

        // String pool is encoded in UTF-8
        UTF8_FLAG = 1<<8
    };
    ////等於0、SORTED_FLAG、UTF8_FLAG或者它們的組合值,用來描述字符串資源串的屬性。
    //例如,SORTED_FLAG位等於1表示字符串是經過排序的,而UTF8_FLAG位等於1表示字符串是使用UTF8編碼的,否則就是UTF16編碼的。
    uint32_t flags;

    // Index from header of the string data.
    uint32_t stringsStart;//等於字符串內容塊相對於其頭部的距離。

    // Index from header of the style data.
    uint32_t stylesStart;//等於字符串樣式塊相對於其頭部的距離。
};

對應內嵌在ResStringPool_header中ResChunk_header對應值如下:
- type:等於RES_STRING_POOL_TYPE,描述這是一個字符串資源池。
- headerSize:等於sizeof(ResStringPool_header),表示頭部的大小。
- size:整個字符串chunk的大小,包括頭部headerSize的大小。

8.生成資源符號

從前面的操作可以知道,所有收集到的資源項都按照類型來保存在一個資源表中,即保存在一個ResourceTable對象。因此,Android資源打包工具aapt只要遍歷每一個Package裏面的每一個Type,然後取出每一個Entry的名稱,並且根據這個Entry在自己的Type裏面出現的次序來計算得到它的資源ID,那麼就可以生成一個資源符號了,這個資源符號由名稱以及資源ID所組成。

比如對於strings.xml文件中名稱爲“start_in_process”的Entry來說,它是一個類型爲string的資源項,假設它出現的次序爲第3,那麼它的資源符號就等於R.string.start_in_process,對應的資源ID就爲0x7f050002,其中,高字節0x7f表示Package ID,次高字節0x05表示string的Type ID,而低兩字節0x02就表示“start_in_process”是第三個出現的字符串。

9.生成資源符號

經過前面步驟生成一個資源列表如下:

image

現在有了上面的表之後aapt就可以生成資源索引表resources.arsc

(1). 收集類型字符串

在上面的例子中有四種類型資源分別是drawable、layout、string和id,於是對應的類型字符串就爲“drawable”、“layout”、“string”和“id”。

這些字符串是按照Package來收集的也就是說,當前被編譯的應用程序資源有幾個Package,就有幾組對應的類型字符串,每一個組類型字符串都保存在其所屬的Package中。

(2). 收集資源項名稱字符串

在第9步的時候一共有12個資源項每一個資源項的名稱分別爲
“icon”、“icon”、“icon”、“main”、“sub”、“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”、“finish”、“button_start_in_process”
和“button_start_in_new_process”
於是收集到的資源項名稱字符串就爲
“icon”、“main”、“sub”、“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”、“finish”、“button_start_in_process”和“button_start_in_new_process”。

注意,這些字符串同樣是按Package來收集的,也就是說,當前被編譯的應用程序資源有幾個Package,就有幾組對應的資源項名稱字符串,每一個組資源項名稱字符串都保存在其所屬的Package中。

(3). 收集資源項值字符串

一共有12個資源項,但是隻有10項是具有值字符串的,它們分別是
“res/drawable-ldpi/icon.png”、“res/drawable-mdpi/icon.png”、“res/drawable-hdpi/icon.png”、“res/layout/main.xml”、“res/layout/sub.xml”、“Activity”、“Sub Activity”、“Start sub-activity in process”、“Start sub-activity in new process”,“Finish activity”。

注意,這些字符串不是按Package來收集的,也就是說,當前所有參與編譯的Package的資源項值字符串都會被統一收集在一起。

(4). 生成Package數據塊

參與編譯的每一個Package的資源項元信息都寫在一塊獨立的數據上,這個數據塊使用一個類型爲ResTable_package的頭部來描述。

Step 1. 寫入Package資源項元信息數據塊頭部

struct ResTable_package
{
    struct ResChunk_header header;

    uint32_t id;//等於Package ID

    char16_t name[128];//等於Package Name

    uint32_t typeStrings;//等於類型字符串資源池相對頭部的偏移位置

    uint32_t lastPublicType;//等於最後一個導出的Public類型字符串在類型字符串資源池中的索引,目前這個值設置爲類型字符串資源池的大小。

    uint32_t keyStrings;//等於資源項名稱字符串相對頭部的偏移位置

    uint32_t lastPublicKey;//等於最後一個導出的Public資源項名稱字符串在資源項名稱字符串資源池中的索引,目前這個值設置爲資源項名稱字符串資源池的大小。
};

嵌入在ResTable_package內部的ResChunk_header的各個成員變量的取值如下所示:
- type:等於RES_TABLE_PACKAGE_TYPE,表示這是一個Package資源項元信息數據塊頭部。
- headerSize:等於sizeof(ResTable_package),表示頭部大小。
- size:等於sizeof(ResTable_package) + 類型字符串資源池大小 + 資源項名稱字符串資源池大小 + 類型規範數據塊大小 + 數據項信息數據塊大小

Step 2. 寫入類型字符串資源池

在前面的第1個操作中,我們已經將每一個Package用到的類型字符串收集起來了,因此,這裏就可以直接將它們寫入到Package資源項元信息數據塊頭部後面的那個數據塊去。

Step 3. 寫入資源項名稱字符串資源池

在前面的第2個操作中,我們已經將每一個Package用到的資源項名稱字符串收集起來了,這裏就可以直接將它們寫入到類型字符串資源池後面的那個數據塊去。

Step 4. 寫入類型規範數據塊

與密度有關

Step 5. 寫入類型資源項數據塊

10. 編譯AndroidManifest.xml文件

經過前面的九個步驟之後,應用程序的所有資源項就編譯完成了,這時候就開始將應用程序的配置文件AndroidManifest.xml也編譯成二進制格式的Xml文件。之所以要在應用程序的所有資源項都編譯完成之後,再編譯應用程序的配置文件,是因爲後者可能會引用到前者。

應用程序配置文件AndroidManifest.xml的編譯過程與其它的Xml資源文件的編譯過程是一樣的,可以參考前面的第七步。注意,應用程序配置文件AndroidManifest.xml編譯完成之後,Android資源打包工具appt還會驗證它的完整性和正確性,例如,驗證AndroidManifest.xml的根節點mainfest必須定義有android:package屬性。

// 取出AndroidManifest.xml文件
    const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0));
    String8 manifestPath(manifestFile->getPrintableSource());

    // Generate final compiled manifest file.
    //清空manifestFile所指向的AndroidManfiest.xml的信息,然後重新解析
    manifestFile->clearData();
    sp<XMLNode> manifestTree = XMLNode::parse(manifestFile);
    if (manifestTree == NULL) {
        return UNKNOWN_ERROR;
    }
    //檢測是否AndroidManifest.xml中是否有overlay資源,如果有就將現有資源替換
    err = massageManifest(bundle, manifestTree);
    if (err < NO_ERROR) {
        return err;
    }
    //編譯AndroidManifest.xml文件
    err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table);
    if (err < NO_ERROR) {
        return err;
    }

11. 生成R.java文件

在前面的第八步中,我們已經將所有的資源項及其所對應的資源ID都收集起來了,因此,這裏只要將直接將它們寫入到指定的R.java文件去就可以了。例如,假設分配給類型爲layout的資源項main和sub的ID爲0x7f030000和0x7f030001,那麼在R.java文件,就會分別有兩個以main和sub爲名稱的常量

public final class R {  
    ......  

    public static final class layout {  
        public static final int main=0x7f030000;  
        public static final int sub=0x7f030001;  
    }  

    ......  
} 

12. 打包APK文件

image

發佈了68 篇原創文章 · 獲贊 12 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章