Android 得到主题中对应的属性的结果或自己设置的style中的结果

  该篇承接Android TypedArray简单分析(二)源代码分析

得到主题中对应的属性的结果或自己设置的style中的结果

  接着分析AttributeResolution类的ApplyStyle()函数,该函数首先调用GetStyleBag()得到的结果是从主题中设置的属性,或者自己设置的style中得到结果集,看下它

base::expected<const ResolvedBag*, NullOrIOError> GetStyleBag(Theme* theme,
                                                              uint32_t theme_attribute_resid,
                                                              uint32_t fallback_resid,
                                                              uint32_t* out_theme_flags) {
  // Load the style from the attribute if specified.
  if (theme_attribute_resid != 0U) {
    std::optional<AssetManager2::SelectedValue> value = theme->GetAttribute(theme_attribute_resid);
    if (value.has_value()) {
      *out_theme_flags |= value->flags;
      auto result = theme->GetAssetManager()->ResolveBag(*value);
      if (result.has_value() || IsIOError(result)) {
        return result;
      }
    }
  }

  // Fallback to loading the style from the resource id if specified.
  if (fallback_resid != 0U) {
    return theme->GetAssetManager()->GetBag(fallback_resid);
  }

  return base::unexpected(std::nullopt);
}

  该函数的参数theme_attribute_resid是属性资源id,fallback_resid是style资源id。在Android里面,会将资源使用一个int整数来表示。
  这个函数做了2件事。第一件,从主题中找到对应的属性的结果集合。第二件,得到自己设置的style的结果集合。如果第一件事就找到结果,第二件就不会做了。
  第一件事,从代码中,可以看到调用成员变量theme的GetAttribute()方法得到theme_attribute_resid属性资源id对应的SelectedValue。得到这个SelectedValue之后,还会通过theme的AssetManager进行解析它,最后生成值的结果集合ResolvedBag*。
  如果上面没有找到结果,则开始做第二件事,会调用theme的AssetManager的GetBag()方法得到自己设置的style的结果集合。
  这里说下Theme与AssetManager的关系。Theme对应着应用的主题。他里面的资源结果是AssetManager中的子集。如果通过Theme得到的结果是一个引用类型,还需要进入AssetManager中继续查询结果集。
  返回的结果都是用ResolvedBag结构来表示的,了解下ResolvedBag结构。

ResolvedBag结构

  它的定义在AssetManager2.h文件中:

// Holds a bag that has been merged with its parent, if one exists.
struct ResolvedBag {
  // A single key-value entry in a bag.
  struct Entry {
    // The key, as described in ResTable_map::name.
    uint32_t key;

    Res_value value;

    // The resource ID of the origin style associated with the given entry.
    uint32_t style;

    // Which ApkAssets this entry came from.
    ApkAssetsCookie cookie;

    ResStringPool* key_pool;
    ResStringPool* type_pool;
  };

  // Denotes the configuration axis that this bag varies with.
  // If a configuration changes with respect to one of these axis,
  // the bag should be reloaded.
  uint32_t type_spec_flags;

  // The number of entries in this bag. Access them by indexing into `entries`.
  uint32_t entry_count;

  // The array of entries for this bag. An empty array is a neat trick to force alignment
  // of the Entry structs that follow this structure and avoids a bunch of casts.
  Entry entries[0];
};

  成员变量type_spec_flags代表它的配置,如果配置信息发生改变,该bag需要重新加载。
  entry_count代表它有多少个Entry 。Entry 结构对应Bag资源中一条具体内容的值。
  Entry 结构的key是一条内容的属性的ID值。style是要查找的Bag资源的ID值。cookie是该资源来自哪个ApkAssets(对应APK文件),key_pool和type_pool分别是对应的key的字符串池和type的字符串池。
  Entry 结构的value是Bag资源中一条具体内容的值。该值有多种类型。根据类型,来处理对应的值。

1、从主题中找到对应的属性的结果集合

  上面说了,首先会在主题类theme中查找得到一个SelectedValue,然后再调用AssetManager类对它进行解析,得到它的结果集。

1.1 主题类theme中查找得到一个SelectedValue

  看下主题类Theme

class Theme {
  friend class AssetManager2;
  …………
  AssetManager2* asset_manager_ = nullptr;
  uint32_t type_spec_flags_ = 0u;

  std::vector<Entry> entries_;
}

