Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)

        前文我们介绍了idmap文件的生成过程,剩下来的就是overlay package和idmap的加载和生效了。

        我们在Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)中讲到PackageManagerService中的createIdmapForPackagePairLI方法的时候,不知道大家有没有注意到这些代码:

private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
            PackageParser.Package opkg) {
    //......省略
    //把所有overlay package的路径写入target package的applicationInfo
    pkg.applicationInfo.resourceDirs = new String[overlayArray.length];
    int i = 0;
    for (PackageParser.Package p : overlayArray) {
        pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath;
    }
}

        也就是说,创建完idmap,PMS还会把overlay package的路径写入到target包的applicationInfo里面去,供我们的target package起来的时候加载。关于应用的启动过程,我们就不在这里多说了,应用起来后会调到ResourcesManager的getTopLevelResources方法:

//frameworks/base/core/java/android/app/ResourcesManager.java
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
            String[] overlayDirs, String[] libDirs, int displayId,
            Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
            
             Resources r;
            //先去查缓存,如果已经加载过,直接返回
            //..............

            //创建AssetManager,真正的资源管理类对象
            AssetManager assets = new AssetManager();
            //我们的应用本身也是一个资源包,应该把它的路径加入到AssetManager
            if (resDir != null) {
                if (assets.addAssetPath(resDir) == 0) {
                    return null;
                }
            }

           //...............
           //加入所有overlay package的路径
           if (overlayDirs != null) {
               for (String idmapPath : overlayDirs) {
                   assets.addOverlayPath(idmapPath);
               }
           }

           //.................

           //创建Resources类对象
           r = new Resources(assets, dm, config, compatInfo, token);
           return r;

}

        这里的overlayDirs就是target package启动的时候从它的ApplicationInfo中获取的。AssetManager实例会把所有的overlay package的路径添加进去,然后加载里面的resources.arsc文件:

    //frameworks/base/core/java/android/content/res/AssetManager.java
    public final int addOverlayPath(String idmapPath) {
        synchronized (this) {
            //native 方法
            int res = addOverlayPathNative(idmapPath);
            /**
             *我们要注意的是,此时AssetManager已经加载了系统资源包0x01和它本身0x7f
             *也就是说现在已经有至少两个资源包(如果有SOC或者手机厂商的系统资源包的话也会加载)了
             *一般每多一个资源包也就会多一个StringBlock,一个StringBlock就对应一个
             *资源包中resources.arsc中的global string pool。所以这个makeStringBlocks
             *也就是把overlay package的global string pool加载进来。
             */
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }

        我们走到JNI层,先看看addOverlayPathNative的实现:

//framework/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
                                                     jstring idmapPath)
{
    //idmapPath这个变量名差评,这明明是overlay包的路径,叫overlayPath比较合适
    ScopedUtfChars idmapPath8(env, idmapPath);
    if (idmapPath8.c_str() == NULL) {
        return 0;
    }

    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }

    int32_t cookie;
    //交给AssetManager,返回cookie值
    bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);

    return (res) ? (jint)cookie : 0;
}

        这里的cookie到底是什么呢?我们知道前面我们已经加载了android系统资源包framework-res.apk(id:0x01)和应用本身资源包(0x7f),如果此时AssetManager没有加载别的资源包,那么cookie的值将会是2.也就是说这里的cookie就是我们要添加的资源包在AssetManager中的索引值 + 1。我们先看一个函数:

    //frameworks/base/libs/androidfw/AssetManager.cpp
    String8 idmapPathForPackagePath(const String8& pkgPath)
    {
        const char* root = getenv("ANDROID_DATA");// /data
        LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
        String8 path(root);
        path.appendPath(kResourceCache);// /data/resource-cache

        char buf[256]; // 256 chars should be enough for anyone...
        strncpy(buf, pkgPath.string(), 255);
        buf[255] = '\0';
        char* filename = buf;
        //这里的pkgPath是绝对路径,所以跳过第一个/
        while (*filename && *filename == '/') {
            ++filename;
        }
        char* p = filename;
        //将剩下的/替换为@
        while (*p) {
            if (*p == '/') {
                *p = '@';
            }
            ++p;
        }
        // /data/resource-cache/xxx@[email protected]
        path.appendPath(filename);
        // /data/resource-cache/xxx@[email protected]@idmap
        path.append("@idmap");

        return path;
    }

        这个函数的作用就是根据我们overlay package的路径,返回idmap文件的路径。如果我们的overlay package路径是/vendor/overlay/demo.apk,那么返回的就是/data/resource-cache/vendor@[email protected]@idmap。我们看addOverlayPath方法的实现:

//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
{
    //根据overlay package的路径,创建idmap文件的路径
    const String8 idmapPath = idmapPathForPackagePath(packagePath);
    //mAssetPaths存储已经添加的所有资源包的路径
    for (size_t i = 0; i < mAssetPaths.size(); ++i) {
        //如果这个路径已经添加过了,就直接返回
        if (mAssetPaths[i].idmap == idmapPath) {
            //返回cookie的值,索引+1
            *cookie = static_cast<int32_t>(i + 1);
            return true;
         }
    }
    /**
     *一连串的检查,包括idmap文件能否访问、里面的target path与overlay path是否正确等等
     */
    //创建 asset_path,类型、path、idmap 三元组;
    oap.path = overlayPath;
    oap.type = ::getFileType(overlayPath.string());
    oap.idmap = idmapPath;
    //加进去,mAssetPaths是一个Vector,存放本AssetManager所有的资源包路径
    mAssetPaths.add(oap);
    //索引值+1
    *cookie = static_cast<int32_t>(mAssetPaths.size());
    //将该资源包的resources.arsc加载、解析、加入ResTable
    //在Android5.x上,居然没有这关键的一句,整个RRO完全不能用。
    if (mResources != NULL) {
        appendPathToResTable(oap);
    }
    return true;
}

        在AssetManager眼里,overlay package和应用本身一样,也是一个资源包,它的路径也会放到mAssetPaths里面去。其实,不仅仅如此,overlay package还会和系统资源包以及我们的target package一样,被添加到ResTable里。

//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap) const 
{
    //打开idmap文件
    Asset* idmap = openIdmapLocked(ap);
    //先从ResTable以及已经加载了的resources.arsc缓存里查,如果查到了,直接加载
    //......
    
    //如果没有找到
    //打开overlay package里的resources.arsc
    ass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                                     Asset::ACCESS_BUFFER,
                                     ap);
    /**
    *mResources是ResTable的实例
    *ass  resources.arsc
    *idmap idmap文件
    */
    mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
    //......
}

        这个方法主要负责打开或者找到overlay package的resources.arsc和idmap文件,然后加入到ResTable中去。

//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
    //拿到resources.arsc的内容
    const void* data = asset->getBuffer(true);
    if (data == NULL) {
        ALOGW("Unable to get buffer of resource asset file");
        return UNKNOWN_ERROR;
    }

    size_t idmapSize = 0;
    const void* idmapData = NULL;
    //拿到idmap文件的内容
    if (idmapAsset != NULL) {
        idmapData = idmapAsset->getBuffer(true);
        if (idmapData == NULL) {
            ALOGW("Unable to get buffer of idmap asset file");
            return UNKNOWN_ERROR;
        }
        idmapSize = static_cast<size_t>(idmapAsset->getLength());
    }

    return addInternal(data, static_cast<size_t>(asset->getLength()),
            idmapData, idmapSize, cookie, copyData);
}

        这个方法主要负责拿到resources.arsc和idmap文件的内容。在看方法的实现之前,我们先看一个结构体:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
/**
*ResTable::Header可以看作是对ResTable_header的封装,而一个ResTable_header
*则对应一个resources.arsc或者说一个资源包。
*/
struct ResTable::Header
{
    Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
        resourceIDMap(NULL), resourceIDMapSize(0) { }

