AAPT源码分析(二)

准备

此篇接着 AAPT源码分析(一) 继续分析 AAPT 的资源编译打包流程。

在资源编译之前,首先需要收集所有有效的资源文件,AAPT 用一个 AaptAssets 对象来描述收集到的资源文件集合。AaptAssets 类继承于 AaptDir 类,是一种树的递归层级结构。我们先看一下涉及到相关类的类图:

上面类图中有一些 Android 源码里面定义的集合和工具类,其中,KeyedVector 是 Android 封装的一种用来储存 Key-Value 格式的数据结构,类似于 Java 的 Map,但是内部是通过动态数组来实现的;DefaultKeyedVector 继承于 KeyedVector,当找不到对应的 Key 时,会返回一个设定的默认值;sp 是 Android 提供的一种智能指针,在普通指针上加了一层封装,通过引用计数法来自动管理内存的回收。

在分析源码之前,我们需要对上图中相关的类以及其成员变量做一个说明,以此来了解一个 AaptAssets 对象是如何记录收集到的资源文件的。


一个 AaptFile 对象描述一个资源文件,例如 assets 目录下的一个 .db 文件、res/drawable 目录下的一张 PNG 图片等,AaptFile 类的部分成员变量如下表:

变量名 类型 描述
mPath String8 资源文件的路径(相对于aapt执行的位置)
mGroupEntry AaptGroupEntry 资源文件的配置信息
mResourceType String8 资源文件的类型(drawable、layout等)

一个 AaptGroupEntry 对象用来描述一个资源文件的配置信息,它只有一个类型为 ConfigDescription 的 mParams 成员变量,而 ConfigDescription 类继承于 ResTable_config,ResTable_config 才是真正保存配置信息的类。另外,AaptGroupEntry、ConfigDescription 都重载了 >、<、== 等操作符,方便对象进行比较。ResTable_config 类的部分信息如下表:

变量名 类型 描述
mcc uint16_t 移动国家代码
mnc uint16_t 移动网络代码
locale uint32_t 语言区域
orientation uint8_t 布局方向
density uint16_t 屏幕像素密度

一个 AaptGroup 对象用来描述一个同名同类型但是不同配置信息的资源文件集合,内部用一个类型为 DefaultKeyedVector 的 mFiles 成员变量来保存。例如项目中有 3 个类型为 drawable,名字为 a.png 的不同配置信息的资源文件:

res/drawable/a.png
res/drawable-xxhdpi/a.png
res/drawable-port-hdpi/a.png

则会存在有一个 AaptGroup 对象来保存这 3 个资源文件,Key 为 a.png 对应的 AaptGroupEntry 对象,Value 为 a.png 对应的 AaptFile 对象。AaptGroup 类的成员变量如下表:

变量名 类型 描述
mFiles DefaultKeyedVector <AaptGroupEntry, sp<AaptFile>> 同名同类型但是不同配置信息的资源文件集合

一个 AaptDir 对象用来描述一个同类型的资源文件集合,例如以下 3 个资源文件夹对应了一个类型为 drawable 的 AaptDir 对象:

res/drawable
res/drawable-xxhdpi
res/drawable-port-hdpi

需要注意的是,AaptDir 类是一种树形结构,它会递归地去收集遵循命名规范的子文件夹的资源。AaptDir 类的成员变量如下表:

变量名 类型 描述
mFiles DefaultKeyedVector<String8, sp<AaptGroup>> 同类型资源的 AaptGroup 的集合,Key 为资源文件名
mDirs DefaultKeyedVector<String8, sp<AaptDir>> 子文件夹 AaptDir 的集合,Key 为资源类型名

一个 ResourceTypeSet 对象描述了一个 res 类型的资源文件集合,有几种 res 类型的资源就有几个 ResourceTypeSet 对象,列举所有的 res 类型如下:

anim
animator
interpolator
transition
drawable
layout
values
xml
raw
color
menu
mipmap

