Android资源管理中的Runtime Resources Overlay-------之AssetManager的处理(三)

        上一篇我们讲过了system_server、installd以及idmap,但是还是没有看到idmap文件到底使如何生成的。今天我们将会介绍AssetManager的处理,idmap文件的内容最终就是在这里产生的,不过它的主要逻辑在ResourceTypes.cpp里面。作为铺垫,我们先简单说下AssetManager中重要的数据结构:

class AssetManager : public AAssetManager {
    //......省略无关代码
    //各个资源包的路径
    Vector<asset_path> mAssetPaths;
    //存贮管理resources.arsc里的信息
    mutable ResTable* mResources;
    //设备的配置信息
    ResTable_config* mConfig;
    //......省略无关代码
}
  • AssetManager   我们资源管理的接口类,上层要访问资源、获取资源信息都要通过它提供的接口来实现;当然,资源信息的加载存储,也是在它里面。
class ResTable
{
    //......省略无关代码
    mutable Mutex               mLock;
    status_t                    mError;
    ResTable_config             mParams;
    // Array of all resource tables.
    Vector<Header*>             mHeaders;//所有resources.arsc的Header
    // Array of packages in all resource tables.
    Vector<PackageGroup*>       mPackageGroups;//所有的PackageGroup
    // Mapping from resource package IDs to indices into the internal
    // package array.
    //下标PackageGroup ID,value-1即为该PackageGroup在mPackageGroups中的索引
    uint8_t                     mPackageMap[256];
    //分配给资源共享库的package Id,从2开始,累加
    uint8_t                     mNextPackageId;
}
  • ResTable  主要用来存储资源AssetManager中加载的所有资源相关的信息,其实就是加载的resources.arsc。AssetManager对象中有一个mutable ResTable* mResources;对象,我们通过addAssetPath添加的资源的resources.arsc里面的资源包都会存储到mResources中。关于resources.arsc的数据格式和结构,我们后面会稍作说明。
struct ResTable::PackageGroup
{
    //所属于的ResTable
    const ResTable* const           owner;
    //PackageGroup的name,为该PackageGroup中第一个Package的name
    String16 const                  name;
    //PackageGroup的id,为该PackageGroup中第一个Package的id
    uint32_t const                  id;

    //PackageGroup里所有的Package
    Vector<Package*>                packages;
    //PackageGroup里所有的类型
    ByteBucketArray<TypeList>       types;

    uint8_t                         largestTypeId;

    // Computed attribute bags, first indexed by the type and second
    // by the entry in that type.
    ByteBucketArray<bag_set**>*     bags;
    //详见:https://blog.csdn.net/dayong198866/article/details/95635910
    //不过跟本文关系不大,可无视之
    DynamicRefTable                 dynamicRefTable;
}
  • PackageGroup  表示一组相关的资源包。什么叫相关呢,有两种情况:一、两个包的id相同(这种情况在同一个AssetManager中出现,虽然不会导致系统崩溃,但应尽量避免),二、一个包是另外一个包的overlay package。这两种情况下,包会被放到同一个PackageGroup里。
struct ResTable::Package
{
    //所属于的ResTable
    const ResTable* const           owner;
    //所属于的resources.arsc, 没错一个Header就对应一个resources.arsc中的ResTable_header
    const Header* const             header;
    //resources.arsc中的package数据段
    const ResTable_package* const   package;
    //type string pool drawable、string、layout eg.
    ResStringPool                   typeStrings;
    //key string pool the name of an entry
    ResStringPool                   keyStrings;
    size_t                          typeIdOffset;
}
  • Package  我们可以简单认为一个Package对应一个资源包或者resources.arsc。
typedef Vector<Type*> TypeList;
  • TypeList   TypeList表示同一个PackageGroup所有包中,某一类型的资源的总和。比如,一个PackageGroup里有一个target包和一个overlay包,那么一个TypeList中会有两个元素,分别表示不同包中的同一类型的资源。和PackageGroup的情况类似,正常情况下,如果这个TypeList所属的Package没有overlay包,那么这个TypeList中的元素的个数应该只有1个。