struct Theme::Entry {
  uint32_t attr_res_id;
  ApkAssetsCookie cookie;
  uint32_t type_spec_flags;
  Res_value value;
};  

  Theme类的成员变量std::vector entries_,它是一个Entry集合。每个Entry对应主题XML文件中的一个item。如下每个item对应一个。

       <style name="Theme.MyTestKotlinApplication"
        		parent="Theme.MaterialComponents.DayNight.DarkActionBar">
          <!-- Primary brand color. -->
          <item name="colorPrimary">@color/purple_500</item>
          <item name="colorPrimaryVariant">@color/purple_700</item>
          <item name="colorOnPrimary">@color/white</item>
          <!-- Secondary brand color. -->
          <item name="colorSecondary">@color/teal_200</item>
          <item name="colorSecondaryVariant">@color/teal_700</item>
          <item name="colorOnSecondary">@color/black</item>
          <!-- Status bar color. -->
          <item name="android:statusBarColor" tools:targetApi="l">
          		?attr/colorPrimaryVariant</item>
          <!-- Customize your theme here.-->
          <item name="customViewAttr">@style/MyStyleTheme</item>
       </style>

  拿其中customViewAttr item来说。Entry的attr_res_id是R.attr.customViewAttr,cookie是指该资源来自哪个apk,type_spec_flags是该资源和什么配置相关,value是对应属性的值。value的dataType是TYPE_REFERENCE,value的data是R.style.MyStyleTheme。
  每个item对应的Res_value 的dataType也可能是别的类型,像这个例子中的android:statusBarColor属性,它对应的value的dataType是TYPE_ATTRIBUTE,value的data是R.attr.colorPrimaryVariant。像这种情况,在Theme中查询的时候,首先通过android:statusBarColor属性的id查询之后,会得到一个TYPE_ATTRIBUTE类型的Res_value。这个时候,还会继续用R.attr.colorPrimaryVariant进行查找,这样就能找到一个TYPE_REFERENCE的结果。
  所以Theme就是所有item的集合。这样通过属性id来和每个Entry的attr_res_id进行比较,就能找到对应的Entry。

std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {

  constexpr const uint32_t kMaxIterations = 20;
  uint32_t type_spec_flags = 0u;
  for (uint32_t i = 0; i <= kMaxIterations; i++) {
    auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
                                     ThemeEntryKeyComparer{});
    if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
      return std::nullopt;
    }

    type_spec_flags |= entry_it->type_spec_flags;
    if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
      resid = entry_it->value.data;
      continue;
    }

    return AssetManager2::SelectedValue(entry_it->value.dataType, entry_it->value.data,
                                        entry_it->cookie, type_spec_flags, 0U /* resid */,
                                        {} /* config */);
  }
  return std::nullopt;
}

  通过上面的解释,再看对应的查找代码,应该就容易了。

1.2 AssetManager类进行解析

  接着就是把上面的SelectedValue继续进行解析,在文件platform\frameworks\base\libs\androidfw\AssetManager2.cpp,调用ResolveBag()方法来解析:

base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::ResolveBag(
    AssetManager2::SelectedValue& value) const {
  if (UNLIKELY(value.type != Res_value::TYPE_REFERENCE)) {
    return base::unexpected(std::nullopt);
  }

  auto bag = GetBag(value.data);
  if (bag.has_value()) {
    value.flags |= (*bag)->type_spec_flags;
  }
  return bag;
}

base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(uint32_t resid) const {
  std::vector<uint32_t> found_resids;
  const auto bag = GetBag(resid, found_resids);
  cached_bag_resid_stacks_.emplace(resid, found_resids);
  return bag;
}

  可见调用该方法,先检查要解析的value,是不是TYPE_REFERENCE类型的资源,如果不是,就直接返回null了。最终会调用到AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids)方法,它有两个参数,第一个是资源ID,是一个32位整数值;第二个参数是一个集合,它是用来处理当资源存在父资源,可能会导致的无限循环查找的情况。例如A style标签里,设置一个parent属性,它设置成B style。B style的parent属性再设置成A style。像存在父资源的情况,会将父资源的item和它本身的item结合作为结果返回。每次查父资源的时候,还会发现存在父资源,就会一直循环。第二个参数child_resids,在查找一次资源ID后,会将该资源ID记录进去,如果再发现该父资源ID,就不会接着循环下去。

1.2.1 GetBag(uint32_t resid, std::vector<uint32_t>& child_resids)

  这个方法挺长,分段来阅读,其中一些代码省略,只说重点代码:

base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag(
    uint32_t resid, std::vector<uint32_t>& child_resids) const {
  if (auto cached_iter = cached_bags_.find(resid); cached_iter != cached_bags_.end()) {
    return cached_iter->second.get();
  }
  
  auto entry = FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */,
                         false /* ignore_configuration */);
  if (!entry.has_value()) {
    return base::unexpected(entry.error());
  }
  auto entry_map = std::get_if<incfs::verified_map_ptr<ResTable_map_entry>>(&entry->entry);
  if (entry_map == nullptr) {
    // Not a bag, nothing to do.
    return base::unexpected(std::nullopt);
  }

  auto map = *entry_map;
  auto map_entry = map.offset(dtohs(map->size)).convert<ResTable_map>();
  const auto map_entry_end = map_entry + dtohl(map->count);           

  首先去找是否存在缓存,这个是存在cached_bags_中,如果存在,就返回。接着会调用FindEntry(),得到entry,这个是FindEntryResult对象。entry->entry里面存放的是incfs::verified_map_ptr<ResTable_map_entry>,接着通过转化,map 就是ResTable_map_entry指针地址,再偏移map->size个字节,就能找到ResTable_map地址,然后再加上dtohl(map->count),再得到最后一个ResTable_map地址结尾。
  这个待解析的resid,是一个style类型的资源。它对应着xml文件中的style。像这种资源,在Android中,叫做Bag资源。上面的ResTable_map_entry、ResTable_map都是用来描述Bag资源的数据结构。看下相关数据结构,

/**
 * This is the beginning of information about an entry in the resource
 * table.  It holds the reference to the name of this entry, and is
 * immediately followed by one of:
 *   * A Res_value structure, if FLAG_COMPLEX is -not- set.
 *   * An array of ResTable_map structures, if FLAG_COMPLEX is set.
 *     These supply a set of name/value mappings of data.
 */
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,
        // If set, this is a weak resource and may be overriden by strong
        // resources of the same name/type. This is only useful during
        // linking with other resource tables.
        FLAG_WEAK = 0x0004,
    };
    uint16_t flags;
    
    // Reference into ResTable_package::keyStrings identifying this entry.
    struct ResStringPool_ref key;
};

/**
 * Extended form of a ResTable_entry for map entries, defining a parent map
 * resource from which to inherit values.
 */
struct ResTable_map_entry : public ResTable_entry
{
    // Resource identifier of the parent mapping, or 0 if there is none.
    // This is always treated as a TYPE_DYNAMIC_REFERENCE.
    ResTable_ref parent;
    // Number of name/value pairs that follow for FLAG_COMPLEX.
    uint32_t count;
};

/**
 * A single name/value mapping that is part of a complex resource
 * entry.
 */
struct ResTable_map
{
    // The resource identifier defining this mapping's name.  For attribute
    // resources, 'name' can be one of the following special resource types
    // to supply meta-data about the attribute; for all other resource types
    // it must be an attribute resource.
    ResTable_ref name;
    …………    
    // This mapping's value.
    Res_value value;
}

  ResTable_map_entry 继承ResTable_entry,它的成员变量size,是ResTable_map_entry 占据的内存大小,它后面是ResTable_map结构数据。而ResTable_map_entry 的成员变量count,就代表它后面有多少个ResTable_map结构数据。Bag资源在内存中的结构如下图:
Bag资源内存结构图

