Android资源篇

前言


去年整理过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方法分析。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容