struct ResTable::Type
{
    //所属于的resources.arsc, 没错一个Header就对应一个resources.arsc中的ResTable_header
    const Header* const             header;
    //所属于的Package
    const Package* const            package;
    //该package中该Type的资源项的个数
    const size_t                    entryCount;
    //该Type的一些描述信息,和resources.arsc中的RES_TABLE_TYPE_SPEC_TYPE数据块对应
    const ResTable_typeSpec*        typeSpec;
    const uint32_t*                 typeSpecFlags;
    //存放该种type的各个资源项的映射信息
    IdmapEntries                    idmapEntries;
    //存放同一种类型的资源项
    Vector<const ResTable_type*>    configs;
}
  • Type   表示一个包中,某一类型的所有资源项,包括不同的配置下的所有资源项。它的configs变量是个Vector,这个Vector的每一项对应一种配置。比如同是类型为drawable的资源,横屏下的drawable-land和竖屏下的drawable-port就分别对应configs中的一个元素。
struct ResTable::Entry {
    //配置信息
    ResTable_config config;
    //对应的资源项
    const ResTable_entry* entry;
    //该资源项所属的type
    const ResTable_type* type;
    uint32_t specFlags;
    //该资源项所属的Package
    const Package* package;
    //该资源项所属的type 在type string pool中的索引
    StringPoolRef typeStr;
    //该资源项的名称在 key string pool中的索引
    StringPoolRef keyStr;
};
  • Entry   一个entry我们就可以理解为一个资源项了,它是一个key-value对(bag资源除外,先不谈它)。比如在竖屏的时候,某一个资源项的key是icon,value是drawable-port/icon.png。

        以上这些数据结构在ResTable中主要用于资源的管理;而下面的数据结构则用于描述resources.arsc,它们都会对应于resources.arsc中的一个数据块:


class ResStringPool
{
    status_t                    mError;
    //数据块
    void*                       mOwnedData;
    //描述这个string pool的相关信息
    const ResStringPool_header* mHeader;
    //数据块占用的空间
    size_t                      mSize;
    mutable Mutex               mDecodeLock;
    //表示string pool中的每一个字符串相对于 mStrings的偏移量
    const uint32_t*             mEntries;
    //表示string pool中的每一个style相对于 mStyles的偏移量
    const uint32_t*             mEntryStyles;
    //string 数据块
    const void*                 mStrings;
    char16_t mutable**          mCache;
    // number of uint16_t
    uint32_t                    mStringPoolSize; 
    //style块   
    const uint32_t*             mStyles;
    // number of uint32_t
    uint32_t                    mStylePoolSize;    
}

  • ResStringPool   用来存储字符串的字符串池,一个resources.arsc中一般会有三个字符串池:global string pool、type string pool和key string pool。
struct ResTable_package
{
    //头信息:类型、头大小、整个段大小
    struct ResChunk_header header;

    /**
    *包id,android系统包(framework-res.apk)id是0x01
    *应用包id是0x7f
    *资源共享库包id是0x00
    *资源共享库是作者自己的叫法,详见:
    *https://blog.csdn.net/dayong198866/article/details/95226237
    */
    uint32_t id;

    // Actual name of this package, \0-terminated.
    uint16_t name[128];

    // type string pool
    uint32_t typeStrings;

    // Last index into typeStrings that is for public use by others.
    uint32_t lastPublicType;

    // key string pool
    uint32_t keyStrings;

    // Last index into keyStrings that is for public use by others.
    uint32_t lastPublicKey;

    uint32_t typeIdOffset;
}
  • ResTable_package   ResTable_package对应resources.arsc中的类型为RES_TABLE_PACKAGE_TYPE的数据块,注意它和ResTable::Package的区分,ResTable::Package是对ResTable_package的一个封装。
struct ResTable_config
{
    //数据块的大小
    uint32_t size;
    //mcc mnc
    union {
        struct {
            // Mobile country code (from SIM).  0 means "any".
            uint16_t mcc;
            // Mobile network code (from SIM).  0 means "any".
            uint16_t mnc;
        };
        uint32_t imsi;
    };
    //......数据太多,不再一一列举
}
  • ResTable_config   内部数据比较多,我们在这里只需要知道它用来描述设备的当前配置即可。