ResourceTypeSet 类继承于 KeyedVector<String8,sp<AaptGroup>>,它跟 AaptDir 类是有本质区别的,AaptDir 类是递归地描述所有的资源文件,包括 assets、res类型,而ResourceTypeSet 只收集 res 类型的资源。实际上, AaptAssets 是先把所有资源文件都收录(源码里面是用 slurp 这个单词,不太好翻译,就理解为收录了)到一个 类型为 DefaultKeyedVector<String8, sp<AaptGroup>> 的 mFiles 变量以及一个类型为 DefaultKeyedVector<String8, sp<AaptDir>> 的 mDirs 变量里面,并同时把 res 类型的 AaptDir 集合收录在一个类型为 Vector<sp<AaptDir>> 的 mResDirs 变量里面,然后才从 mResDirs 变量里面把资源收集到一个类型为 KeyedVector<String8, sp<ResourceTypeSet>> 的 mRes 指针变量里面,AaptAssets 类的部分成员变量如下表:

变量名 类型 描述
mFiles DefaultKeyedVector<String8, sp<AaptGroup>> 从 AaptDir 类继承
mDirs DefaultKeyedVector<String8, sp<AaptDir>> 从 AaptDir 类继承
mGroupEntries SortedVector 顺序添加所有 AaptGroupEntry 的集合
mResDirs Vector<sp> mDirs 变量中 Key 为 res 类型名的 AaptDir 对象同时会记录到这个集合里面
mIncludedAssets AssetManager 用来解析查询引用资源包的(”-I” 参数指定的资源包)
mOverlay AaptAssets Overlay资源( “-S” 参数指定的多个资源路径)
mRes KeyedVector<String8, sp<ResourceTypeSet>>* 收集 res 类型的资源集合

源码分析

AAPT 把解析完的命令参数信息保存在一个 Bundle 对象中,然后执行 Command.cpp 的 doPackage() 方法,开始收集、编译并打包资源文件:

/*
 * 收集、编译、打包项目资源文件
 */
int doPackage(Bundle* bundle)
{
    ......
    status_t err;
    sp<AaptAssets> assets;
    ......
    sp<ApkBuilder> builder;
    ......
    // 构造 AaptAssets 对象,用来保存收集到的资源文件
    assets = new AaptAssets();
    ......
    //收录所有资源
    err = assets->slurpFromArgs(bundle);
    ......
    builder = new ApkBuilder(configFilter);
    ......
     // 编译资源文件(res类型、AndroidManifest.xml)
    if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
        err = buildResources(bundle, assets, builder);
        if (err != 0) {
            goto bail;
        }
    }
}

从上可知,首先构造出一个 AaptAssets 对象,然后调用它的 slurpFromArgs() 方法收录所有资源文件,这个方法定义在 AaptAssets.h 头文件里面,我们看一下它的具体实现:

