Android资源篇

作者: 空同定翁 | 来源:发表于2017-06-16 16:04 被阅读170次

    前言


    去年整理过android系统资源加载流程,时间久了发现有些地方仍旧不清楚,这里重新整理了一次。这次整理的流程主要分析了资源查找流程、AssetManager添加apk时都做了什么等。

    备注:参考源码为android6.0,之前是参考android4.4源码学习;对比发现在资源处理这块,6.0版本的源码跟4.4已经很大不同。

    基础知识


    资源ID

    Package ID + Type ID + Entry ID

    关键词

    AssetManager

    AssetManager一般用来加载资源文件,AssetManager有个ResTable类型的成员变量,该ResTable变量保存了AssetManager加载的所有资源文件中的资源。

    ResTable

    ResTable即资源表,与某个AssetManager关联。内部存在一个PackageGroup列表,该列表用来保存所有加载的资源文件资源。

    PackageGroup

    1. 一个PackageGroup存放了Package ID相同的Package信息
    2. 一个PackageGroup可能对应了多个Package ID相同的Package

    Package

    1. 一个Package对应一个APK包
    2. 一个Package包含多种Type类型的资源

    Type

    1. 资源类型,如:layout、drawable、string等各对应一个Type。
    2. 一个Type可能对应多个Config

    Config

    1. Config表示同名资源
    2. 一个Config可能包含多个Entry,即一个资源的不同配置。

    Entry

    具体配置下的资源

    重叠包(overlay)

    假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的资源。

    重叠包设置可以参考:编译时替换资源 - Android重叠包与资源合并一见

    举个栗子

    1.png

    上图中存在三种类型资源:layout、drawable和string,即对应存在3中Type;有:

    1. Type为drawable的资源由1个Config,即icon.png。该Config有3个Entry:res/drawable-ldip/icon.png、res/drawable-mdip/icon.png、res/drawable-hdip/icon.png。
    2. Type为layout的资源由2个Config,即main.xml和sub.xml。
      • main.xml的Config对应的Entry为:res/layout/main.xml。
      • sub.xml的Config对应的Entry为:res/layout/subxml。

    ResTable分析


    从代码中看下资源存储的具体结构,参考源码为Android6.0,4.4源码与现有代码已很大不同。

    PackageGroup

    该结构定义在ResourceTYpes.cpp中。

    /**
    * 一个PackageGroup保存了所有资源ID相同的Package信息
    * PackageGroup中第一个Package是root package
    */
    struct ResTable::PackageGroup
    {
      PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id)
          : owner(_owner)
          , name(_name)
          , id(_id)
          , largestTypeId(0)
          , bags(NULL)
          , dynamicRefTable(static_cast<uint8_t>(_id)){ }
    
      //析构函数,清理,代码忽略  
      ~PackageGroup() {
          clearBagCache();
          ......
      }
    
      //清理内存
      void clearBagCache() {
          ......
      }
      ......
    
      const ResTable* const           owner;    //指向其所在的ResTable
      String16 const                  name;     //root package的包名
      uint32_t const                  id;       //
    
      //当前PackageGroup加载的Packages,这些Packages的Package ID相同
      Vector<Package*>                packages;
    
      //该PackageGroup下所有Package的类型,即一个TypeList对应了所有Package的相同类型资源?
      //如,所有Package的drawable类型资源,保存在一个TypeList中
      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;
    
      // The table mapping dynamic references to resolved references for
      // this package group.
      // TODO: We may be able to support dynamic references in overlays
      // by having these tables in a per-package scope rather than
      // per-package-group.
      DynamicRefTable                 dynamicRefTable;
    }
    

    Package

    该结构定义在ResourceTYpes.cpp中。

    //一个apk对应一个Package?
    struct ResTable::Package
    {
      Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
          : owner(_owner), header(_header), package(_package), typeIdOffset(0) {
          if (dtohs(package->header.headerSize) == sizeof(package)) {
              // The package structure is the same size as the definition.
              // This means it contains the typeIdOffset field.
              typeIdOffset = package->typeIdOffset;
          }
      }
    
      const ResTable* const           owner;
      const Header* const             header;
      const ResTable_package* const   package;
    
      //Type池,即保存了该Package下所有的资源类型
      ResStringPool                   typeStrings;  
      
      //key池,即保存了该Package下所有的资源名称
      ResStringPool                   keyStrings;   
      
      //指示当前Package在其PackageGroup中的偏移量?
      size_t                          typeIdOffset; 
    };
    

    Type

    该结构定义在ResourceTYpes.cpp中。

    //资源类型
    struct ResTable::Type
    {
      Type(const Header* _header, const Package* _package, size_t count)
          : header(_header), package(_package), entryCount(count),
            typeSpec(NULL), typeSpecFlags(NULL) { }
      
      const Header* const             header;
      const Package* const            package;
      const size_t                    entryCount;
      const ResTable_typeSpec*        typeSpec;
      const uint32_t*                 typeSpecFlags;
      IdmapEntries                    idmapEntries;
      
      //一个Type由多个config组成,config用ResTable_type表示
      Vector<const ResTable_type*>    configs;  
    };
    

    Entry

    该结构定义在ResourceTYpes.cpp中。

    struct ResTable::Entry {
      ResTable_config config;
      const ResTable_entry* entry;
      const ResTable_type* type;    //所在config
      uint32_t specFlags;
      const Package* package;       //指向所在Pakcage
    
      StringPoolRef typeStr;     //指向Package的Type池
      StringPoolRef keyStr;     //指向Package的key池
    };
    

    Header

    Header保存了ResTable的部分信息,该结构定义在ResourceTYpes.cpp中。

    struct ResTable::Header
    {
        Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
            resourceIDMap(NULL), resourceIDMapSize(0) { }
        ~Header()
        {
            free(resourceIDMap);
        }
    
        const ResTable* const           owner;
        void*                           ownedData;
        const ResTable_header*          header;
        size_t                          size;
        const uint8_t*                  dataEnd;
        size_t                          index;
        int32_t                         cookie; //该值为索引值
    
        ResStringPool                   values;
        uint32_t*                       resourceIDMap;
        size_t                          resourceIDMapSize;
    };
    

    ResTable

    看完了上面一些结构的定义,现在分析下ResTable。ResTable是个用来进行资源操作的类,该类定义在ResourceTyps.h,实现在ResourceTYpes.cpp中。

    class ResTable
    {
    public:
        ResTable();
        ResTable(const void* data, size_t size, const int32_t cookie,
                 bool copyData=false);
        ~ResTable();
    
        //add方法,用来添加资源,内部调用addInternal方法
        status_t add(const void* data, size_t size, const int32_t cookie=-1, bool copyData=false);
        status_t add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,const       
                     int32_t cookie=-1, bool copyData=false);
        status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false);
        status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false);
        
        //添加其它ResTable
        status_t add(ResTable* src);
        
        //添加空的ResTable,此时只添加Header
        status_t addEmpty(const int32_t cookie);
    
        status_t getError() const;
        void uninit();
    
        //资源名称结构
        struct resource_name
        {
            const char16_t* package;
            size_t packageLen;
            const char16_t* type;
            const char* type8;
            size_t typeLen;
            const char16_t* name;
            const char* name8;
            size_t nameLen;
        };
    
        //根据资源ID获取资源名称
        bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
    
        bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const;
    
        //根据资源ID获取资源的值,返回值为资源在 索引值
        ssize_t getResource(uint32_t resID, Res_value* outValue, bool mayBeBag = false,
                        uint16_t density = 0,
                        uint32_t* outSpecFlags = NULL,
                        ResTable_config* outConfig = NULL) const;
        ......
        
        //根据资源的全限定名,获取其资源ID
        uint32_t identifierForName(const char16_t* name, size_t nameLen,
                                   const char16_t* type = 0, size_t typeLen = 0,
                                   const char16_t* defPackage = 0,
                                   size_t defPackageLen = 0,
                                   uint32_t* outTypeSpecFlags = NULL) const;
        ......
    
    private:
        struct Header;
        struct Type;
        struct Entry;
        struct Package;
        struct PackageGroup;
        struct bag_set;
        typedef Vector<Type*> TypeList;
    
        //添加资源
        status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,const int32_t cookie, bool copyData);
        
        //根据资源Id获取资源在mPackageGroups的索引
        ssize_t getResourcePackageIndex(uint32_t resID) const;
        
        //获取具体资源信息
        status_t getEntry(
            const PackageGroup* packageGroup, int typeIndex, int entryIndex,
            const ResTable_config* config,
            Entry* outEntry) const;
        
        //获取具体资源的资源ID
        uint32_t findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,size_t nameLen, uint32_t* outTypeSpecFlags) const;
        ......
          
        ResTable_config             mParams;
    
        //当前ResTable添加的其它ResTable的Header信息
        Vector<Header*>             mHeaders;
      
        //一个PackageID对应一个PackageGroup
        Vector<PackageGroup*>       mPackageGroups;     
       
        //保存了PackageID到mPckageGroups索引的映射关系
        uint8_t                     mPackageMap[256];
    
        uint8_t                     mNextPackageId;
    };
    

    ReTable方法还是比较多的,这里分析其几个比较重要的方法。

    getResourceName方法

    //根据资源ID查询资源名称
    bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
    {
        if (mError != NO_ERROR) {
            return false;
        }
    
        //获取资源在mPackageGroups的索引
        const ssize_t p = getResourcePackageIndex(resID);
        //Type ID
        const int t = Res_GETTYPE(resID);
        //Entry ID
        const int e = Res_GETENTRY(resID);
    
        if (p < 0) {
            if (Res_GETPACKAGE(resID)+1 == 0) { //Pckage ID为-1,不存在
                ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
            } else {
                ALOGW("No known package when getting name for resource number 0x%08x", resID);
            }
            return false;
        }
        if (t < 0) {
            ALOGW("No type identifier when getting name for resource number 0x%08x", resID);
            return false;
        }
    
        //资源所在PackageGroup
        const PackageGroup* const grp = mPackageGroups[p];
        if (grp == NULL) {
            ALOGW("Bad identifier when getting name for resource number 0x%08x", resID);
            return false;
        }
    
        Entry entry;
        
        //在目的PackageGroup中查找对应资源
        status_t err = getEntry(grp, t, e, NULL, &entry);
        
        //未找到资源,返回false
        if (err != NO_ERROR) {
            return false;
        }
        
        /**
         * 将查找到的资源名称保存到outName返回
        **/
      
        //资源Package名称,即其所在PackageGroup的名称
        outName->package = grp->name.string();
        outName->packageLen = grp->name.size();
      
        //资源Type、Entry名称
        if (allowUtf8) {
            //Type名称在其Package的Type池中查找
            outName->type8 = entry.typeStr.string8(&outName->typeLen);
            //Entry名称在其Package的Key池中查找
            outName->name8 = entry.keyStr.string8(&outName->nameLen);
        } else {
            outName->type8 = NULL;
            outName->name8 = NULL;
        }
        if (outName->type8 == NULL) {
            outName->type = entry.typeStr.string16(&outName->typeLen);
            if (outName->type == NULL) {
                return false;
            }
        }
        if (outName->name8 == NULL) {
            outName->name = entry.keyStr.string16(&outName->nameLen);
            if (outName->name == NULL) {
                return false;
            }
        }
    
        return true;
    }
    

    分析:

    该方法先根据资源的PackageID找到资源所在的PackageGroup,再在该PackageGroup中查找。在PackageGroup的查找调用getEntry方法实现。

    getEntry方法

    /**
     * 在PackageGroup查找具体的资源
     * pakcageGroup : 目的PackageGroup
     * typeIndex:资源Type ID
     * entryIndex:资源Entry ID
     * config: 资源配置信息
     * outEntry:返回值
     */
    status_t ResTable::getEntry(
            const PackageGroup* packageGroup, int typeIndex, int entryIndex,
            const ResTable_config* config,
            Entry* outEntry) const
    {
        //根据Type ID查找到目的PacakgeGroup下的目标类型
        const TypeList& typeList = packageGroup->types[typeIndex];
        if (typeList.isEmpty()) {
            ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
            return BAD_TYPE;
        }
    
        const ResTable_type* bestType = NULL;
        uint32_t bestOffset = ResTable_type::NO_ENTRY;
        const Package* bestPackage = NULL;
        uint32_t specFlags = 0;
        uint8_t actualTypeIndex = typeIndex;
        ResTable_config bestConfig;
        memset(&bestConfig, 0, sizeof(bestConfig));
    
        //注:一个TypeList存储了所有Package下相同类型的资源
        const size_t typeCount = typeList.size();
        
        //遍历每个Package的目标Type
        for (size_t i = 0; i < typeCount; i++) {
            const Type* const typeSpec = typeList[i];
    
            int realEntryIndex = entryIndex;
            int realTypeIndex = typeIndex;
            bool currentTypeIsOverlay = false;
    
            //是否有idmap资源
            if (typeSpec->idmapEntries.hasEntries()) {
                uint16_t overlayEntryIndex;
                if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
                    // No such mapping exists
                    continue;
                }
                realEntryIndex = overlayEntryIndex;
                realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1;
                currentTypeIsOverlay = true;
            }
    
            if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
                ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
                        Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
                        entryIndex, static_cast<int>(typeSpec->entryCount));
                // We should normally abort here, but some legacy apps declare
                // resources in the 'android' package (old bug in AAPT).
                continue;
            }
    
            // Aggregate all the flags for each package that defines this entry.
            if (typeSpec->typeSpecFlags != NULL) {
                specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
            } else {
                specFlags = -1;
            }
    
            const size_t numConfigs = typeSpec->configs.size();
            
            //遍历每个Type下的configs
            for (size_t c = 0; c < numConfigs; c++) {
                const ResTable_type* const thisType = typeSpec->configs[c];
                if (thisType == NULL) {
                    continue;
                }
    
                ResTable_config thisConfig;
                thisConfig.copyFromDtoH(thisType->config);
    
                //与所查找资源配置不相同
                if (config != NULL && !thisConfig.match(*config)) {
                    continue;
                }
    
                //获取当前config下所有entry
                const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
                        reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
                
                //在entry列表中查找是否存在匹配的Entry
                uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
                if (thisOffset == ResTable_type::NO_ENTRY) {
                    // There is no entry for this index and configuration.
                    continue;
                }
    
                //比较,找到最佳配置(即配置信息更详细)
                if (bestType != NULL) {
                    // Check if this one is less specific than the last found.  If so,
                    // we will skip it.  We check starting with things we most care
                    // about to those we least care about.
                    if (!thisConfig.isBetterThan(bestConfig, config)) {
                        if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) {
                            continue;
                        }
                    }
                }
    
                //保存当前最佳配置信息
                bestType = thisType;
                bestOffset = thisOffset;
                bestConfig = thisConfig;
                bestPackage = typeSpec->package;
                actualTypeIndex = realTypeIndex;
    
                // If no config was specified, any type will do, so skip
                if (config == NULL) {
                    break;
                }
            }
        }
    
        if (bestType == NULL) {
            return BAD_INDEX;
        }
    
        bestOffset += dtohl(bestType->entriesStart);
    
        if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) {
            ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
                    bestOffset, dtohl(bestType->header.size));
            return BAD_TYPE;
        }
        if ((bestOffset & 0x3) != 0) {
            ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset);
            return BAD_TYPE;
        }
    
        //读取最佳配置对应的Entry
        const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
                reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
                
        if (dtohs(entry->size) < sizeof(*entry)) {
            ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
            return BAD_TYPE;
        }
    
        //将最佳配置的Entry保存到outEntry,并返回
        if (outEntry != NULL) {
            outEntry->entry = entry;
            outEntry->config = bestConfig;
            outEntry->type = bestType;
            outEntry->specFlags = specFlags;
            outEntry->package = bestPackage;
            outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
            outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
        }
        return NO_ERROR;
    }
    

    分析:

    该方法的整理流程还是挺清楚的,主要有:

    1. 在目标PackageGroup中,根据TypeID找到对应的资源类型集合TypeList。(注:一个TypeList保存了当前PakcageGroup某种类型下所有Package的资源)
    2. 遍历TypeList,在每个Type中查找。
    3. 遍历每个Type中的configs列表,即查找不同的名称的资源。
    4. 在每个configs对应的entry中查找是否有有匹配的资源,有则保存返回。查找过程中,如果查到多个匹配的资源,则选择配置最佳的资源返回。

    getResource方法

    
    //根据资源ID查询资源值
    ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
            uint32_t* outSpecFlags, ResTable_config* outConfig) const
    {
        if (mError != NO_ERROR) {
            return mError;
        }
    
        const ssize_t p = getResourcePackageIndex(resID);   //mPacageksGroups下的索引
        const int t = Res_GETTYPE(resID);   //Type ID
        const int e = Res_GETENTRY(resID);  //Entry ID
    
        if (p < 0) {
            if (Res_GETPACKAGE(resID)+1 == 0) {
                ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
            } else {
                ALOGW("No known package when getting value for resource number 0x%08x", resID);
            }
            return BAD_INDEX;
        }
        if (t < 0) {
            ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
            return BAD_INDEX;
        }
    
        //资源所在PackageGroup
        const PackageGroup* const grp = mPackageGroups[p];
        if (grp == NULL) {
            ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
            return BAD_INDEX;
        }
    
        // Allow overriding density
        ResTable_config desiredConfig = mParams;
        if (density > 0) {
            desiredConfig.density = density;
        }
    
        Entry entry;
        //查找目标PakcageGroup下的资源Entry
        status_t err = getEntry(grp, t, e, &desiredConfig, &entry); 
        
        if (err != NO_ERROR) {
            // Only log the failure when we're not running on the host as
            // part of a tool. The caller will do its own logging.
    #ifndef STATIC_ANDROIDFW_FOR_TOOLS
            ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n",
                    resID, t, e, err);
    #endif
            return err;
        }
      
        if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
            if (!mayBeBag) {
                ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
            }
            return BAD_VALUE;
        }
    
        //根据资源Entry得到资源值
        const Res_value* value = reinterpret_cast<const Res_value*>(
                reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
    
        //返回资源值
        outValue->size = dtohs(value->size);
        outValue->res0 = value->res0;
        outValue->dataType = value->dataType;
        outValue->data = dtohl(value->data);
    
        // The reference may be pointing to a resource in a shared library. These
        // references have build-time generated package IDs. These ids may not match
        // the actual package IDs of the corresponding packages in this ResTable.
        // We need to fix the package ID based on a mapping.
        if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
            ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data);
            return BAD_VALUE;
        }
    
        if (kDebugTableNoisy) {
            size_t len;
            printf("Found value: pkg=%zu, type=%d, str=%s, int=%d\n",
                    entry.package->header->index,
                    outValue->dataType,
                    outValue->dataType == Res_value::TYPE_STRING ?
                        String8(entry.package->header->values.stringAt(outValue->data, &len)).string() :
                        "",
                    outValue->data);
        }
    
        if (outSpecFlags != NULL) {
            *outSpecFlags = entry.specFlags;
        }
    
        if (outConfig != NULL) {
            *outConfig = entry.config;
        }
    
        return entry.package->header->index;
    }
    

    分析

    该方法与getResourceName方法类似,即先找到目标PackageGroup,再在目标PackageGroup中调用getEntry方法找到对应的Entry。然后再读取Entry中的值,保存并返回。

    identifierForName方法

    //由资源名称获取资源ID
    uint32_t ResTable::identifierForName(const char16_t* name, size_t nameLen,
                                         const char16_t* type, size_t typeLen,
                                         const char16_t* package,
                                         size_t packageLen,
                                         uint32_t* outTypeSpecFlags) const
    {
        ......
        if (mError != NO_ERROR) {
            return 0;
        }
    
        bool fakePublic = false;
    
        /**
         * 根据全限定名读取具体的Package、Type、Entry的名称和长度
         */
    
        const char16_t* packageEnd = NULL;
        const char16_t* typeEnd = NULL;
        const char16_t* const nameEnd = name+nameLen;
        const char16_t* p = name;
        while (p < nameEnd) {
            if (*p == ':') packageEnd = p;
            else if (*p == '/') typeEnd = p;
            p++;
        }
        if (*name == '@') {
            name++;
            if (*name == '*') {
                fakePublic = true;
                name++;
            }
        }
        if (name >= nameEnd) {
            return 0;
        }
    
        if (packageEnd) {
            package = name;
            packageLen = packageEnd-name;
            name = packageEnd+1;
        } else if (!package) {
            return 0;
        }
    
        if (typeEnd) {
            type = name;
            typeLen = typeEnd-name;
            name = typeEnd+1;
        } else if (!type) {
            return 0;
        }
    
        if (name >= nameEnd) {
            return 0;
        }
        nameLen = nameEnd-name;
    
        if (kDebugTableNoisy) {
            printf("Looking for identifier: type=%s, name=%s, package=%s\n",
                    String8(type, typeLen).string(),
                    String8(name, nameLen).string(),
                    String8(package, packageLen).string());
        }
    
        const String16 attr("attr");
        const String16 attrPrivate("^attr-private");
    
        //所有的PackageGroup,一个Package ID对应一个PackageGroup
        const size_t NG = mPackageGroups.size();
        
        //遍历每个PackageGroup
        for (size_t ig=0; ig<NG; ig++) {
            const PackageGroup* group = mPackageGroups[ig];
    
            //找到目标PackageGroup
            //packageGroup的名称是否与资源包名相同,packagegroup的名称是root package的名称
            if (strzcmp16(package, packageLen,
                          group->name.string(), group->name.size())) {
                if (kDebugTableNoisy) {
                    printf("Skipping package group: %s\n", String8(group->name).string());
                }
                continue;
            }
    
            //packagegroup下的所有package
            const size_t packageCount = group->packages.size();
            
            //遍历每个package
            for (size_t pi = 0; pi < packageCount; pi++) {
                const char16_t* targetType = type;
                size_t targetTypeLen = typeLen;
    
                do {
                    //在当前package中查找是否存在目标Type,存在则返回该package的Type池索引
                    ssize_t ti = group->packages[pi]->typeStrings.indexOfString(
                            targetType, targetTypeLen);
                    if (ti < 0) {
                        continue;
                    }
    
                    //索引加上偏移值,该偏移值为当前Package在PackageGroup的偏移?
                    ti += group->packages[pi]->typeIdOffset;
    
                    //根据Type索引找到目标资源Entry,并返回资源ID
                    const uint32_t identifier = findEntry(group, ti, name, nameLen,
                            outTypeSpecFlags);
                            
                    if (identifier != 0) {
                        if (fakePublic && outTypeSpecFlags) {
                            *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
                        }
                        //资源ID返回
                        return identifier;
                    }
                    
                } while (strzcmp16(attr.string(), attr.size(), targetType, targetTypeLen) == 0
                        && (targetType = attrPrivate.string())
                        && (targetTypeLen = attrPrivate.size())
                );
            }
            break;
        }
        return 0;
    }
    

    分析

    该方法根据资源全限定名获取资源ID:

    1. 遍历ResTable中的所有PackageGroup,即mPackageGroups
    2. 根据查找资源的包名,找到目标PackageGroup。
    3. 遍历目标PackageGroup的每个Package
    4. 在每个Package中查找资源Type,存在,则返回该Type在当前Package的偏移。
    5. 调用findEntry方法在当前PackageGroup中查找资源,如果查找到则返回资源ID。

    findEntry方法

    
    /**
     * 在目标PacakgeGroup中查找资源,并返回对应的资源ID
     */
    uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,
            size_t nameLen, uint32_t* outTypeSpecFlags) const {
        
        //目标PakcageGroup下所有Package的目标Type资源
        const TypeList& typeList = group->types[typeIndex];
        
        const size_t typeCount = typeList.size();
        //遍历每个Package的目标Type
        for (size_t i = 0; i < typeCount; i++) {
            const Type* t = typeList[i];
            
            //当前Package的key池中查找资源,返回资源在key池中的索引。
            const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
            if (ei < 0) {
                continue;
            }
    
            const size_t configCount = t->configs.size();
            //遍历当前package下目标Tyle的configs
            for (size_t j = 0; j < configCount; j++) {
                const TypeVariant tv(t->configs[j]);
                
                //遍历当前configs下的每个Entry
                for (TypeVariant::iterator iter = tv.beginEntries();
                     iter != tv.endEntries();
                     iter++) {
                    const ResTable_entry* entry = *iter;
                    if (entry == NULL) {
                        continue;
                    }
                    
                    //entry的资源索引是否与key池中索引相同,找到
                    if (dtohl(entry->key.index) == (size_t) ei) {
                        //根据PackageID、TypeID、entry出现的顺序生成 资源ID
                        uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
                        if (outTypeSpecFlags) {
                            Entry result;
                            if (getEntry(group, typeIndex, iter.index(), NULL, &result) != NO_ERROR) {
                                ALOGW("Failed to find spec flags for 0x%08x", resId);
                                return 0;
                            }
                            *outTypeSpecFlags = result.specFlags;
                        }
                        return resId;
                    }
                }
            }
        }
        return 0;
    }
    

    分析

    这个方法流程跟getEntry还是很相似的:

    1. 在目标PackageGroup中根据Type ID找到对应的TypeList
    2. 遍历找到的TypeList,即遍历每个Package该类型下的Type;
    3. 通过Type持有其所在Package的引用,找到Package,再在该Package中的key池中根据资源名称查找。返回结果为资源在Pakcage的key池中的偏移值,偏移值大于0则表示找到该资源。
    4. 找到目标Type后,再遍历该Type下的configs,并遍历每个configs下的Entry。如果Entry的资源索引与第3步返回的偏移值相同,则该Entry就是目标资源条目。
    5. 找到Entry,根据Pcakge ID、Type ID、Entry ID生成并返回资源ID。注意:其中Entry ID为资源Entry对应的config在Type中的顺序。

    AssetManager与ResTable


    AssetManager类的定义还是挺庞大的,这里就不贴出来了,从AssetManager的几个方法入手,了解其工作原理。在了解AssetManager方法前,先介绍几个AssetManager重要的成员变量。

    几个成员变量

    Vector<asset_path> mAssetPaths
    • 该变量保存了AssetManager已加载的资源文件(apk)的路径
    ResTable* mResources
    • 资源表,所有已加载的apk资源都会添加到该资源表中。
    • 即一个AssetManager与一个ResTable相关联。

    几个方法

    addAssetPath方法

    /**
     * 添加资源文件(apk)
     * path:资源文件路径
     *cookie:mAssetPaths列表中该apk索引
     */
    bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
    {
        AutoMutex _l(mLock);
        asset_path ap;
    
        //资源路径校验
        String8 realPath(path);
        if (kAppZipName) {
            realPath.appendPath(kAppZipName);
        }
        ap.type = ::getFileType(realPath.string());
        if (ap.type == kFileTypeRegular) {
            ap.path = realPath;
        } else {
            ap.path = path;
            ap.type = ::getFileType(path.string());
            if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
                ALOGW("Asset path %s is neither a directory nor file (type=%d).",
                     path.string(), (int)ap.type);
                return false;
            }
        }
    
        //该资源文件已加载,返回
        for (size_t i=0; i<mAssetPaths.size(); i++) {
            if (mAssetPaths[i].path == ap.path) {
                if (cookie) {
                    *cookie = static_cast<int32_t>(i+1);
                }
                return true;
            }
        }
    
        ALOGV("In %p Asset %s path: %s", this,
             ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
    
        
        //检查资源文件路径下是否有AndroidManifest.xml文件
        Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
                kAndroidManifest, Asset::ACCESS_BUFFER, ap);    
        if (manifestAsset == NULL) {
            // This asset path does not contain any resources.
            delete manifestAsset;
            return false;
        }
        delete manifestAsset;
    
        //添加资源文件路径
        mAssetPaths.add(ap);
    
        // new paths are always added at the end
        //更新cookie值,cookie值为ap在mAssetPaths中的索引
        if (cookie) {
            *cookie = static_cast<int32_t>(mAssetPaths.size());
        }
    
    #ifdef HAVE_ANDROID_OS
        //加载重叠资源文件
        asset_path oap;
        for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
            mAssetPaths.add(oap);
        }
    #endif
    
        if (mResources != NULL) {
            //添加资源到资源表
            appendPathToResTable(ap);
        }
    
        return true;
    }
    

    分析

    该方法主要根据资源文件路径,将其添加到AssetManager。方法主要流程有:

    1. 校验资源文件路径。
    2. 判断资源文件是否已加载,即已加载会将资源文件路径添加到mAssetPaths中。
    3. 调用openNonAssetInPathLocked方法,打开资源文件,检查文件中(即apk中)是否有AndroidManifest文件,没有则返回结束流程。
    4. 添加资源文件路径到mAssetPaths,并更新其cookie值。
    5. 如果有重叠资源文件包,则加载重叠资源文件。
    6. 调用appendPathToResTable方法,将资源文件下的资源添加到资源表中。

    从上面流程可以看出,资源的添加是通过appendPathToResTable方法实现的。

    appendPathToResTable方法

    /**
     * 添加资源到资源表
     * ap:资源文件路径
     */
    bool AssetManager::appendPathToResTable(const asset_path& ap) const {
        
        // skip those ap's that correspond to system overlays
        if (ap.isSystemOverlay) {
            return true;
        }
    
        Asset* ass = NULL;
        ResTable* sharedRes = NULL; //临时资源表
        bool shared = true;
        bool onlyEmptyResources = true;
        MY_TRACE_BEGIN(ap.path.string());
        
        //打开路径下的idmap资源
        Asset* idmap = openIdmapLocked(ap);
        
        //返回资源表中已加载资源包总数
        size_t nextEntryIdx = mResources->getTableCount();
        
        ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
        if (ap.type != kFileTypeDirectory) {
            
            //如果是系统资源包
            if (nextEntryIdx == 0) {
                //读取缓存的系统ResTable
                sharedRes = const_cast<AssetManager*>(this)->
                    mZipSet.getZipResourceTable(ap.path);
                
                //ResTable缓存命中
                if (sharedRes != NULL) {
                    //更新索引
                    nextEntryIdx = sharedRes->getTableCount();
                }
            }
            
            /**
             * case 1:系统资源包,ResTable缓存未命中
             * case 2: 应用资源包
             */
            if (sharedRes == NULL) {
                //读取缓存的资源Asset
                ass = const_cast<AssetManager*>(this)->
                    mZipSet.getZipResourceTableAsset(ap.path);
                    
                //Asset缓存未命中,读取文件
                if (ass == NULL) {
                    ALOGV("loading resource table %s\n", ap.path.string());
                    
                    //打开路径下的resources.arsc文件,读取资源Asset
                    ass = const_cast<AssetManager*>(this)->
                        openNonAssetInPathLocked("resources.arsc",
                                                 Asset::ACCESS_BUFFER,
                                                 ap);
                    
                    //缓存Asset,避免重复读取resources.arsc文件资源
                    if (ass != NULL && ass != kExcludedAsset) {
                        ass = const_cast<AssetManager*>(this)->
                            mZipSet.setZipResourceTableAsset(ap.path, ass);
                    }
                }
                
                //系统资源包
                if (nextEntryIdx == 0 && ass != NULL) {
                    // If this is the first resource table in the asset
                    // manager, then we are going to cache it so that we
                    // can quickly copy it out for others.
                    ALOGV("Creating shared resources for %s", ap.path.string());
                    sharedRes = new ResTable();
                    
                    //添加资源文件到sharedRes
                    sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
                    
    #ifdef HAVE_ANDROID_OS
                    //处理重叠包
                    const char* data = getenv("ANDROID_DATA");
                    LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
                    String8 overlaysListPath(data);
                    overlaysListPath.appendPath(kResourceCache);
                    overlaysListPath.appendPath("overlays.list");
                    addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
    #endif
                    //系统资源缓存到ResTable
                    sharedRes = const_cast<AssetManager*>(this)->
                        mZipSet.setZipResourceTable(ap.path, sharedRes);
                }
            }
        } else {
            ALOGV("loading resource table %s\n", ap.path.string());
            ass = const_cast<AssetManager*>(this)->
                openNonAssetInPathLocked("resources.arsc",
                                         Asset::ACCESS_BUFFER,
                                         ap);
            shared = false;
        }
    
        if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
            ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
            
            if (sharedRes != NULL) {
                ALOGV("Copying existing resources for %s", ap.path.string());
                
                //添加系统资源到mResources(AssetManager关联的ResTable)
                mResources->add(sharedRes);
            } else {
                ALOGV("Parsing resources for %s", ap.path.string());
                
                //添加应用资源
                mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
            }
            onlyEmptyResources = false;
    
            if (!shared) {
                delete ass;
            }
        } else {
            ALOGV("Installing empty resources in to table %p\n", mResources);
            
            //添加空的ResTable,只更新mResources的Header信息
            mResources->addEmpty(nextEntryIdx + 1);
        }
    
        if (idmap != NULL) {
            delete idmap;
        }
        MY_TRACE_END();
    
        return onlyEmptyResources;
    }
    

    分析

    该方法主要是讲资源文件中的资源添加到AssetManager中的资源表中,注释已经说明大部分问题,但仍旧需要注意以下几点:

    1. 添加资源时,都是先从缓存中读取。这里需要区别的是,系统资源包是缓存在资源表(ResTable)中,而应用资源包资源是缓存在Asset中。
    2. 缓存未命中时,读取resources.arsc文件中内容,并缓存。
    3. 无论缓存是否命中,最后都会将读取的资源添加到资源表mResources中。资源添加调用了ResTable的add方法,见上ResTable方法分析。

    相关文章

      网友评论

        本文标题:Android资源篇

        本文链接:https://www.haomeiwen.com/subject/qelmqxtx.html