Android资源管理框架-------之资源信息的加载(五)

        在Android资源管理框架-------之Android中的资源包(二)一文中,我们讲了Android对资源包的管理,当然这个管理我们说得比较宏观。在了解了Android资源管理相关的一些数据结构后,我们就可以深入地学习Android对资源包的管理和组织了,本篇我们先说资源信息的加载。

        在Android资源管理框架-------之Android中的资源包(二)一文中,我们讲到当应用启动后,framework会通过ResourcesManager.getTopLevelResources()方法来创建AssetManager对象,并调用AssetManager的相关方法,将该应用本身(它自己也是一个资源包)、资源共享库、overlay包统统添加进去。具体怎么实现的呢?我们接着往下看:

    //framework/base/core/java/android/content/res/AssetManager.java
    
    public final int addAssetPath(String path) {
        synchronized (this) {
            //添加资源包,res是cookie值,也就是该资源包在AssetManager中所有资源包中的索引值+1
            int res = addAssetPathNative(path);
            //提取出该资源包的Global(Value) String Pool
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }

    public final int addOverlayPath(String idmapPath) {
        synchronized (this) {

            //添加资源包,res是cookie值,也就是该资源包在AssetManager中所有资源包中的索引值+1
            int res = addOverlayPathNative(idmapPath);
            //提取出该资源包的Global(Value) String Pool
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }

        其中,添加应用本身和资源共享库调用的是addAssetPath方法,添加overlay包调用的是addOverlayPath方法。makeStringBlocks以及addOverlayPath方法的实现我们分别在Android资源管理框架-------之Android中的资源包(二)和Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)中已经讲过,这里不再赘述。我们看addAssetPathNative方法,它对应的native实现是:

//frameworks/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
                                                       jstring path)
{
    //判空检查
    ScopedUtfChars path8(env, path);
    if (path8.c_str() == NULL) {
        return 0;
    }
    //拿到native层的AssetManager对象的地址,其实这个地址保存在java层的一个变量中,这里只是获取一下
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }

    int32_t cookie;
    //添加之,cookie是输出参数
    bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
    //添加成功则返回cookie值,否则返回0
    return (res) ? static_cast<jint>(cookie) : 0;
}

        这个比较简单,我们看AssetManageraddAssetPath方法的实现:

//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
    //路径合法性检查,略去
    
    ......
    
    //如果已经添加过则不再重复添加
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                //cookie 的值为 索引 + 1
                *cookie = static_cast<int32_t>(i+1);
            }
            return true;
        }
    }

    //检查资源包中是否存在AndroidManifest.xml,如果不存在,则返回false

    ......

    //路径列表中添加该路径
    mAssetPaths.add(ap);
    if (cookie) {
        //cookie 的值为 索引 + 1
        *cookie = static_cast<int32_t>(mAssetPaths.size());
    }

   #ifdef HAVE_ANDROID_OS
    // 顺便还会查找和添加overlay package
    asset_path oap;
    for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
        mAssetPaths.add(oap);
    }
   #endif

    //这里才是重点,资源包中的resources.arsc就是在这里加载的
    if (mResources != NULL) {
        appendPathToResTable(ap);
    }

    return true;
}

        我们看到,这个方法主要做了两件事情:把path加入到mAssetPaths中去;加载path对应的资源包中的resources.arsc。其中,后者是在appendPathToResTable方法中实现的。我们注意到,执行这个方法前先去判断mResources是否为空。mResourcesAssetManager类的成员,它的类型为ResTable,并且在构造AssetManager对象的时候,并不会给这个成员赋值,也就是说,如果在AssetManager对象刚构造完,就调用addAssetPath方法添加资源包,这个时候,资源包是不会被加载的。那这种情况下,到底在什么时候加载呢?