    ~Header()
    {
        free(resourceIDMap);
    }
    //所属于的ResTable
    const ResTable* const           owner;
    //
    void*                           ownedData;
    //resources.arsc中的RES_TABLE_TYPE数据块,也就是整个resources.arsc
    const ResTable_header*          header;
    //大小
    size_t                          size;
    //这个在解析string pool的时候会引用到,但是根本没什么卵用
    //因为string pool有自己的一套计算方法
    const uint8_t*                  dataEnd;
    //这个header在该ResTable中的所有headers中的索引
    size_t                          index;
    //这个资源包的cookie,通常一个header对应一个资源包
    //所以 cookied应该等于index + 1
    int32_t                         cookie;
    // global string pool (或者叫 value string pool)
    ResStringPool                   values;
    //idmap文件的内容
    uint32_t*                       resourceIDMap;
    //idmap大小
    size_t                          resourceIDMapSize;
}

        很简单,但也很重要,我们还是顺便了解一下比较好,因为addInternal方法本质就是将resources.arsc和idmap文件构造成一个一个的数据对象:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
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) {
                // Only use the first string chunk; ignore any others that
                // may appear.
                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的一级子元素,然后通过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的内容
     *key: overlay typeId
     *value: 该type下的所有entryId的映射关系
     */
     KeyedVector<uint8_t, IdmapEntries> idmapEntries;
     //如果有idmap数据
     if (header->resourceIDMap != NULL) {
         uint8_t targetPackageId = 0;
         //解析所有的idmap内容,这个后面我们会详细分析
         status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries);
         if (err != NO_ERROR) {
             ALOGW("Overlay is broken");
             return (mError=err);
         }
         /**
         *overlay包的id居然被记录成了别的东西!!!
         *它被记录成了对应的target包的Id,这个Id被用来作为PackageGroup的id,
         *也就是说,所有的overlay package,都会被放到它们所对应的
         *target package所在的那个PackageGroup里面去!!!
         */
         id = targetPackageId;
     }
     
     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,表示这个包还没有加载
         *target Pacakge肯定已经加载了,这里走不到,不表
         */
     } else {
         group = mPackageGroups.itemAt(idx - 1);
     }
     //我们看到,把overlay包加入到了target包所在的PackageGroup里面去了
     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;
                 ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
                 if (idmapIndex >= 0) {
                     //根据overlay package的typeId,从idmap数据中找到
                     //target package中对应的type的ID,再 -1得索引
                     typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
                 }
                 //根据typeIndex直接找到typeList,此时的typeList里
                 //要么是空,要么只有target package的对应Type和其它overlay package的对应Type
                 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));
                 //会把该type中所有entry的映射关系写进去
                 if (idmapIndex >= 0) {
                     t->idmapEntries = idmapEntries[idmapIndex];
                 }
                 //加入到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在overlay package中的索引
                 uint8_t typeIndex = type->id - 1;
                 //该type在idmap中的索引
                 ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
                 if (idmapIndex >= 0) {
                     //找到该type在target package中的索引
                     typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
                 }
                 //根据索引拿到对应的typeList
                 TypeList& typeList = group->types.editItemAt(typeIndex);
                 //拿到typeList中最后一个元素
                 //为什么是最后一个?
                 //因为最后一个才是我们刚才在RES_TABLE_TYPE_SPEC_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;
}

        parsePackage方法主要对我们的overlay package的resources.arsc进行解析,然后把解析出来的数据放到target package所对应的PackageGroup中相应的数据结构中去。比如package会添加到target package所在的PackageGroup中,Type也会放到target package所在的TypeList中。另外,在parsePackage的过程中,必须对idmap数据进行加载和解析:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t parseIdmap(const void* idmap, size_t size, uint8_t* outPackageId, KeyedVector<uint8_t, IdmapEntries>* outMap) {
    /**
    *idmap是整个idmap文件的数据
    *一个IdmapEntries表示一个Type的所有entry的映射信息
    */
    if (!assertIdmapHeader(idmap, size)) {
        return UNKNOWN_ERROR;
    }
    //跳过idmap header
    size -= ResTable::IDMAP_HEADER_SIZE_BYTES;
    if (size < sizeof(uint16_t) * 2) {
        ALOGE("idmap: too small to contain any mapping");
        return UNKNOWN_ERROR;
    }
    //跳过idmap header
    const uint16_t* data = reinterpret_cast<const uint16_t*>(
            reinterpret_cast<const uint8_t*>(idmap) + ResTable::IDMAP_HEADER_SIZE_BYTES);
    //拿到targetPackageId
    uint16_t targetPackageId = dtohs(*(data++));
    if (targetPackageId == 0 || targetPackageId > 255) {
        ALOGE("idmap: target package ID is invalid (%02x)", targetPackageId);
        return UNKNOWN_ERROR;
    }
    //拿到types count
    uint16_t mapCount = dtohs(*(data++));
    if (mapCount == 0) {
        ALOGE("idmap: no mappings");
        return UNKNOWN_ERROR;
    }
    if (mapCount > 255) {
        ALOGW("idmap: too many mappings. Only 255 are possible but %u are present", (uint32_t) mapCount);
    }
    //解析DATA BLOCK,其实就是一个Type一个Type地创建IdmapEntries
    while (size > sizeof(uint16_t) * 4) {
        IdmapEntries entries;
        status_t err = entries.setTo(data, size);
        if (err != NO_ERROR) {
            return err;
        }

        ssize_t index = outMap->add(entries.overlayTypeId(), entries);
        if (index < 0) {
            return NO_MEMORY;
        }

        data += entries.byteSize() / sizeof(uint16_t);
        size -= entries.byteSize();
    }
    //返回targetPackageId
    if (outPackageId != NULL) {
        *outPackageId = static_cast<uint8_t>(targetPackageId);
    }
    return NO_ERROR;
}

        这个方法就是一个Type一个Type地解析所有的entry的映射信息。那么每个Type内部是怎么解析的呢:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