......
static const char* kAssetDir = "assets";
static const char* kResourceDir = "res";
static const char* kValuesDir = "values";
static const char* kMipmapDir = "mipmap";
......

ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
{
    int count;
    int totalCount = 0;
    FileType type;
    const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
    const size_t dirCount =resDirs.size();
    sp<AaptAssets> current = this;

    const int N = bundle->getFileSpecCount();

    /*
     * 收录 AndroidManifest.xml 文件(-M 参数指定)
     */
    if (bundle->getAndroidManifestFile() != NULL) {
        //从 Bundle 对象中获取指定的 AndroidManifest.xml 文件的路径
        String8 srcFile(bundle->getAndroidManifestFile());
        //这里 srcFile.getPathLeaf() 即为 "AndroidManifest.xml"
        addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
                NULL, String8());
        totalCount++;
    }

    /*
     * 收录 assets 类型资源文件(-A 参数指定)
     */
    const Vector<const char*>& assetDirs = bundle->getAssetSourceDirs();
    const int AN = assetDirs.size();
    for (int i = 0; i < AN; i++) {
        .......
        String8 assetRoot(assetDirs[i]);
        sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
        AaptGroupEntry group;
        //递归记录
        count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
                                            String8(), mFullAssetPaths, true);
        ......
        totalCount += count;
        ......
    }

    /*
     * 收录 res 类型资源(-S 参数指定)
     */
    for (size_t i=0; i<dirCount; i++) {
        const char *res = resDirs[i];
        if (res) {
            type = getFileType(res);
            ......
            if (type == kFileTypeDirectory) {
                //-S 指定多个参数的情况,按照优先级依次设置 Overlay 包
                if (i>0) {
                    sp<AaptAssets> nextOverlay = new AaptAssets();
                    current->setOverlay(nextOverlay);
                    current = nextOverlay;
                    ......
                }
                count = current->slurpResourceTree(bundle, String8(res));
                ......
                totalCount += count;
            }
        }
    }
    /*
     * 收录 assets 类型资源文件(执行 AAPT 命令时直接写在最后面的那些文件夹路径)
     */
    for (int arg=0; arg<N; arg++) {
       ......
    }
    return totalCount;
}

分析上面代码,首先通过 AaptAssets 对象的 addFile() 方法收录AndroidManifest.xml 文件,方法具体代码如下:

/*
 * 添加 AndroidManifest.xml 文件的时候传进来的 filePath 就是 "AndroidManifest.xml",
 * 跟踪代码,最后 AndroidManifest.xml 文件被收录在 AaptAssets 对象的 mFiles 变量里面,
 * Key 为 文件名 "AndroidManifest.xml",Value 为一个 AaptGroup 对象,其中包含了一个 AndroidManifest.xml 文件的 AaptFile对象
 */
sp<AaptFile> AaptAssets::addFile(
        const String8& filePath, const AaptGroupEntry& entry,
        const String8& srcDir, sp<AaptGroup>* outGroup,
        const String8& resType)
{
    sp<AaptDir> dir = this;
    sp<AaptGroup> group;
    sp<AaptFile> file;
    String8 root, remain(filePath), partialPath;
    while (remain.length() > 0) {
        /*
         * walkPath方法解释:
         * "/tmp/foo/bar.c" --> "tmp" (remain = "foo/bar.c")
         * "/tmp" --> "tmp" (remain = "")
         * "bar.c" --> "bar.c" (remain = "")
         * 对于传进来的 "AndroidManifest.xml" 而言,执行一次之后,root 为 "AndroidManifest.xml",remain 为 ""
         */
        root = remain.walkPath(&remain);
        partialPath.appendPath(root);

        const String8 rootStr(root);

        if (remain.length() == 0) {
            //查询 mFiles 集合里面有没有 "AndroidManifest.xml" 的 AaptGroup 对象
            ssize_t i = dir->getFiles().indexOfKey(rootStr);
            if (i >= 0) {
                group = dir->getFiles().valueAt(i);
            } else {
                //创建 "AndroidManifest.xml" 的 AaptGroup 对象,添加到 mFiles 集合
                group = new AaptGroup(rootStr, filePath);
                status_t res = dir->addFile(rootStr, group);
                if (res != NO_ERROR) {
                    return NULL;
                }
            }
            //创建 AndroidManifest.xml 文件的 AaptFile 对象
            file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
            //添加到刚才创建的 AaptGroup 对象中,至此 AndroidManifest.xml 文件收录完毕
            status_t res = group->addFile(file);
            if (res != NO_ERROR) {
                return NULL;
            }
            break;

        } else {
            ssize_t i = dir->getDirs().indexOfKey(rootStr);
            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;
            }
        }
    }

    mGroupEntries.add(entry);
    if (outGroup) *outGroup = group;
    return file;
}