//frameworks/base/libs/androidfw/AssetManager.cpp
const ResTable* AssetManager::getResTable(bool required) const
{
    //如果已经构造过,则返回
    ResTable* rt = mResources;
    if (rt) {
        return rt;
    }

    mResources = new ResTable();
    //主要是更新Locale信息,设置资源配置信息
    updateResourceParamsLocked();

    bool onlyEmptyResources = true;
    const size_t N = mAssetPaths.size();
    //在这里会按mAssetPaths中所有的路径来加载resources.arsc
    for (size_t i=0; i<N; i++) {
        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
        onlyEmptyResources = onlyEmptyResources && empty;
    }
    //required默认是true,还未见用false的场景,也就是说只要mAssetPaths中
    //有一个路径下找不到resources.arsc,就会清空mResources
    if (required && onlyEmptyResources) {
        ALOGW("Unable to find resources file resources.arsc");
        delete mResources;
        mResources = NULL;
    }
}

        也就是说,AssetManageraddAssetPath方法,未必会立即去加载资源包中的resources.arsc,有可能会有一个延迟,延迟到构造它的mResources成员,也就是ResTable实例的时候再去加载。那么这个getResTable方法具体会在什么时候调用呢?在我们查寻AssetManager中资源id、资源项、加载资源、获取StringBlock等的时候,都会调用这个方法,进而加载resources.arsc。

        接下来看看appendPathToResTable是如何加载resources.arsc的:

//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    /*
     * 在这里ass表示我们的资源包中的resources.arsc,
     * sharedRes则是用来存放资源包中的resources.arsc中的具体数据
     * 由于一个ResTable对象可以添加多个资源包,所以ResTable可以简单认为是Asset的集合
     */
    Asset* ass = NULL;
    ResTable* sharedRes = NULL;
    
    bool shared = true;
    bool onlyEmptyResources = true;
    MY_TRACE_BEGIN(ap.path.string());
    //idmap 是RRO相关的概念,这里不再多说
    Asset* idmap = openIdmapLocked(ap);
    /*
     * 这里要先看一下已经加载过多少个资源包了,
     * 如果没有加载过,那么就认为默认加载的头一个资源包是framework-res.apk
     * 也就是Android的系统资源包
     */
    size_t nextEntryIdx = mResources->getTableCount();

    /*
     * 我们要加载resources.arsc,大多数情况这个文件位于压缩包也就是apk中
     * 对应我们这个if分支
     * 但AssetManager也是支持对解压出来的resources.arsc的
     * 这种情况对应下面的else分支
     */
     //resources.arsc在apk中
    if (ap.type != kFileTypeDirectory) {
        
        if (nextEntryIdx == 0) {//加载的是framework-res.apk,也就是Google的Android系统资源包
            //先去缓存中查找系统资源包及其overlay包是否已经加载过,如果加载过,同时会创建
            //ResTable对象,并缓存,也就是sharedRes得到的值肯定不为空
            sharedRes = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTable(ap.path);
            if (sharedRes != NULL) {
                /**
                 * 如果已经创建过了,那么要添加的这个资源包,就应该放到它们后面
                 * 这种情况出现在在同一个进程创建多个不同的AssetManager或者Resources对象的时候,
                 * 每个AssetManager中都会添加系统资源包,这时候就用上这个缓存了
                 */ 
                nextEntryIdx = sharedRes->getTableCount();
            }
        }

        //如果当前要加载的不是系统资源或者是系统资源,但是之前并未加载过,走这里
        if (sharedRes == NULL) {
            //查缓存,看看之前是否加载并创建过Asset对象
            ass = const_cast<AssetManager*>(this)->
                mZipSet.getZipResourceTableAsset(ap.path);
            //如果没有找到,则加载之,即打开resources.arsc,并将得到的ass放入缓存
            if (ass == NULL) {
                ALOGV("loading resource table %s\n", ap.path.string());
                //打开resources.arsc
                ass = const_cast<AssetManager*>(this)->
                    openNonAssetInPathLocked("resources.arsc",
                                             Asset::ACCESS_BUFFER,
                                             ap);
                //放入缓存
                if (ass != NULL && ass != kExcludedAsset) {
                    ass = const_cast<AssetManager*>(this)->
                        mZipSet.setZipResourceTableAsset(ap.path, ass);
                }
            }
            //如果要加载的是系统资源,但这时候还从未加载过,则连同系统资源包的overlay package一并加载
            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());
                //创建ResTable对象
                sharedRes = new ResTable();
                //加入资源包中的resources.arsc以及idmap
                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");
                //加载系统资源包的overlay package,并将这些资源包也加入sharedRes,
                addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
                //缓存sharedRes
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.setZipResourceTable(ap.path, sharedRes);
            }
        }
    } else {//resources.arsc不在apk中,而在某个路径下
        //加载到内存,创建Asset对象
        ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                                     Asset::ACCESS_BUFFER,
                                     ap);
        //不是apk中的不缓存,不共享,添完就删
        shared = false;
    }
    if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
        if (sharedRes != NULL) {
            ALOGV("Copying existing resources for %s", ap.path.string());
            //如果是系统资源包,则将系统资源包连同它的overlay package一起添加到我们的
            //mResources对象当中去
            mResources->add(sharedRes);
        } else {
            ALOGV("Parsing resources for %s", ap.path.string());
            //否则只添加我们要加载的这个资源包(其实仅仅是它的resources.arsc和idmap文件而已)
            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;
}

        appendPathToResTable方法总结起来也很简单,就是查缓存、加载资源(这中间可能创建AssetResTable等对象)、存缓存、异常情况处理(没找到资源时)。但是,由于涉及到等情况比较多,包括:Zygote起来后preload系统资源、应用加载系统资源、应用第一次加载其它资源(包括应用本身、资源共享库、overlay package)、应用第二次加载其它资源共四种情况。

        先说Zygote起来后preload的这种情况。我们知道,在Zygote进程加载完VM,进入java世界后,会去执行ZygoteInit.java的main方法。它会创建serverSocket,用来响应SystemServier进程发出的创建Android应用进程的请求,然后就会预加载很多东西了,包括我们常用的类以及Android的系统资源。这样在Android应用进程从Zygote进程fork出来以后,就不用重新加载了,节约应用的启动时间。这个时候它会创键一个system AssetManager,此时appendPathToResTable方法的流程是:

                nextEntryIdx = 0,从mZipSet缓存中获取sharedRes,结果是NULL,然后从mZipSet缓存中获取ass,结果还是NULL,然后调用openNonAssetInPathLocked打开系统资源包中的resources.arsc文件,并赋值给ass,并把该ass存入mZipSet缓存中,以后就可以从缓存中获取了。然后创建ResTable对象并赋给sharedRes,并把ass添加进sharedRes,然后同样加载系统资源包的overlay包,并添加到sharedRes中。然后再把sharedRes整个存入缓存,这样以后加载系统资源包,就不用一个一个ass地添加了,而是把整个sharedRes一起添加进去就可以了,然后我们看到mResources->add(sharedRes);这一句就是连同系统资源包和它的overlay包一并添加到mResources中的。当然mResourcesResTable的一个实例,也是Assetmanager中最重要的一个成员,资源都加载到它里面了。这样这个system的AssetManager的资源就加载完了。当然,overlay package是如何被添加到sharedRes中的,我们一句带过,想要详细了解请参考Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)。

        再说应用加载系统资源的这种情况,我们前文讲过每一个Android应用的AssetManager中都会加载系统的资源信息,具体是怎么加载的详见Android资源管理框架-------之Android中的资源包(二)。在加载的时候appendPathToResTable方法的流程是:

                nextEntryIdx = 0,从mZipSet缓存中获取sharedRes,结果不为NULL,修正nextEntryIdx = sharedRes->getTableCount();mResources->add(sharedRes);这样,系统资源包就都添加到mResources中了。

        我们再看应用第一次加载其它资源(包括应用本身、资源共享库、overlay package)的情况:nextEntryIdx = mResources->getTableCount();不为0,因为此时已经添加过系统资源包及其overlay包了,sharedRes为空,缓存中查找ass为空,调用openNonAssetInPathLocked方法打开资源包中的resources.arsc文件,并赋值给ass,并把该ass存入mZipSet缓存中,以后就可以从缓存中获取了。此时ass不为空,调用mResources->add(ass, idmap, nextEntryIdx + 1, !shared);完成资源包的加载。

        最后看应用第二次加载其它资源(包括应用本身、资源共享库、overlay package)的情况:nextEntryIdx = mResources->getTableCount();不为0,因为此时已经添加过系统资源包及其overlay包了,sharedRes为空,缓存中查找ass不为空,调用mResources->add(ass, idmap, nextEntryIdx + 1, !shared);完成资源包的加载。

        到这里AssetManagerappendPathToResTable方法总算说完了,这个方法中,最关键的一步是调用ResTable类的对象mResourcesadd方法。add方法重载了好多个,但最终都会走到一下两个方法中的某一个:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t ResTable::add(ResTable* src)
{
    mError = src->mError;
    //添加Headers
    for (size_t i=0; i<src->mHeaders.size(); i++) {
        mHeaders.add(src->mHeaders[i]);
    }
    //添加PackageGroups
    for (size_t i=0; i<src->mPackageGroups.size(); i++) {
        PackageGroup* srcPg = src->mPackageGroups[i];
        PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
        //对每个PackageGroup,添加package
        //需要说明的是overlay package 会和它的target package 放到同一个PackageGroup中
        for (size_t j=0; j<srcPg->packages.size(); j++) {
            pg->packages.add(srcPg->packages[j]);
        }
        ///对每个PackageGroup添加TypeList
        for (size_t j = 0; j < srcPg->types.size(); j++) {
            if (srcPg->types[j].isEmpty()) {
                continue;
            }

            TypeList& typeList = pg->types.editItemAt(j);
            typeList.appendVector(srcPg->types[j]);
        }
        //添加DynamicRefTable
        pg->dynamicRefTable.addMappings(srcPg->dynamicRefTable);
        pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId);
        mPackageGroups.add(pg);
    }
    //
    memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap));

    return mError;
}

        这个方法非常简单,就是对每个ResTable中的数据结构进行添加。DynamicRefTable是资源共享库相关的东西,详见Android资源管理中的SharedLibrary和Dynamic Reference-------之AssetManager的处理(四)。下面我们看另外一个方法:

//frameworks/base/libs/androidfw/ResourceTypes.cpp

/*
 * data 从表示resources.arsc的Asset对象中获取,表示resources.arsc文件的内容
 * dataSize data的大小
 * idmapData 从表示idmap文件的Asset对象中获取,表示idmap文件的内容
 * idmapDataSize idmapData的大小
 * cookie 该资源包的index + 1
 * copyData是否复制data到该ResTable中,对于appendPathToResTable方法中shared
 * 为false的copyData为true,否则为false
 */
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, 
            size_t idmapDataSize, const int32_t cookie, bool copyData)
{
    //......例行检查,代码省略 

    //一个Header对应一个resources.arsc,也就是一个资源包
    Header* header = new Header(this);
    //mHeaders中的元素一般和资源包也就是resources.arsc是对应的
    header->index = mHeaders.size();
    //cookie = mHeaders.size() + 1
    header->cookie = cookie;
    //为idmap数据分配内存,header也会持有其指针
    if (idmapData != NULL) {
        header->resourceIDMap = (uint32_t*) malloc(idmapDataSize);
        if (header->resourceIDMap == NULL) {
            delete header;
            return (mError = NO_MEMORY);
        }
        memcpy(header->resourceIDMap, idmapData, idmapDataSize);
        header->resourceIDMapSize = idmapDataSize;
    }
    //mHeaders表示这个ResTable(或者说AssetManager中所有的资源包信息)
    mHeaders.add(header);
    
    //......大小端的处理,如有必要还会复制resources.arsc的数据

    //resources.arsc开头就是整个文件数据的header
    header->header = (const ResTable_header*)data;
    header->size = dtohl(header->header->header.size);
    //dataEnd,起始地址加上整个resources.arsc的大小
    header->dataEnd = ((const uint8_t*)header->header) + header->size;

    size_t curPackage = 0;
    /**
    *跳过resources.arsc中第一个header
    *也就是整个资源索引表的header
    *需要说明的是整个resources.arsc有一个总的header,前面我们
    *已经解析过,所以跳过
    *另外,resources.arsc的一级子元素只有两种类型:
    *RES_STRING_POOL_TYPE类型的global string pool
    *RES_TABLE_PACKAGE_TYPE类型的资源包
    */
    const ResChunk_header* chunk =
        (const ResChunk_header*)(((const uint8_t*)header->header)
                                 + dtohs(header->header->header.headerSize));
    //遍历解析每一个子元素
    while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) &&
           ((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) {
        //获取当前子元素的数据块的大小和类型
        const size_t csize = dtohl(chunk->size);
        const uint16_t ctype = dtohs(chunk->type);
        if (ctype == RES_STRING_POOL_TYPE) {
            // global string pool
            if (header->values.getError() != NO_ERROR) {
                //把global string pool提取出来
                status_t err = header->values.setTo(chunk, csize);
                if (err != NO_ERROR) {
                    return (mError=err);
                }
            } else {
                ALOGW("Multiple string chunks found in resource table.");
            }
        } else if (ctype == RES_TABLE_PACKAGE_TYPE) {
            //解析包信息
            if (curPackage >= dtohl(header->header->packageCount)) {
                ALOGW("More package chunks were found than the %d declared in the header.",
                     dtohl(header->header->packageCount));
                return (mError=BAD_TYPE);
            }
            //具体解析工作交给parsePackage方法
            if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
                return mError;
            }
            curPackage++;
       } else {
           ALOGW("Unknown chunk type 0x%x in table at %p.\n",
                ctype,
                    (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
       }
       //处理下一个子元素
       chunk = (const ResChunk_header*)
            (((const uint8_t*)chunk) + csize);
    }

    //一系列检查,先不必关注
    if (curPackage < dtohl(header->header->packageCount)) {
        //没找全
        ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.",
             (int)curPackage, dtohl(header->header->packageCount));
        return (mError=BAD_TYPE);
    }
    mError = header->values.getError();
    if (mError != NO_ERROR) {
        //包中没有global string pool
        ALOGW("No string values found in resource table!");
    }

    TABLE_NOISY(ALOGV("Returning from add with mError=%d\n", mError));
    return mError;
}

        这个方法其实就是解析resources.arsc的二级chunk,然后通过ResTable::Header的形式记录之,并把它添加到ResTable的mHeaders中。我们接下来看parsePackage方法,这个方法比较长,跟本文关系不大的内容这里就不贴出来了:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t ResTable::parsePackage(const ResTable_package* const pkg,
                                const Header* const header)
{
     const uint8_t* base = (const uint8_t*)pkg;
     const uint32_t pkgSize = dtohl(pkg->header.size);
     //拿到这个资源包resources.arsc中写的包id
     uint32_t id = dtohl(pkg->id);
     
     //idmap相关的东西,和本文关系不大,略去......
     
     PackageGroup* group = NULL;
     Package* package = new Package(this, header, pkg);
     /**
     *写入type string pool,不过最后一个参数是错的
     *不过ResStringPool有自己的解析方法,这个参数的值无所谓
     */
     err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
                                   header->dataEnd-(base+dtohl(pkg->typeStrings)));
     /**
     *写入type string pool,不过最后一个参数是错的
     *不过ResStringPool有自己的解析方法,这个参数的值无所谓
     */
     err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
                                  header->dataEnd-(base+dtohl(pkg->keyStrings)));
     //mPackageMap的值-1就是该id对应的包所在的PackageGroup在mPackageGroups中的索引
     //另外我们看到,这个id的值是target Package的id也就是说此时它是按target pacakge来处理的
     size_t idx = mPackageMap[id];
     if (idx == 0) { 
         //idx = 0,表示这个包还没有加载过,修正为它真正的idx也就是index + 1
         idx = mPackageGroups.size() + 1;
         //获取包名
         char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])];
         strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0]));
         /**
          * 创建PackageGroup对象,我们不考虑overlay package的情况下,
          * 一个资源包,也就对应一个PackageGroup
          */
         group = new PackageGroup(this, String16(tmpName), id);
         if (group == NULL) {
            delete package;
            return (mError=NO_MEMORY);
        }
        //添加进去
        err = mPackageGroups.add(group);
        if (err < NO_ERROR) {
            return (mError=err);
        }
        //资源共享库相关的处理,这里可以不用关注
        size_t N = mPackageGroups.size();
        for (size_t i = 0; i < N; i++) {
            //给ResTable中的所有PackageGroup,添加运行时该包的id,
            //方便其它包引用当前包里面的资源
            mPackageGroups[i]->dynamicRefTable.addMapping(
                    group->name, static_cast<uint8_t>(group->id));
        }
     } else {
         //这种情况下,如果有overlay package,我们在这里不考虑,略去......
         //否则就是往同一个AssetManager对象中,添加了两个id相同的包,
         //最好不要这么干,有可能引起typeId混乱,而出错。
     }
     //把new出来的package添加到group中
     err = group->packages.add(package);

     //跳过package的header
     const ResChunk_header* chunk =
        (const ResChunk_header*)(((const uint8_t*)pkg)
                                 + dtohs(pkg->header.headerSize));
     const uint8_t* endPos = ((const uint8_t*)pkg) + dtohs(pkg->header.size);
     /**
     *解析resources.arsc中package数据块的内部数据
     *一共有三种子结构(type stringpool、keystring pool除外,前面我们已经提取这两个了):
     *RES_TABLE_TYPE_SPEC_TYPE
     *RES_TABLE_TYPE_TYPE
     *RES_TABLE_LIBRARY_TYPE
     */
     while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) &&
           ((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) {
         //当前数据块的大小和类型
         const size_t csize = dtohl(chunk->size);
         const uint16_t ctype = dtohs(chunk->type);
         //处理TYPE_SPEC数据块
         if (ctype == RES_TABLE_TYPE_SPEC_TYPE) {
             const ResTable_typeSpec* typeSpec = (const ResTable_typeSpec*)(chunk);
             //拿到占用的空间和entryCount
             const size_t typeSpecSize = dtohl(typeSpec->header.size);
             const size_t newEntryCount = dtohl(typeSpec->entryCount);
             //有entry的话
             if (newEntryCount > 0) {
                 //type都是从1开始计数的,所以要 - 1
                 uint8_t typeIndex = typeSpec->id - 1;
                 
                 //idmap相关的东西,略去......

                 //根据typeIndex直接找到typeList,不考虑overlay package的情况下,此时的typeList应该是空的
                 TypeList& typeList = group->types.editItemAt(typeIndex);
                 if (!typeList.isEmpty()) {
                     //target包的Type
                     const Type* existingType = typeList[0];
                     /**
                     *这个if语句出现的情景是:同一个AssetManager加载了两个id相同的包
                     */
                     if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
                         ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
                             (int) newEntryCount, (int) existingType->entryCount);
                         // We should normally abort here, but some legacy apps declare
                         // resources in the 'android' package (old bug in AAPT).
                     }
                 }
                 //创建type并写入相关信息
                 Type* t = new Type(header, package, newEntryCount);
                 t->typeSpec = typeSpec;
                 t->typeSpecFlags = (const uint32_t*)(
                    ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
                 
                 //idmap相关的东西,略去......
                 
                 //加入到target pakage对应的typeList里面
                 typeList.add(t);
             }
         } else if (ctype == RES_TABLE_TYPE_TYPE) {
             const ResTable_type* type = (const ResTable_type*)(chunk);
             //拿到占用的空间和entryCount
             const uint32_t typeSize = dtohl(type->header.size);
             const size_t newEntryCount = dtohl(type->entryCount);
             //如果有entry
             if (newEntryCount > 0) {
                //该type的索引
                uint8_t typeIndex = type->id - 1;
                                 
                 //idmap相关的东西,略去......
                 
                //拿到我们之前添加进去的Type对象
                Type* t = typeList.editItemAt(typeList.size() - 1);
                //把该种配置下的资源添加进去
                t->configs.add(type);
             }
         } else if (ctype == RES_TABLE_LIBRARY_TYPE) {
             //资源共享库相关的东西,和本文关系不大,如有兴趣:
             //https://blog.csdn.net/dayong198866/article/details/95226237
         } else {
                 //......
         }
         chunk = (const ResChunk_header*)
            (((const uint8_t*)chunk) + csize);
     }
     return NO_ERROR;
}

        这个方法就是根据传过来的resources.arsc中的RES_TABLE_PACKAGE_TYPE类型的chunk,创建PackageGroupPackageTypeListType等对象,并把该chunk中的数据地址写入到这些创建的数据结构中,完成对resources.arsc的解析。后面我们就可以通过这些数据结构,来进行对资源的管理工作了。另外,本文力求简洁,略去了RRO和idmap相关的处理,如果您对这部分感兴趣,请移步Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)。

        我们这里介绍的东西,绝大部分都和Android资源管理的那些数据结构关系非常紧密,如果您对这些数据结构不太熟悉,可能读起本文来会比较费力,因此建议您先仔细阅读前面的两篇文章,对这些数据结构比较熟悉后,读起本文就会容易许多。

你可能感兴趣的:(#,AssetManager)