class IdmapEntries {
public:
    IdmapEntries() : mData(NULL) {}

    //是否有entry
    bool hasEntries() const {
        if (mData == NULL) {
            return false;
        }
        return (dtohs(*mData) > 0);
    }

    size_t byteSize() const {
        if (mData == NULL) {
            return 0;
        }
        /**
        *mData[0] = target type id
        *mData[1] = overlay type id
        *mData[2] = entry count
        *mData[3] = entry offset
        */
        uint16_t entryCount = dtohs(mData[2]);
        return (sizeof(uint16_t) * 4) + (sizeof(uint32_t) * static_cast<size_t>(entryCount));
    }

    uint8_t targetTypeId() const {
        if (mData == NULL) {
            return 0;
        }
        return dtohs(mData[0]);
    }

    uint8_t overlayTypeId() const {
        if (mData == NULL) {
            return 0;
        }
        return dtohs(mData[1]);
    }

    //setTo很简单,提取处targetTypeId、overlayTypeId
    //指针指过去,完事
    status_t setTo(const void* entryHeader, size_t size) {
        if (reinterpret_cast<uintptr_t>(entryHeader) & 0x03) {
            ALOGE("idmap: entry header is not word aligned");
            return UNKNOWN_ERROR;
        }

        if (size < sizeof(uint16_t) * 4) {
            ALOGE("idmap: entry header is too small (%u bytes)", (uint32_t) size);
            return UNKNOWN_ERROR;
        }

        const uint16_t* header = reinterpret_cast<const uint16_t*>(entryHeader);
        const uint16_t targetTypeId = dtohs(header[0]);
        const uint16_t overlayTypeId = dtohs(header[1]);
        if (targetTypeId == 0 || overlayTypeId == 0 || targetTypeId > 255 || overlayTypeId > 255) {
            ALOGE("idmap: invalid type map (%u -> %u)", targetTypeId, overlayTypeId);
            return UNKNOWN_ERROR;
        }

        uint16_t entryCount = dtohs(header[2]);
        if (size < sizeof(uint32_t) * (entryCount + 2)) {
            ALOGE("idmap: too small (%u bytes) for the number of entries (%u)",
                    (uint32_t) size, (uint32_t) entryCount);
            return UNKNOWN_ERROR;
        }
        mData = header;
        return NO_ERROR;
    }

