该篇承接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资源在内存中的结构如下图:
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 找到资源的内存数据位置。