收录 AndroidManifest.xml 文件 之后,开始收录 assets 类型的资源文件,这里主要涉及两个方法,makeDir() 和 slurpFullTree(),这两个方法都是定义在 AaptDir 类里面的,其中 makeDir()用来创建对应的 AaptDir 对象,并添加到 AaptAssets 的 mDirs 变量里面,而 slurpFullTree() 方法则是递归地收录 assets 文件夹下的所有资源。我们先看一下 makeDir() 方法的实现:

sp<AaptDir> AaptDir::makeDir(const String8& path)
{
    String8 name;
    String8 remain = path;
    //AaptAssets 继承于 AaptDir,向上转型
    sp<AaptDir> subdir = this;
    //AaptAssets 以树的储存结构客观地反映了资源文件本身的层级结构,根据传入的 path 一级一级地创建 AaptDir 对象
    //不过实际上,传入这里的 path 类似于 "assets",不会走到循环里面去
    while (name = remain.walkPath(&remain), remain != "") {
        subdir = subdir->makeDir(name);
    }
    //查询 mDirs 变量中是否有对应的 AaptDir 对象
    ssize_t i = subdir->mDirs.indexOfKey(name);
    if (i >= 0) {
        return subdir->mDirs.valueAt(i);
    }
    //创建新的 AaptDir 对象
    sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
    //添加到 mDirs 集合里面
    subdir->mDirs.add(name, dir);
    return dir;
}

创建好对应的 AaptDir 对象之后,就会调用它的 slurpFullTree() 方法递归地收录资源文件了,实际上就是遍历指定的资源文件夹,递归查找所有的子元素,然后按照资源文件本身的层级结构储存起来。