struct ResTable_typeSpec
{
    //头信息:类型、头大小、整个段大小
    struct ResChunk_header header;
    //type 的 id,从1开始
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // Number of uint32_t entry configuration masks that follow.
    uint32_t entryCount;

    enum {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000
    };
};
  • ResTable_typeSpec   用来描述某一类型的资源本身,比如有多少资源项等信息。
struct ResTable_type
{
    //头信息:类型、头大小、整个段大小
    struct ResChunk_header header;
    enum {
        NO_ENTRY = 0xFFFFFFFF
    };
    //type 的 id,从1开始,和它对应的ResTable_typeSpec中的id值相等
    uint8_t id;
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    //资源项的个数
    uint32_t entryCount;
    
    //资源项相对与该type的ResChunk_header的起始地址的偏移量
    uint32_t entriesStart;
    
    // Configuration this collection of entries is designed for.
    ResTable_config config;
};
  • ResTable_type   用来描述resources.arsc中某一特定配置下的一种类型的所有资源。我们要注意的是一个ResTable_typeSpec可能对应多个ResTable_type,因为它们的ResTable_config可能不一样。请注意它和ResTable::Type的区分:一个ResTable::Type中有多个ResTable_type,ResTable::Type包含了某一种类型的所有配置的所有资源。
struct ResTable_entry
{
    // Number of bytes in this structure.
    uint16_t size;
    enum {
        // If set, this is a complex entry, holding a set of name/value
        // mappings.  It is followed by an array of ResTable_map structures.
        FLAG_COMPLEX = 0x0001,
        // If set, this resource has been declared public, so libraries
        // are allowed to reference it.
        FLAG_PUBLIC = 0x0002
    };
    uint16_t flags;
    
    // Reference into ResTable_package::keyStrings identifying this entry.
    //资源的名称在key string pool中的索引
    struct ResStringPool_ref key;
}
  • ResTable_entry   一个资源项,确切地说是一个资源项的名称,在resources.arsc中这个结构体后面会跟一个ResTable_value作为资源项的值(先不考虑bag资源)。ResTable::Entry是对ResTable_entry的一个封装。
struct Res_value
{
    // Number of bytes in this structure.
    uint16_t size;
    // Always set to 0.
    uint8_t res0;
    uint8_t dataType;
    uint32_t data;
}
  • Res_value   在resources.arsc中这个结构体会跟在ResTable_entry后面,表示一个资源项的值。

        AssetManager相关的数据结构总算介绍完了!因为资源这一块儿相对比较复杂,所以涉及的数据结构比较多,但这是理解Android资源管理的基础。资源管理最主要的工作就是resources.arsc的解析和管理,所以我们还要对resources.arsc有个直观的认识,这个网上介绍的文章很多,大家可以去看看。


        前文我们讲到idmap进程最终还是把idmap文件的生成工作交给了AssetManager来完成,那么我们继续看看AssetManager的实现:

bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath,
        uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize)
{
    AutoMutex _l(mLock);
    const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
    ResTable tables[2];

    for (int i = 0; i < 2; ++i) {
        asset_path ap;
        ap.type = kFileTypeRegular;
        ap.path = paths[i];
        //代开对应的resources.arsc
        Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
        if (ass == NULL) {
            ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
            return false;
        }
        //构造ResTable
        tables[i].add(ass);
    }

    return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
            targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
}

        我们看到AssetManager会根据target包和overlay包的路径,分别去打开并加载他们的resources.arsc文件,然后分别创建Restable的实例,剩下的工作就看Restable了:

//用来表示某一类型的资源的idmap信息,这个结构体很简单,没什么好说的
struct IdmapTypeMap {
    ssize_t overlayTypeId;
    size_t entryOffset;
    Vector<uint32_t> entryMap;
};