    /**
    * entryId 输入 targetId
    * outEntryId 输出 overlayId
    */
    status_t lookup(uint16_t entryId, uint16_t* outEntryId) const {
        uint16_t entryCount = dtohs(mData[2]);
        uint16_t offset = dtohs(mData[3]);

        if (entryId < offset) {
            // The entry is not present in this idmap
            return BAD_INDEX;
        }
        //idmap里的映射关系是从target包的第offset项开始的
        entryId -= offset;

        if (entryId >= entryCount) {
            // The entry is not present in this idmap
            return BAD_INDEX;
        }

        //跳过type header
        const uint32_t* entries = reinterpret_cast<const uint32_t*>(mData) + 2;
        uint32_t mappedEntry = dtohl(entries[entryId]);
        //0xffffffff表示不需要映射
        if (mappedEntry == 0xffffffff) {
            // This entry is not present in this idmap
            return BAD_INDEX;
        }
        *outEntryId = static_cast<uint16_t>(mappedEntry);
        return NO_ERROR;
    }

private:
    const uint16_t* mData;
}

        IdmapEntries表示某一种类型的所有entry的映射关系。还记得我们在
Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)说的的那个例子吗?假设我们的target 包里有10个drawable资源,它们的名称为drawable0~drawable9,id为0x7f020000~0x7f020009;overlay包里有3个资源它们的名称分别是drawable4、drawable5、drawable8,id分别是0x7f030000、0x7f030001、0x7f030002。这样就表示overlay包要覆盖target包里的drawable4、drawable5、drawable8三个资源。那么它生成的idmap的主要内容应该如下:
        target type 0x00000002
        overlay type 0x00000003
        entry offset 0x00000004
        entry count 0x00000006
        entry 0x00000000 drawable/drawable4
        entry 0x00000001 drawable/drawable5
        entry 0xffffffff drawable/drawable6
        entry 0xffffffff drawable/drawable7
        entry 0x00000002 drawable/drawable8
        entry 0xffffffff drawable/drawable9
        结合idmap的生成过程,target包中第一个要映射的资源是drawable4,其entry ID为0x00000004,所以entry offset =0x00000004

         因为drawable4在overlay包中的ID是0x00000000,所以记作

         entry 0x00000000 drawable/drawable4
         因为drawable5在overlay包中的ID是0x00000001

         entry 0x00000001 drawable/drawable5
         因为drawable6、drawable7不需要映射,所以记作

         entry 0xffffffff drawable/drawable6
         entry 0xffffffff drawable/drawable7

         因为drawable8在overlay包中的ID是0x00000002,所以记作

         entry 0x00000002 drawable/drawable8

         因为drawable9不需要映射,所以记作

         entry 0xffffffff drawable/drawable9

         我们可以试一下lookup函数,如果我们传入entryId=0x00000008,offset=0x00000004,entryId-offset=0x00000004,映射表的第4项(从第0项开始)刚好是0x00000002,也就是overlay package中drawable8的entry ID。

         另外,我们知道android系统的资源我们也是可以通过RRO来改变的,前文我们说到系统资源的RRO是在native层做的。在AssetManager添加系统资源包的时候,会调用到addOverlayPath,然后调到appendPathToResTable方法:

//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
    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");
    //overlaysListPath="/data/resource-cache/overlays.list";
    addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
}

void AssetManager::addSystemOverlays(const char* pathOverlaysList,
        const String8& targetPackagePath, ResTable* sharedRes, size_t offset) const
{
    //打开/data/resource-cache/overlays.list
    FILE* fin = fopen(pathOverlaysList, "r");
    if (fin == NULL) {
        return;
    }

    char buf[1024];
    while (fgets(buf, sizeof(buf), fin)) {
        //每一行表示一个overlay package,并且格式是
        // 空格 
        char* space = strchr(buf, ' ');
        char* newline = strchr(buf, '\n');
        asset_path oap;

        if (space == NULL || newline == NULL || newline < space) {
            continue;
        }
        //解析出overlay package的路径和idmap文件的路径
        oap.path = String8(buf, space - buf);
        oap.type = kFileTypeRegular;
        oap.idmap = String8(space + 1, newline - space - 1);

        //打开idmap文件
        Asset* oass = const_cast<AssetManager*>(this)->
            openNonAssetInPathLocked("resources.arsc",
                    Asset::ACCESS_BUFFER,
                    oap);

        if (oass != NULL) {
            Asset* oidmap = openIdmapLocked(oap);
            offset++;
            //ResTable::add方法,进入了熟悉的流程,我们前面已经分析过这个方法了
            sharedRes->add(oass, oidmap, offset + 1, false);
            const_cast<AssetManager*>(this)->mAssetPaths.add(oap);
            const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap);
        }
    }
    fclose(fin);
}

        我们看到系统资源包framework-res.apk的RRO很简单,我们只需要准备好overlay package, 然后通过idmap这个bin文件生成一下idmap文件,然后在/data/resource-cache/overlays.list文件里写一下overlay package的路径和生成的idmap文件的路径即可,系统就会自己去加载了。
        到这里overlay包和idamp文件的加载解析已经完成,也就是说AssetManager中已经有了overlay package相关的全部信息和资源,剩下的就是我们去引用和生效了,下期讲。

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