/*
 * @param bundle 保存命令行信息的 Bundle 对象
 * @param srcDir 资源目录(相对于 AAPT 命令执行的路径)
 * @param resType 资源类型名,assets类型的资源的话直接传了个 String8()
 */
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());
        ......
        //遍历 srcDir 目录下所有的文件以及文件夹,并把它们的名字保存在 类型为 Vector 的 fileNames 变量里面
        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);
            ......
        }
        closedir(dir);
    }

    ssize_t count = 0;

    /*
     * 遍历 fileNames,开始递归地收录资源文件,
     * 如果子元素是文件的话,则直接添加到 AaptDir 的 mFiles 集合里面,
     * 如果子元素是文件夹的话,创建新的 AaptDir 对象添加到 mDirs 集合,
     * 然后递归地调用 slurpFullTree() 方法,直到 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());
        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);
            }
            count += res;
        } 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 类型的资源文件收录完毕,随后开始收录 res 类型的资源,res 类型的资源也是通过 makeDir() 和 slurpFullTree() 这两个方法收录的,但是相比于 assets 类型的资源来说,res 类型的资源在逻辑上更加复杂一些,它是以 Overlay 的形式来处理多个资源文件夹的,并且它需要检查 res 下面子文件夹的命名是否有效,然后根据子文件夹的命名来解析出对应的资源配置信息,即 AaptGroupEntity 对象。收录 res 类型资源的入口方法为 AaptAssets 对象的 slurpResourceTree() 方法:

//收录对应目录的 res 类型的资源
ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
{
    ssize_t err = 0;
    DIR* dir = opendir(srcDir.string());
    ......
    status_t count = 0;

    //遍历 srcDir 目录下的子文件夹,
    while (1) {
        struct dirent* entry = readdir(dir);
        if (entry == NULL) {
            break;
        }

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

        String8 subdirName(srcDir);
        subdirName.appendPath(entry->d_name);

        AaptGroupEntry group;
        String8 resType;
        //根据子文件夹的名字解析出对应的资源配置对象 group 以及资源类型名 resType
        bool b = group.initFromDirName(entry->d_name, &resType);
        ......
        //忽略低于指定版本的资源(--max-res-version 参数指定)
        if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) {
            int maxResInt = atoi(bundle->getMaxResVersion());
            const char *verString = group.getVersionString().string();
            int dirVersionInt = atoi(verString + 1); // skip 'v' in version name
            if (dirVersionInt > maxResInt) {
              fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name);
              continue;
            }
        }

        FileType type = getFileType(subdirName.string());

        //res 目录下的子文件夹才会被收录
        if (type == kFileTypeDirectory) {
            //熟悉的 makeDir() 和 slurpFullTree() 方法,一个 res 资源类型对应一个 AaptDir 对象,
            //所以就算有多个 drawable 资源文件夹,实际上也只会生成一个 AaptDir 对象
            sp<AaptDir> dir = makeDir(resType);
            ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
                                                resType, mFullResPaths);
            ......
            if (res > 0) {
                //把资源配置信息添加到 AaptAssets 对象的 mGroupEntries 集合里面
                mGroupEntries.add(group);
                count += res;
            }
            //重点!!! res 类型的 AaptDir 对象同时会被添加进 AaptAssets 对象的 mResDirs 集合里面
            sp<AaptDir> rdir = resDir(resType);
            if (rdir == NULL) {
                mResDirs.add(dir);
            }
        } else {
            //res 目录下的子文件不会收录,直接忽略
            if (bundle->getVerbose()) {
                fprintf(stderr, "   (ignoring file '%s')\n", subdirName.string());
            }
        }
    }
    ......
    return count;
}

res 类型的资源收录完成之后,会继续收录直接写在 AAPT 命令最后面的那些文件夹的资源,这些也是 assets 类型的资源,与 -A 参数指定的 assets 资源目录不同的是它们会直接打进 apk 的根目录。至此,所有资源文件都已经收录完毕。再回到 Command.cpp 的 doPackage() 方法里面:

int doPackage(Bundle* bundle)
{
    ......
    //到此资源收录完毕
    ......
    if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
        //如果存在 res 类型的资源或者 AndroidManifest.xml 文件,则需要进行编译
        err = buildResources(bundle, assets, builder);
        ......
    }
}

从上可以看出,会调用 buildResources() 方法来编译资源文件,这个方法定义在 Main.h 头文件里面,具体实现是在 Resource.cpp 里面,这个方法比较长,我们目前只关注资源收集相关的代码即可:

status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder)
{
    //找出之前收录到的名字为 "AndroidManifest.xml" 的 AaptGroup 对象
    sp<AaptGroup> androidManifestFile =
            assets->getFiles().valueFor(String8("AndroidManifest.xml"));
    ......
    //解析 AndroidManifest.xml 文件,获取包名、SDK版本之类的基本信息,因为创建资源表需要包名,
    //资源表就是一个 ResourceTable 对象,AAPT 也是根据这个资源表来打包资源的
    status_t err = parsePackage(bundle, assets, androidManifestFile);
    ......
    //判断资源表的类型,这个类型决定了 R.java 里面资源 ID 的高位字节的值是 0x7f(App)、  
    //0x01(System) 还是 0x00(SharedLibrary),默认是 App 类型
    ResourceTable::PackageType packageType = ResourceTable::App;
    if (bundle->getBuildSharedLibrary()) {
        packageType = ResourceTable::SharedLibraL
    } else if (bundle->getExtending()) {
        packageType = ResourceTable::System;
    } else if (!bundle->getFeatureOfPackage().isEmpty()) {
        packageType = ResourceTable::AppFeature;
    }
    //通过资源包名、资源类型创建资源表对象
    ResourceTable table(bundle, String16(assets->getPackage()), packageType);
    //添加引用资源包的路径,即把 -I 指定的路径添加到 AaptAssets 对象的 mIncludedAssets 变量里面去
    err = table.addIncludedResources(bundle, assets);
    ......
    //关键操作,AaptAssets 从 mResDirs 集合里面把 res 类型的资源都收集到 mRes 集合里面去
    KeyedVector<String8, sp<ResourceTypeSet> > *resources = 
            new KeyedVector<String8, sp<ResourceTypeSet> >;
    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;

    //分配收集到的资源类型集合 ResourceTypeSet
    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);

    //资源收集完毕,setResources() 即是给 AaptAssets 对象的 mRes 集合赋值
    assets->setResources(resources);
    //处理那些 Overlay 包,即把每一个 Overlay 对应的 AaptAssets 对象都按照上述方法把 res 资源收集一遍
    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();
    }
    //Overlay 包里面的资源覆盖基础包的资源,即如果 -S 参数指定了多个资源文件夹,左边覆盖右边同名同配置信息的资源
    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;
    }

    //资源编译相关的代码从这里开始
    ......
}

上述代码有两个比较重要的方法,一个是 collect_files(),这个方法会遍历 AaptAssets 对象的 mResDirs 集合,把之前收录到的 res 资源收集到 mRes 集合,这是 AaptDir 到 ResourceTypeSet 的转变:

static void collect_files(const sp<AaptAssets>& ass,
        KeyedVector<String8, sp<ResourceTypeSet> >* resources)
{
    const Vector<sp<AaptDir> >& dirs = ass->resDirs();
    int N = dirs.size();
    //遍历 mResDirs 集合
    for (int i=0; i<N; i++) {
        sp<AaptDir> d = dirs.itemAt(i);
        if (kIsDebug) {
            printf("Collecting dir #%d %p: %s, leaf %s\n", i, d.get(), d->getPath().string(),
                    d->getLeaf().string());
        }
        //具体的资源收集方法
        collect_files(d, resources);
        ......
        //收集完毕后从 AaptAssets 对象的 mDirs 集合里面移除
        ass->removeDir(d->getLeaf());
    }
}

//具体的资源收集方法
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();
    //遍历 AaptDir 收录的所有 AaptGroup 对象
    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();
        ......
        //获取这个 AaptGroup 对象的资源类型名
        String8 resType = files.valueAt(0)->getResourceType();
        //判断是否已经包含这个资源类型的 ResourceTypeSet 对象了,没有则创建一个新的
        ssize_t index = resources->indexOfKey(resType);
        if (index < 0) {
            //创建 ResourceTypeSet 对象
            sp<ResourceTypeSet> set = new ResourceTypeSet();
            ......
            //添加这个 AaptGroup 对象到 ResourceTypeSet
            set->add(leafName, group);
            //添加 ResourceTypeSet 对象到 AaptAssets 对象的 mRes 集合里面
            resources->add(resType, set);
        } else {
            //已经存在这个 res 类型的 ResourceTypeSet 对象
            sp<ResourceTypeSet> set = resources->valueAt(index);
            //判断 ResourceTypeSet 对象是否已经存在这个文件名对应的 AaptGroup 对象
            index = set->indexOfKey(leafName);
            if (index < 0) {
                ......
                //没有则把它添加到 ResourceTypeSet
                set->add(leafName, group);
            } else {
                sp<AaptGroup> existingGroup = set->valueAt(index);
                ......
                //有则遍历这个 AaptGroup 的所有文件,并依次添加到已经存在的 AaptGroup 对象里面去
                for (size_t j=0; j<files.size(); j++) {
                    ......
                    existingGroup->addFile(files.valueAt(j));
                }
            }
        }
    }
}

另外一个比较重要的方法是 applyFileOverlay(),Overlay 包以优先级从低到高的顺序依次覆盖基础包(例如 -S 指定了 3 个资源文件夹 A、B、C,则依次用 B、A 来 覆盖 C 的资源文件):

static bool applyFileOverlay(Bundle *bundle,
                             const sp<AaptAssets>& assets,
                             sp<ResourceTypeSet> *baseSet,
                             const char *resType)
{
    ......
    sp<AaptAssets> overlay = assets->getOverlay();
    String8 resTypeString(resType);
    ......
    //根据优先级依次覆盖指定的资源类型(resType)
    while (overlay.get()) {
        KeyedVector<String8, sp<ResourceTypeSet> >* overlayRes = overlay->getResources();
        // 查看 Overlay 包是否存在这个资源类型的 ResourceTypeSet 对象
        ssize_t index = overlayRes->indexOfKey(resTypeString);
        if (index >= 0) {
            sp<ResourceTypeSet> overlaySet = overlayRes->valueAt(index);
            size_t overlayCount = overlaySet->size();
            //遍历 Overlay 包中 ResourceTypeSet 的 AaptGroup 对象
            for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) {
                ......
                ssize_t baseIndex = -1;
                if (baseSet->get() != NULL) {
                    //基础包是否存在这个 AaptGroup 对象
                    baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex));
                }
                //基础包存在对应的 AaptGroup 对象 
                if (baseIndex >= 0) {
                    sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
                    //基础包对应的 AaptGroup 对象 
                    sp<AaptGroup> baseGroup = (*baseSet)->valueAt(baseIndex);

                    DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
                            overlayGroup->getFiles();
                    ......
                    size_t overlayGroupSize = overlayFiles.size();
                    //遍历 Overlay 包 AaptGroup 对象里面的 AaptFile
                    for (size_t overlayGroupIndex = 0;
                            overlayGroupIndex<overlayGroupSize;
                            overlayGroupIndex++) {
                        //查询基础包里面是否有这个 AaptFile
                        ssize_t baseFileIndex =
                                baseGroup->getFiles().indexOfKey(overlayFiles.
                                keyAt(overlayGroupIndex));
                        if (baseFileIndex >= 0) {
                            ......
                            //有则从基础包的 AaptGroup 对象移除这个 AaptFile
                            baseGroup->removeFile(baseFileIndex);
                        } else {
                           //没有的话直接添加到基础包的 AaptGroup 对象里面
                           ......
                        }
                        //基础包的 AaptGroup 对象添加 这个 AaptFile
                        baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex));
                        assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
                    }
                } else {
                    ......
                     //基础包不存在对应的 AaptGroup 对象,即这个资源文件只存在 Overlay 包里面,则直接添加到基础包里面去
                    (*baseSet)->add(overlaySet->keyAt(overlayIndex),
                            overlaySet->valueAt(overlayIndex));
                    sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex);
                    DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles =
                            overlayGroup->getFiles();
                    size_t overlayGroupSize = overlayFiles.size();
                    for (size_t overlayGroupIndex = 0;
                            overlayGroupIndex<overlayGroupSize;
                            overlayGroupIndex++) {
                        assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex));
                    }
                }
            }
        }
        //迭代处理下一个优先级的 Overlay 包
        overlay = overlay->getOverlay();
    }
    return true;
}

到这里,所有的资源文件就已经收集完毕了。

总结

这片文章主要分析了 AAPT 是如何用一个 AaptAssets 对象来描述以及收集所有资源文件的,资源文件收集完成之后,AAPT 会把所有的资源都写进一张表里面,即一个 ResourceTable 对象里面,然后最终会根据这个 ResourceTable 对象来打包生成资源包文件。上述源码分析的大致流程整理为如下的时序图:


  转载请注明: ThisMJ AAPT源码分析(二)

 本篇
AAPT源码分析(二) AAPT源码分析(二)
准备此篇接着 AAPT源码分析(一) 继续分析 AAPT 的资源编译打包流程。 在资源编译之前,首先需要收集所有有效的资源文件,AAPT 用一个 AaptAssets 对象来描述收集到的资源文件集合。AaptAssets 类继承于 Aapt
2019-04-10
下一篇 
RxJava-理解响应式编程 RxJava-理解响应式编程
前言如何系统地学习一个优秀的开源框架?我认为比较正确的方式应该是,先了解它的设计思想,然后学会如何去使用它,最后通过阅读源码来弄清楚它的实现原理。所以在学习 RxJava 之前,我们需要先对响应式编程有个大概的理解。 编程范式编程范式是如何
2019-03-26
  目录