status_t ResTable::createIdmap(const ResTable& overlay,
        uint32_t targetCrc, uint32_t overlayCrc,
        const char* targetPath, const char* overlayPath,
        void** outData, size_t* outSize) const
{
     //......略去次要代码
     //用来保存所有的idmap数据,key:target typeindex
     KeyedVector<uint8_t, IdmapTypeMap> map;
     //在target ResTable中我们只加载了target package 一个资源包,此时里面应该只有一个元素
     const PackageGroup* pg = mPackageGroups[0];
     // starting size is header
    *outSize = ResTable::IDMAP_HEADER_SIZE_BYTES;
    // target package id and number of types in map
    *outSize += 2 * sizeof(uint16_t);
    // overlay packages are assumed to contain only one package group
    const ResTable_package* overlayPackageStruct = overlay.mPackageGroups[0]->packages[0]->package;
    char16_t tmpName[sizeof(overlayPackageStruct->name)/sizeof(overlayPackageStruct->name[0])];
    strcpy16_dtoh(tmpName, overlayPackageStruct->name, sizeof(overlayPackageStruct->
           name)/sizeof(overlayPackageStruct->name[0]));
    //overlay PackageName
    const String16 overlayPackage(tmpName);
    /**
    *按照typeIndex对所有类型的资源做处理
    *另外,我们看到这个循环是根据pg变量(也就是target package)来做的,
    *所以,如果我们的overlay包里新添加了某种类型的资源,将会被无视。
    */
    for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) {
        const TypeList& typeList = pg->types[typeIndex];
        if (typeList.isEmpty()) {
            continue;
        }
        //此时target ResTable中只有一个target资源包,所以每一个typeList中都最多只有一个元素
        const Type* typeConfigs = typeList[0];
        //用来存储一种type的所有entry的映射关系
        IdmapTypeMap typeMap;
        //overlay package中的type id
        typeMap.overlayTypeId = -1;
        /**
        *从target package中id 为typeIndex的类型的资源的entryOffset项开始做idmap
        *为什么要有这个偏移量呢?为了节省资源:
        *假设我们要覆盖target包中drawable中的第7,8,12项资源
        *那么前面的0~6项我们没必要处理,从第7项开始就可以了,所以才有这个entryOffset变量
        */
        typeMap.entryOffset = 0;
        //记录每一个资源项的映射关系。
        for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
            resource_name resName;
            //做检查,target包中都没有的资源还处理个啥,跳过
            if (!this->getResourceName(resID, false, &resName)) {
                //但是要记录我们跳过了一个资源entryOffset++
                if (typeMap.entryMap.isEmpty()) {
                    typeMap.entryOffset++;
                }
                continue;
            }
            
            const String16 overlayType(resName.type, resName.typeLen);
            const String16 overlayName(resName.name, resName.nameLen);
            //根据资源名称获取资源id,string pool遍历操作,效率极低,所以只在生成索引的时候
            //做一次,后面的就直接根据id(也就是索引来了)。
            uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
                                                              overlayName.size(),
                                                              overlayType.string(),
                                                              overlayType.size(),
                                                              overlayPackage.string(),
                                                              overlayPackage.size());
            //target包中有,但overlay 包中没有的资源,不做映射,但要记录一下我们跳过去了
            if (overlayResID == 0) {
                if (typeMap.entryMap.isEmpty()) {
                    typeMap.entryOffset++;
                }
                continue;
            }
            //写入target type id 和overlay type id的映射关系,
            //target type id就是这个typeMap在变量map中的key
            if (typeMap.overlayTypeId == -1) {
                typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
            }
            if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) {
                /**
                *对于target包中entryOffset后面的资源,如果overlay package中没有
                *则对应entry映射关系写成:
                *target: entryOffset+index-----> overlay: 0xffffffff
                *0xffffffff(-1)表示不需要做映射,也即overlay package里没有的资源
                */
                size_t index = typeMap.entryMap.size();
                size_t numItems = entryIndex - (typeMap.entryOffset + index);
                //overlay package里没有的资源补成0xffffffff
                if (typeMap.entryMap.insertAt(0xffffffff, index, numItems) < 0) {
                    return NO_MEMORY;
                }
            }
            //添加entry映射关系:
            //entryOffset+index-----> overlay: Res_GETENTRY(overlayResID)
            typeMap.entryMap.add(Res_GETENTRY(overlayResID));
        }
        //处理完一个类型的资源的映射,如果有映射数据
        if (!typeMap.entryMap.isEmpty()) {
            //添加到最终的记录里面去
            if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) {
                return NO_MEMORY;
            }
            //输出数据的szie加上一个IdmapTypeMap的实际大小
            *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t));
        }
    }
    
    /**
    *第一阶段工作完成,记录target package和overlay package中所有type和所有entry的映射关系
    *保存在map变量中,并且计算出了idmap数据的大小outSize。
    *下面开始输出idmap的具体数据到outData
    */

    //分配内存
    if ((*outData = malloc(*outSize)) == NULL) {
        return NO_MEMORY;
    }
    
    //写入相关头信息
    uint32_t* data = (uint32_t*)*outData;
    *data++ = htodl(IDMAP_MAGIC);
    *data++ = htodl(IDMAP_CURRENT_VERSION);
    *data++ = htodl(targetCrc);
    *data++ = htodl(overlayCrc);

    //写入targetPath、overlayPath
    const char* paths[] = { targetPath, overlayPath };
    for (int j = 0; j < 2; ++j) {
        char* p = (char*)data;
        const char* path = paths[j];
        const size_t I = strlen(path);
        if (I > 255) {
            ALOGV("path exceeds expected 255 characters: %s\n", path);
            return UNKNOWN_ERROR;
        }
        for (size_t i = 0; i < 256; ++i) {
            *p++ = i < I ? path[i] : '\0';
        }
        data += 256 / sizeof(uint32_t);
    }
    //写入target包id和types count
    const size_t mapSize = map.size();
    uint16_t* typeData = reinterpret_cast<uint16_t*>(data);
    *typeData++ = htods(pg->id);
    *typeData++ = htods(mapSize);
    //写入每一个entry的映射信息
    for (size_t i = 0; i < mapSize; ++i) {
        uint8_t targetTypeId = map.keyAt(i);
        const IdmapTypeMap& typeMap = map[i];
        //写入target type
        *typeData++ = htods(targetTypeId + 1);
        //写入overlay type
        *typeData++ = htods(typeMap.overlayTypeId);
        //写入entry count
        *typeData++ = htods(typeMap.entryMap.size());
        //写入entry offset
        *typeData++ = htods(typeMap.entryOffset);

        const size_t entryCount = typeMap.entryMap.size();
        uint32_t* entries = reinterpret_cast<uint32_t*>(typeData);
        for (size_t j = 0; j < entryCount; j++) {
            //写入每一个entry的映射信息
            //key:typeMap.entryOffset+j
            //value:typeMap.entryMap[j]
            entries[j] = htodl(typeMap.entryMap[j]);
        }
        //指针移动 entryCount * 2 * 2 = entryCount * sizeof(uint32_t)个字节
        typeData += entryCount * 2;
    }
    return NO_ERROR;
}

        至此,idmap总算生成了,这个方法本身并不长,只是全是我加的注释,哈哈。关于idmap,我们要注意一下几点:

  • idmap本质上是从target包的某些资源到overlay包的资源的id的映射。我们知道资源的id是0xpptteeee形式的,所以这个映射本身也是分为三部分:target packageId到overlay packageId的映射、target typeId到overlay typeId的映射、target entryId到overlay entryId的映射。
  • 其中target entryId到overlay entryId的映射比较复杂,target entryId = entryMapIndex + entry offset。entryMapIndex指该项在本type中出现的index。对于entry offset后面的资源,如果 overlay包中没有则Map的值为0xffffffff。Android的这种处理方式也是用空间换时间,因为访问时,它可以基于索引直接计算出对应的overlay package中entryId,速度肯定快;副作用是对于不需要映射的资源也要做记录0xffffffff,占用空间。其实我们完全可以做一个Map,它的key为需要映射的target Package的resId,value为对应的overlay package中的resId,不需要映射的我们根本不用记录。这样我们要记录的数据就会少很多。但访问时,就要遍历我们的这个Map去查寻,而不是基于索引去访问,运行效率会降低。
  • 从idmap中映射关系的生成过程来看,里面的遍历都是基于target包的,这样overlay 包只能覆盖,而不能新增资源。新增的将被无视掉。

        现在RRO中最关键的组件idmap文件已经生成了,剩下的就是如何加载和生效了,下期讲。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值