Bag资源内存结构图
  ResTable_map结构数据是对应着,xml文件中style中的每个item 内容。ResTable_map的成员变量name是属性名ID值,成员变量value是item的内容值。 这样一解释,应该就能懂上面的代码的意思了。

  FindEntry()函数还没说,这个下篇文章再说,它是个核心方法。先把GetBag(uint32_t resid, std::vector<uint32_t>& child_resids)的方法的逻辑说完。
  接着看GetBag()函数的剩下部分:

  …………
  uint32_t parent_resid = dtohl(map->parent.ident);
  if (parent_resid == 0U ||
      std::find(child_resids.begin(), child_resids.end(), parent_resid) != child_resids.end()) {
    // There is no parent or a circular parental dependency exist, meaning there is nothing to
    // inherit and we can do a simple copy of the entries in the map.
    const size_t entry_count = map_entry_end - map_entry;
    util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
        malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))};

    bool sort_entries = false;
    for (auto new_entry = new_bag->entries; map_entry != map_entry_end; ++map_entry) {
      if (UNLIKELY(!map_entry)) {
        return base::unexpected(IOError::PAGES_MISSING);
      }

      uint32_t new_key = dtohl(map_entry->name.ident);
      …………
      new_entry->cookie = entry->cookie;
      new_entry->key = new_key;
      new_entry->key_pool = nullptr;
      new_entry->type_pool = nullptr;
      new_entry->style = resid;
      new_entry->value.copyFrom_dtoh(map_entry->value);
      status_t err = entry->dynamic_ref_table->lookupResourceValue(&new_entry->value);
      if (UNLIKELY(err != NO_ERROR)) {
        LOG(ERROR) << base::StringPrintf(
            "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
            new_entry->value.data, new_key);
        return base::unexpected(std::nullopt);
      }

      sort_entries = sort_entries ||
          (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
      ++new_entry;
    }
    if (sort_entries) {
      std::sort(new_bag->entries, new_bag->entries + entry_count,
                [](auto&& lhs, auto&& rhs) { return lhs.key < rhs.key; });
    }

    new_bag->type_spec_flags = entry->type_flags;
    new_bag->entry_count = static_cast<uint32_t>(entry_count);
    ResolvedBag* result = new_bag.get();
    cached_bags_[resid] = std::move(new_bag);
    return result;
  }
  …………    

  这块代码,是处理该Bag资源没有父类的情况下得处理。如果该Bag资源是style类型资源,就对应着没设置parent 属性的情况。查找出来的结果是用ResolvedBag类来表示的。看代码,根据ResTable_map的数量,来分配ResolvedBag。然后,在对应设置它的具体值。
  entry->cookie(int32_t)代表它来自哪个ApkAssets(对应APK文件),new_key是Bag资源的具体每条属性名称的ID值,例如style类型资源对应item 属性名称的ID值。并且将new_entry->style的值设置为了要查找的Bag资源的ID值。并且将map_entry->value的值拷贝到new_entry->value。
  并且会将ResolvedBag中的entries进行升序排序。如果它发现,下一个new_entry比它之前的key的值小的话,会发生排序,保持升序排列。
  entry->type_flags代表是资源的配置信息。它是和屏幕密度、MCC、MNC等相关的。具体的在platform\frameworks\base\libs\androidfw\include\androidfw\ResourceTypes.h

    // Flags indicating a set of config values.  These flag constants must
    // match the corresponding ones in android.content.pm.ActivityInfo and
    // attrs_manifest.xml.
    enum {
        CONFIG_MCC = ACONFIGURATION_MCC,
        CONFIG_MNC = ACONFIGURATION_MNC,
        CONFIG_LOCALE = ACONFIGURATION_LOCALE,
        CONFIG_TOUCHSCREEN = ACONFIGURATION_TOUCHSCREEN,
        CONFIG_KEYBOARD = ACONFIGURATION_KEYBOARD,
        CONFIG_KEYBOARD_HIDDEN = ACONFIGURATION_KEYBOARD_HIDDEN,
        CONFIG_NAVIGATION = ACONFIGURATION_NAVIGATION,
        CONFIG_ORIENTATION = ACONFIGURATION_ORIENTATION,
        CONFIG_DENSITY = ACONFIGURATION_DENSITY,
        CONFIG_SCREEN_SIZE = ACONFIGURATION_SCREEN_SIZE,
        CONFIG_SMALLEST_SCREEN_SIZE = ACONFIGURATION_SMALLEST_SCREEN_SIZE,
        CONFIG_VERSION = ACONFIGURATION_VERSION,
        CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT,
        CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
        CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
        CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
        CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE,
    };

  最后,会将个数设置到ResolvedBag类中。并且会将该查找ResolvedBag放入cached_bags_中,如果以后再查找,就会在开始直接返回了。最后就是将结果返回。

	…………
 // Get the parent and do a merge of the keys.
  const auto parent_bag = GetBag(parent_resid, child_resids);
	…………
  // Create the max possible entries we can make. Once we construct the bag,
  // we will realloc to fit to size.
  const size_t max_count = (*parent_bag)->entry_count + dtohl(map->count);
  util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>(
      malloc(sizeof(ResolvedBag) + (max_count * sizeof(ResolvedBag::Entry))))};
  ResolvedBag::Entry* new_entry = new_bag->entries;

  const ResolvedBag::Entry* parent_entry = (*parent_bag)->entries;
  const ResolvedBag::Entry* const parent_entry_end = parent_entry + (*parent_bag)->entry_count;
  // The keys are expected to be in sorted order. Merge the two bags.
  bool sort_entries = false;
  while (map_entry != map_entry_end && parent_entry != parent_entry_end) {
  	…………
    uint32_t child_key = dtohl(map_entry->name.ident);    	  
    …………
    if (child_key <= parent_entry->key) {
      // Use the child key if it comes before the parent
      // or is equal to the parent (overrides).
      new_entry->cookie = entry->cookie;
      new_entry->key = child_key;
      new_entry->key_pool = nullptr;
      new_entry->type_pool = nullptr;
      new_entry->value.copyFrom_dtoh(map_entry->value);
      new_entry->style = resid;
      status_t err = entry->dynamic_ref_table->lookupResourceValue(&new_entry->value);
      if (UNLIKELY(err != NO_ERROR)) {
        LOG(ERROR) << base::StringPrintf(
            "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
            new_entry->value.data, child_key);
        return base::unexpected(std::nullopt);
      }
      ++map_entry;
    } else {
      // Take the parent entry as-is.
      memcpy(new_entry, parent_entry, sizeof(*new_entry));
    }

    sort_entries = sort_entries ||
        (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
    if (child_key >= parent_entry->key) {
      // Move to the next parent entry if we used it or it was overridden.
      ++parent_entry;
    }
    // Increment to the next entry to fill.
    ++new_entry;
  }

  // Finish the child entries if they exist.
  while (map_entry != map_entry_end) {
    …………
    uint32_t new_key = dtohl(map_entry->name.ident);
    …………
    new_entry->cookie = entry->cookie;
    new_entry->key = new_key;
    new_entry->key_pool = nullptr;
    new_entry->type_pool = nullptr;
    new_entry->value.copyFrom_dtoh(map_entry->value);
    new_entry->style = resid;
    status_t err = entry->dynamic_ref_table->lookupResourceValue(&new_entry->value);
    …………
    sort_entries = sort_entries ||
        (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key));
    ++map_entry;
    ++new_entry;
  }

  // Finish the parent entries if they exist.
  if (parent_entry != parent_entry_end) {
    // Take the rest of the parent entries as-is.
    const size_t num_entries_to_copy = parent_entry_end - parent_entry;
    memcpy(new_entry, parent_entry, num_entries_to_copy * sizeof(*new_entry));
    new_entry += num_entries_to_copy;
  }

  // Resize the resulting array to fit.
  const size_t actual_count = new_entry - new_bag->entries;
  if (actual_count != max_count) {
    new_bag.reset(reinterpret_cast<ResolvedBag*>(realloc(
        new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
  }

  if (sort_entries) {
    std::sort(new_bag->entries, new_bag->entries + actual_count,
              [](auto&& lhs, auto&& rhs) { return lhs.key < rhs.key; });
  }

  // Combine flags from the parent and our own bag.
  new_bag->type_spec_flags = entry->type_flags | (*parent_bag)->type_spec_flags;
  new_bag->entry_count = static_cast<uint32_t>(actual_count);
  ResolvedBag* result = new_bag.get();
  cached_bags_[resid] = std::move(new_bag);
  return result; 
}       

  这最后这一段代码,是主要处理Bag资源存在parent属性的情况。先将父Bag资源解析出来结果,然后将父Bag资源结果和当前Bag资源结果进行合并。并且这个结果集合里面,也是按照属性的资源ID值得大小进行升序排列的。总体的逻辑就是这样,其实并不难。可以结合代码看一下。
  不过81行代码,会比较最终的结果里的ResolvedBag::Entry的数量和刚开始max_count进行比较,如果不相等,会重新设置大小。这个怎么理解,原来,Bag资源里的Entry可能会重写父Bag资源里的Entry。如果存在这种情况,结果集里面只收集Bag资源里的Entry。还拿Style类型举例子,当前Style里的item的属性重写了父Style的item的属性。最终的结果,只会取当前Style里的item的属性值。这块的代码处理在上面44~46行。

2、得到自己设置的style的结果集合

   它也是调用theme的AssetManager的GetBag()方法得到自己设置的style的结果集合。这个逻辑和上面说的一样。
  这块只是将GetBag(uint32_t resid, std::vector<uint32_t>& child_resids)的处理逻辑说了一遍,比较核心的FindEntry()还没说。看下一篇文章Android 找到资源的内存数据位置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值