前言
上一篇文章已經(jīng)聊了資源系統(tǒng)的初始化,本文就來看看資源適合查找到的。
如果遇到問題,歡迎在下面這個地址下留言:http://www.itdecent.cn/p/b153d63d60b3
正文
資源的查找,Xml布局文件的讀取
到這里AssetManager就生成了,我們來看看資源是怎么查找的。我們來看看之前我沒有深入探討的解析Xml方法。
文件:/frameworks/base/core/java/android/content/res/Resources.java
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
...
} finally {
releaseTempTypedValue(value);
}
}
這里大致分為兩個步驟:
- 1.通過ResourcesImpl.getValue獲取當(dāng)前resId對應(yīng)的資源,設(shè)置到TypedValue
- 2.如果當(dāng)前返回的數(shù)據(jù)類型是String,則直接調(diào)用loadXmlResourceParser 讀取資源具體的內(nèi)容中,如Xml布局文件。
- 3.緩存當(dāng)前資源
查找resId對應(yīng)的資源
文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java
void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
...
}
能看到這里面調(diào)用了AssetManager的getResourceValue方法。
文件:/frameworks/base/core/java/android/content/res/AssetManager.java
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
synchronized (this) {
ensureValidLocked();
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
在這里面會調(diào)用nativeGetResourceValue獲取到Asset的cookie,從底層復(fù)制數(shù)據(jù)到TypedValue中。如果判斷到TypedValue中解析出來的數(shù)據(jù)是String類型,則從全局字符串字符串中獲取對應(yīng)TypeValue中data對應(yīng)的字符串?dāng)?shù)據(jù)。
那么我們可以推測,如果當(dāng)前的資源類型是一個字符串(說明找到),那么nativeGetResourceValue方法實際上并不會賦值給outValue.string。因為可以通過字符串資源池更加快速查找字符串的方法。
獲取native層資源id對應(yīng)的資源
文件:/frameworks/base/core/jni/android_util_AssetManager.cpp
using ApkAssetsCookie = int32_t;
enum : ApkAssetsCookie {
kInvalidCookie = -1,
};
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
Res_value value;
ResTable_config selected_config;
uint32_t flags;
ApkAssetsCookie cookie =
assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
static_cast<uint16_t>(density), &value, &selected_config, &flags);
if (cookie == kInvalidCookie) {
return ApkAssetsCookieToJavaCookie(kInvalidCookie);
}
uint32_t ref = static_cast<uint32_t>(resid);
if (resolve_references) {
cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
if (cookie == kInvalidCookie) {
return ApkAssetsCookieToJavaCookie(kInvalidCookie);
}
}
return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
}
在里面設(shè)計到一個比較核心的結(jié)構(gòu)體ApkAssetsCookie。這個對象是在構(gòu)建動態(tài)資源映射表時候,按照順序遞增加入到packageGroup中。這個結(jié)構(gòu)體能看到,是十分簡單的只包含一個int類型。
換句話說,ApkAssetsCookie對應(yīng)到Java層的Cookie實際上就是指當(dāng)前的資源來源于packageGroup中cookie的index,注意加入邏輯,cookie添加的順序?qū)嶋H上和package數(shù)據(jù)包添加的順序是一致,也就是說,可以通過這個cookief反向查找package數(shù)據(jù)包。
能看到這里面有一個比較核心的方法,assetmanager->GetResource。
AssetManager2 GetResource
文件:/frameworks/base/libs/androidfw/AssetManager2.cpp
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
uint16_t density_override, Res_value* out_value,
ResTable_config* out_selected_config,
uint32_t* out_flags) const {
FindEntryResult entry;
ApkAssetsCookie cookie =
FindEntry(resid, density_override, false /* stop_at_first_match */, &entry);
if (cookie == kInvalidCookie) {
return kInvalidCookie;
}
if (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
if (!may_be_bag) {
...
return kInvalidCookie;
}
// Create a reference since we can't represent this complex type as a Res_value.
out_value->dataType = Res_value::TYPE_REFERENCE;
out_value->data = resid;
*out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
const Res_value* device_value = reinterpret_cast<const Res_value*>(
reinterpret_cast<const uint8_t*>(entry.entry) + dtohs(entry.entry->size));
out_value->copyFrom_dtoh(*device_value);
// Convert the package ID to the runtime assigned package ID.
entry.dynamic_ref_table->lookupResourceValue(out_value);
*out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
這個方法中包含了兩種情況:
1.當(dāng)當(dāng)前的引用屬于比較復(fù)雜的時候,是一個引用,并非真實的資源數(shù)據(jù),則data返回的是當(dāng)前引用中的resid。
2.通過FindEntry,查找每一個資源的entry,還記得上面說過的,每一個entry會包含真實的資源數(shù)據(jù),這個時候out_value會獲取當(dāng)前entry中的真實數(shù)據(jù)。接著會覆蓋當(dāng)前的資源id,以及相關(guān)的配置等信息。
FindEntry查找資源Entry
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
bool /*stop_at_first_match*/,
FindEntryResult* out_entry) const {
// Might use this if density_override != 0.
ResTable_config density_override_config;
// Select our configuration or generate a density override configuration.
const ResTable_config* desired_config = &configuration_;
if (density_override != 0 && density_override != configuration_.density) {
density_override_config = configuration_;
density_override_config.density = density_override;
desired_config = &density_override_config;
}
if (!is_valid_resid(resid)) {
LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
return kInvalidCookie;
}
const uint32_t package_id = get_package_id(resid);
const uint8_t type_idx = get_type_id(resid) - 1;
const uint16_t entry_idx = get_entry_id(resid);
const uint8_t package_idx = package_ids_[package_id];
if (package_idx == 0xff) {
...
return kInvalidCookie;
}
const PackageGroup& package_group = package_groups_[package_idx];
const size_t package_count = package_group.packages_.size();
ApkAssetsCookie best_cookie = kInvalidCookie;
const LoadedPackage* best_package = nullptr;
const ResTable_type* best_type = nullptr;
const ResTable_config* best_config = nullptr;
ResTable_config best_config_copy;
uint32_t best_offset = 0u;
uint32_t type_flags = 0u;
// If desired_config is the same as the set configuration, then we can use our filtered list
// and we don't need to match the configurations, since they already matched.
const bool use_fast_path = desired_config == &configuration_;
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
ApkAssetsCookie cookie = package_group.cookies_[pi];
// If the type IDs are offset in this package, we need to take that into account when searching
// for a type.
const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
if (UNLIKELY(type_spec == nullptr)) {
continue;
}
uint16_t local_entry_idx = entry_idx;
// If there is an IDMAP supplied with this package, translate the entry ID.
if (type_spec->idmap_entries != nullptr) {
if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) {
// There is no mapping, so the resource is not meant to be in this overlay package.
continue;
}
}
type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
// If the package is an overlay, then even configurations that are the same MUST be chosen.
const bool package_is_overlay = loaded_package->IsOverlay();
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
if (use_fast_path) {
const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
const size_t type_count = candidate_configs.size();
for (uint32_t i = 0; i < type_count; i++) {
const ResTable_config& this_config = candidate_configs[i];
// We can skip calling ResTable_config::match() because we know that all candidate
// configurations that do NOT match have been filtered-out.
if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
(package_is_overlay && this_config.compare(*best_config) == 0)) {
const ResTable_type* type_chunk = filtered_group.types[i];
const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
continue;
}
best_cookie = cookie;
best_package = loaded_package;
best_type = type_chunk;
best_config = &this_config;
best_offset = offset;
}
}
} else {
const auto iter_end = type_spec->types + type_spec->type_count;
for (auto iter = type_spec->types; iter != iter_end; ++iter) {
ResTable_config this_config;
this_config.copyFromDtoH((*iter)->config);
if (this_config.match(*desired_config)) {
if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
(package_is_overlay && this_config.compare(*best_config) == 0)) {
const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
continue;
}
best_cookie = cookie;
best_package = loaded_package;
best_type = *iter;
best_config_copy = this_config;
best_config = &best_config_copy;
best_offset = offset;
}
}
}
}
}
if (UNLIKELY(best_cookie == kInvalidCookie)) {
return kInvalidCookie;
}
const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
if (UNLIKELY(best_entry == nullptr)) {
return kInvalidCookie;
}
out_entry->entry = best_entry;
out_entry->config = *best_config;
out_entry->type_flags = type_flags;
out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
out_entry->entry_string_ref =
StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
return best_cookie;
}
在這里,我們先回顧一下資源ID的組成:
資源ID:0xPPTTEEEE 。最高兩位PP是指PackageID,一般編譯之后,應(yīng)用資源包是0x7f,系統(tǒng)資源包是0x01,而第三方資源包,則是從0x02開始逐個遞增。接下來的兩位TT,代表著當(dāng)前資源類型id,如anim文件夾下的資源就是0x01,組合起來就是0x7f01.最后四位是指資源entryID,是指的資源每一項對應(yīng)的id,可能是0000,一般是按照資源編譯順序遞增,如果是0001,則當(dāng)前資源完整就是0x7f010001.
而這個方法就需要尋找通過資源id去尋找正確的資源。步驟如下:
- 1.解析當(dāng)前資源id中packageID,typeID,entryID
- 2.獲取AssetManager2中的packageID對應(yīng)的packageGroup,在這個packageGroup中,根據(jù)typeID尋找對應(yīng)資源類型。如果發(fā)現(xiàn)typeSpec中有idmap說明有id需要被覆蓋,就嘗試通過Loadedmap::Lookup,轉(zhuǎn)化一下entryID(從IdEntryMap的數(shù)組獲取對應(yīng)entry數(shù)組中的id),接著從ConfiguredPackage獲取到對應(yīng)的package數(shù)據(jù)。
接下來有兩種情況,一種是和原來的配置config一致,一種是不一致。
1.一致的情況下,在ConfiguredPackage中存放著在根據(jù)config已經(jīng)過濾好的資源列表,直接從里面循環(huán)直接拿到對應(yīng)資源Type,并調(diào)用方法GetEntryOffset根據(jù)資源entryId獲取對應(yīng)的資源entry對象。
- 不一致的情況下,則循環(huán)typeSpec中映射好的資源關(guān)系,先尋找合適的config接著在嘗試尋找有沒有對應(yīng)的entryID。
最后確定已經(jīng)存在了資源的存在,則會通過當(dāng)前的資源類型以及資源類型中的偏移數(shù)組通過方法GetEntryFromOffset獲取對應(yīng)的entry。
最后返回當(dāng)前的cookie。
通過id尋找是否存在對應(yīng)的entry
文件;/frameworks/base/libs/androidfw/LoadedArsc.cpp
uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {
const size_t entry_count = dtohl(type_chunk->entryCount);
const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
...
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
return dtohl(entry_offsets[entry_index]);
}
能看到通過獲取資源ResTable_type起始地址+ResTable_type的頭部大小+頭部起點地址,來找到entry偏移數(shù)組。

通過偏移數(shù)組中的結(jié)果來確定當(dāng)前的entry是否存在。
GetEntryFromOffset 查找具體的內(nèi)容
const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
uint32_t offset) {
...
return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
offset + dtohl(type_chunk->entriesStart));
}
因為在type中記錄對應(yīng)entry中數(shù)據(jù)的偏移量,因此,可以通過簡單的相加找到對應(yīng)的地址。

而entry當(dāng)中就有一個Res_value對象.這個對象保存著真實的數(shù)據(jù),最后會通過CopyValue的方法,把Res_value.data拷貝到TypeValue中。此時就擁有了當(dāng)前資源真實數(shù)據(jù)。換到當(dāng)前情景就是指,找到了布局文件,layout/xxx.xml字符串對應(yīng)的index。
通過cookie以及解析資源信息嘗試查找非Asset資源中具體內(nèi)容
上一個步驟中已經(jīng)準備好了資源包對應(yīng)的cookie,確認了資源的存在以及位置,可以嘗試的著讀取數(shù)據(jù)。
文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
@NonNull String type)
throws NotFoundException {
if (id != 0) {
try {
synchronized (mCachedXmlBlocks) {
final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;
final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;
final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;
// First see if this block is in our cache.
final int num = cachedXmlBlockFiles.length;
for (int i = 0; i < num; i++) {
if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null
&& cachedXmlBlockFiles[i].equals(file)) {
return cachedXmlBlocks[i].newParser();
}
}
final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
if (block != null) {
final int pos = (mLastCachedXmlBlockIndex + 1) % num;
mLastCachedXmlBlockIndex = pos;
final XmlBlock oldBlock = cachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();
}
cachedXmlBlockCookies[pos] = assetCookie;
cachedXmlBlockFiles[pos] = file;
cachedXmlBlocks[pos] = block;
return block.newParser();
}
}
} catch (Exception e) {
...
}
}
...
}
能看見在整個ResourcesImpl中會對所有加載過的Xml文件有一層mCachedXmlBlockFiles緩存,如果找不到則會嘗試著通過openXmlBlockAsset從native查找數(shù)據(jù)。最后會通過XmlBlock生成解析器。
我們看看AssetManager的openXmlBlockAsset方法。
@NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
final XmlBlock block = new XmlBlock(this, xmlBlock);
incRefsLocked(block.hashCode());
return block;
}
}
該方法會調(diào)用native方法 nativeOpenXmlAsset。這個方法最后會獲取native層數(shù)據(jù)塊對應(yīng)的地址,并且交給XmlBlock進行操控。
文件:/frameworks/base/core/jni/android_util_AssetManager.cpp
static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie,
jstring asset_path) {
ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
ScopedUtfChars asset_path_utf8(env, asset_path);
...
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
std::unique_ptr<Asset> asset;
if (cookie != kInvalidCookie) {
asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
} else {
asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie);
}
....
const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
asset.reset();
...
return reinterpret_cast<jlong>(xml_tree.release());
}
這里大致上分為如下幾個步驟:
- 1.通過OpenNonAsset讀取資源名稱對應(yīng)的Asset
- 2.獲取AssetManager2的動態(tài)映射表,并且把cookie對應(yīng)的動態(tài)映射表轉(zhuǎn)化為ResXMLTree,讀取asset中對應(yīng)的數(shù)據(jù),設(shè)置到ResXMLTree中。等待解析。
OpenNonAsset讀取資源名稱對應(yīng)的Asset
文件:/frameworks/base/libs/androidfw/AssetManager2.cpp
這個方法是要打開所有非Asset資源對象,這種資源還是以zip的entry方式讀取出來。
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
Asset::AccessMode mode,
ApkAssetsCookie* out_cookie) const {
for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
if (asset) {
if (out_cookie != nullptr) {
*out_cookie = i;
}
return asset;
}
}
if (out_cookie != nullptr) {
*out_cookie = kInvalidCookie;
}
return {};
}
循環(huán)每一個AssetManager2管理的ApkAsset對象,獲取對應(yīng)資源Asset。此時的Asset,就是之前我們nativeLoad的時候解析resource.arsc生成的對象。
而方法名中的Asset的這個Asset并非是native層的解析resource.arsc的Asset資源,而是指Asset文件夾
ApkAssets::Open
std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
CHECK(zip_handle_ != nullptr);
::ZipString name(path.c_str());
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
...
if (entry.method == kCompressDeflated) {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
....
std::unique_ptr<Asset> asset =
Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
...
return asset;
} else {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
...
std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
...
return asset;
}
}
這里的邏輯其實和之前說過解包resource.arsc的邏輯一樣。注意這里這個方法名也叫FindEntry,不過找的是zip包中壓縮單位,而不是資源數(shù)據(jù)中的資源entry。這樣就能找到資源的layout布局文件,在資源目錄下的數(shù)據(jù)。
XmlBlock生成解析器
XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
mAssets = assets;
mNative = xmlBlock;
mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
}
此時XmlBlock會持有之前從zip包中解析出來的xmlBlock,以及全局字符串,方便之后解析Xml文件。
接下來就是解析一個樹狀的數(shù)據(jù)結(jié)構(gòu),獲取里面所有的屬性了,這里就不贅述。
AssetManager查找Asset資源
上面一大段聊了非Asset資源的查找,接下來讓我們看看Asset資源的查找。而方法名中的Asset的這個Asset并非是native層的解析resource.arsc的Asset資源,而是指Asset文件夾。
當(dāng)我們想要打開Asset目錄下資源文件的時候,一般會調(diào)用如下方法:
public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset file: " + fileName);
}
final AssetInputStream assetInputStream = new AssetInputStream(asset);
incRefsLocked(assetInputStream.hashCode());
return assetInputStream;
}
}
而這個方法最后會調(diào)用native層下的如下方法:
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
Asset::AccessMode mode) const {
const std::string new_path = "assets/" + filename;
return OpenNonAsset(new_path, cookie, mode);
}
系統(tǒng)這種方式,設(shè)置了相對路徑。一樣還是從OpenNonAsset中查找數(shù)據(jù)。最后會把Asset返回回去。此時AssetInputStream就會嘗試著操作這個Asset對象,會持有著對應(yīng)zipEntry,生成的FileMap。
當(dāng)我們嘗試著讀取數(shù)據(jù)的時候會調(diào)用AssetInputStream中的read方法:
文件:/frameworks/base/core/jni/android_util_AssetManager.cpp
static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
uint8_t b;
ssize_t res = asset->read(&b, sizeof(b));
return res == sizeof(b) ? static_cast<jint>(b) : -1;
}
資源屬性的獲取
在資源管理體系中,還有一個很重要的知識點,那就是解析View標簽中寫的屬性。在此之前,想要理解整個資源Theme屬性是如何獲取的,先來看看Theme是如何初始化的。
Theme的初始化
文件:/frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
....
}
一般在Activity的onCreate的生命周期,會調(diào)用setTheme設(shè)置從Xml中解析出來的主題Theme。而這個方法最后會調(diào)用到ContextImpl中:
@Override
public void setTheme(int resId) {
synchronized (mSync) {
if (mThemeResource != resId) {
mThemeResource = resId;
initializeTheme();
}
}
}
private void initializeTheme() {
if (mTheme == null) {
mTheme = mResources.newTheme();
}
mTheme.applyStyle(mThemeResource, true);
}
能看到其實整個主題是有一個Theme對象在控制,最后才把resID設(shè)置到Theme對象中進去。
文件:/frameworks/base/core/java/android/content/res/Resources.java
public final Theme newTheme() {
Theme theme = new Theme();
theme.setImpl(mResourcesImpl.newThemeImpl());
synchronized (mThemeRefs) {
mThemeRefs.add(new WeakReference<>(theme));
if (mThemeRefs.size() > mThemeRefsNextFlushSize) {
mThemeRefs.removeIf(ref -> ref.get() == null);
mThemeRefsNextFlushSize = Math.max(MIN_THEME_REFS_FLUSH_SIZE,
2 * mThemeRefs.size());
}
}
return theme;
}
在Resources中,能看到每一個主題都換緩存到弱引用下來,方便下次查找。核心方法是實例化了一個ThemeImpl對象。
文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java
ThemeImpl newThemeImpl() {
return new ThemeImpl();
}
public class ThemeImpl {
/**
* Unique key for the series of styles applied to this theme.
*/
private final Resources.ThemeKey mKey = new Resources.ThemeKey();
@SuppressWarnings("hiding")
private final AssetManager mAssets;
private final long mTheme;
/**
* Resource identifier for the theme.
*/
private int mThemeResId = 0;
/*package*/ ThemeImpl() {
mAssets = ResourcesImpl.this.mAssets;
mTheme = mAssets.createTheme();
}
...
}
文件:/frameworks/base/core/java/android/content/res/AssetManager.java
long createTheme() {
synchronized (this) {
ensureValidLocked();
long themePtr = nativeThemeCreate(mObject);
incRefsLocked(themePtr);
return themePtr;
}
}
調(diào)用native方法實例化底層的Theme對象.
文件;/frameworks/base/core/jni/android_util_AssetManager.cpp
static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
}
std::unique_ptr<Theme> AssetManager2::NewTheme() {
return std::unique_ptr<Theme>(new Theme(this));
}
這樣就對應(yīng)著Java層中ThemeImpl,在native中同樣生成一樣的Theme。
ThemeImpl.applyStyle
文件:/frameworks/base/core/java/android/content/res/ResourcesImpl.java
void applyStyle(int resId, boolean force) {
synchronized (mKey) {
mAssets.applyStyleToTheme(mTheme, resId, force);
mThemeResId = resId;
mKey.append(resId, force);
}
}
文件:/frameworks/base/core/java/android/content/res/AssetManager.java
void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
synchronized (this) {
ensureValidLocked();
nativeThemeApplyStyle(mObject, themePtr, resId, force);
}
}
static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
jint resid, jboolean force) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
CHECK(theme->GetAssetManager() == &(*assetmanager));
(void) assetmanager;
theme->ApplyStyle(static_cast<uint32_t>(resid), force);
}
可以看到在設(shè)置Theme過程中,核心方法是Theme的ApplyStyle。
Theme的ApplyStyle
文件:/frameworks/base/libs/androidfw/AssetManager2.cpp
bool Theme::ApplyStyle(uint32_t resid, bool force) {
const ResolvedBag* bag = asset_manager_->GetBag(resid);
type_spec_flags_ |= bag->type_spec_flags;
int last_type_idx = -1;
int last_package_idx = -1;
Package* last_package = nullptr;
ThemeType* last_type = nullptr;
using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>;
const auto bag_iter_end = reverse_bag_iterator(begin(bag));
for (auto bag_iter = reverse_bag_iterator(end(bag)); bag_iter != bag_iter_end; ++bag_iter) {
const uint32_t attr_resid = bag_iter->key;
const int package_idx = get_package_id(attr_resid);
const int type_idx = get_type_id(attr_resid);
const int entry_idx = get_entry_id(attr_resid);
if (last_package_idx != package_idx) {
std::unique_ptr<Package>& package = packages_[package_idx];
if (package == nullptr) {
package.reset(new Package());
}
last_package_idx = package_idx;
last_package = package.get();
last_type_idx = -1;
}
if (last_type_idx != type_idx) {
util::unique_cptr<ThemeType>& type = last_package->types[type_idx];
if (type == nullptr) {
type.reset(reinterpret_cast<ThemeType*>(
calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1)));
type->entry_count = entry_idx + 1;
} else if (entry_idx >= type->entry_count) {
const int new_count = entry_idx + 1;
type.reset(reinterpret_cast<ThemeType*>(
realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry)))));
memset(type->entries + type->entry_count, 0,
(new_count - type->entry_count) * sizeof(ThemeEntry));
type->entry_count = new_count;
}
last_type_idx = type_idx;
last_type = type.get();
}
ThemeEntry& entry = last_type->entries[entry_idx];
if (force || (entry.value.dataType == Res_value::TYPE_NULL &&
entry.value.data != Res_value::DATA_NULL_EMPTY)) {
entry.cookie = bag_iter->cookie;
entry.type_spec_flags |= bag->type_spec_flags;
entry.value = bag_iter->value;
}
}
return true;
}
- 1.首先先通過resId找到對應(yīng)的bag,通過Bag獲取對應(yīng)的packageID,typeID,entryID
- 2.檢查當(dāng)前Theme對象中,之前是否緩存了當(dāng)前要查找的packageID對應(yīng)的Package對象,沒有則繼續(xù)啊檢查是否包含對應(yīng)packageID的package對象。都沒有就會創(chuàng)建一個新的Package對象。
- 3.檢查當(dāng)前的package對象中,是否緩存了當(dāng)前要查找typeID,是否包含對應(yīng)typeID的ThemeType對象,沒有則創(chuàng)建一個新的(大小為entry的大小*typeID),如果typeID超過了當(dāng)前ThemeType的容量,則擴容。
- 4.根據(jù)entryID,查找ThemeType的entries對應(yīng)index的ThemeEntry,最后把bag中的數(shù)據(jù)賦值進來。
因此在這里面有一個核心方法GetBag。
AssetManager2的GetBag
const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) {
auto cached_iter = cached_bags_.find(resid);
if (cached_iter != cached_bags_.end()) {
return cached_iter->second.get();
}
FindEntryResult entry;
ApkAssetsCookie cookie =
FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
if (cookie == kInvalidCookie) {
return nullptr;
}
if (dtohs(entry.entry->size) < sizeof(ResTable_map_entry) ||
(dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) == 0) {
// Not a bag, nothing to do.
return nullptr;
}
const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry.entry);
const ResTable_map* map_entry =
reinterpret_cast<const ResTable_map*>(reinterpret_cast<const uint8_t*>(map) + map->size);
const ResTable_map* const map_entry_end = map_entry + dtohl(map->count);
child_resids.push_back(resid);
uint32_t parent_resid = dtohl(map->parent.ident);
if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid)
!= child_resids.end()) {
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))))};
ResolvedBag::Entry* new_entry = new_bag->entries;
for (; map_entry != map_entry_end; ++map_entry) {
uint32_t new_key = dtohl(map_entry->name.ident);
new_entry->cookie = cookie;
new_entry->key = new_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
new_entry->value.copyFrom_dtoh(map_entry->value);
status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
++new_entry;
}
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;
}
entry.dynamic_ref_table->lookupResourceId(&parent_resid);
const ResolvedBag* parent_bag = GetBag(parent_resid, child_resids);
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;
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) {
new_entry->cookie = cookie;
new_entry->key = child_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
new_entry->value.copyFrom_dtoh(map_entry->value);
status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
++map_entry;
} else {
// Take the parent entry as-is.
memcpy(new_entry, parent_entry, sizeof(*new_entry));
}
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;
}
while (map_entry != map_entry_end) {
uint32_t new_key = dtohl(map_entry->name.ident);
new_entry->cookie = cookie;
new_entry->key = new_key;
new_entry->key_pool = nullptr;
new_entry->type_pool = nullptr;
new_entry->value.copyFrom_dtoh(map_entry->value);
status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
++map_entry;
++new_entry;
}
if (parent_entry != parent_entry_end) {
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;
}
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)))));
}
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;
}
- 1.首先通過FindEntry找到對應(yīng)的FindEntryResult對象,里面包含著ResTable_entry,相關(guān)的配置,以及資源池中指向的字符串。
struct FindEntryResult {
//查找到的entry結(jié)果
const ResTable_entry* entry;
//配置
ResTable_config config;
uint32_t type_flags;
//動態(tài)映射表
const DynamicRefTable* dynamic_ref_table;
//entry對應(yīng)的類型名
StringPoolRef type_string_ref;
//entry名
StringPoolRef entry_string_ref;
};
- 2.如果當(dāng)前的ResTable_entry不是ResTable_map_entry對象則返回,通過大小的來確定。并且獲取后面ResTable_map對象。ResTable_map_entry的數(shù)據(jù)結(jié)構(gòu)如下:
struct ResTable_map_entry : public ResTable_entry
{
//指向父ResTable_map_entry的引用
ResTable_ref parent;
// 后面ResTable_map的數(shù)量
uint32_t count;
};
ResTable_map的位置計算如下:
ResTable_map = ResTable_map_entry的起點+ResTable_map_entry大小
剛好在ResTable_map_entry后面。ResTable_map的數(shù)據(jù)結(jié)構(gòu)如下:
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才是真正持有Res_value的對象。ResTable_map里面包含著鍵值對。分別指的是當(dāng)前資源當(dāng)前的命名以及資源中的值。
- 3.接下來分為兩種情況,一種是ResTable_map_entry不包含父資源或者已經(jīng)在原來child_resids找到了,一種的是包含父資源。
1 .當(dāng)不包含父資源的時候,則循環(huán)ResTable_map中的引用,把里面的包含的資源真實數(shù)據(jù),cookie都設(shè)置到ResolveBag的Entry指針中。這樣ResolveBag就包含了當(dāng)前ResTable_entry中所有的數(shù)據(jù)。一般的一個ResolveBag就包含一個ResTable_map。
2 .當(dāng)包含父資源的時候,將會通過動態(tài)映射表去查找對應(yīng)的父資源的packageID,遞歸當(dāng)前的方法,找到所有父資源的數(shù)據(jù),獲取父ResolveBag。當(dāng)遇到每一個子資源和父資源沖突,則讓子資源覆蓋父資源。
這樣就把所有的父子資源壓縮到一起交給了ResolveBag中保管了,并且緩存到cached_bags_中。
查找當(dāng)前主題下的屬性的值常用方法
接下來,讓我們探索一下,當(dāng)我們編寫自定義View,以及自定義屬性時候常用三種在當(dāng)前主題下查找對應(yīng)的資源屬性中的值。
public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
}
TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
@NonNull int[] values,
@NonNull int[] attrs) {
synchronized (mKey) {
final int len = attrs.length;
if (values == null || len != values.length) {
throw new IllegalArgumentException(
"Base attribute values must the same length as attrs");
}
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
array.mTheme = wrapper;
array.mXml = null;
return array;
}
}
boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
synchronized (mKey) {
return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
}
}
而這兩個方法都會調(diào)用ThemeImpl中的下面方法。
TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
@NonNull int[] values,
@NonNull int[] attrs) {
synchronized (mKey) {
final int len = attrs.length;
if (values == null || len != values.length) {
throw new IllegalArgumentException(
"Base attribute values must the same length as attrs");
}
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
array.mTheme = wrapper;
array.mXml = null;
return array;
}
}
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
synchronized (mKey) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
array.mDataAddress, array.mIndicesAddress);
array.mTheme = wrapper;
array.mXml = parser;
return array;
}
}
其核心原理是十分相似。首先在ActivityThread的handleLaunch階段,會設(shè)置一個Theme。這個Theme就是ThemeImpl,同時會在native層中生成一個Theme對象。
最后分別調(diào)用AssetManager的resolveAttrs以及applyStyle方法。
void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
@Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
long outIndicesAddress) {
synchronized (this) {
nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
outIndicesAddress);
}
}
boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
@Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
@NonNull int[] outIndices) {
synchronized (this) {
return nativeResolveAttrs(mObject,
themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices);
}
}
boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
boolean resolveRefs) {
Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
ensureValidLocked();
final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue,
resolveRefs);
if (cookie <= 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
obtainStyledAttributes 工作原理
static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
CHECK(theme->GetAssetManager() == &(*assetmanager));
(void) assetmanager;
ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr);
uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr);
jsize attrs_len = env->GetArrayLength(java_attrs);
jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
...
ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr),
static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len,
out_values, out_indices);
env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
}
核心方法調(diào)用ApplyStyle
文件;/frameworks/base/libs/androidfw/AttributeResolution.cpp
void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
uint32_t* out_values, uint32_t* out_indices) {
AssetManager2* assetmanager = theme->GetAssetManager();
ResTable_config config;
Res_value value;
int indices_idx = 0;
uint32_t def_style_flags = 0u;
if (def_style_attr != 0) {
Res_value value;
if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
def_style_resid = value.data;
}
}
}
// Retrieve the style resource ID associated with the current XML tag's style attribute.
uint32_t style_resid = 0u;
uint32_t style_flags = 0u;
if (xml_parser != nullptr) {
ssize_t idx = xml_parser->indexOfStyle();
if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
if (value.dataType == value.TYPE_ATTRIBUTE) {
// Resolve the attribute with out theme.
if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) {
value.dataType = Res_value::TYPE_NULL;
}
}
if (value.dataType == value.TYPE_REFERENCE) {
style_resid = value.data;
}
}
}
const ResolvedBag* default_style_bag = nullptr;
if (def_style_resid != 0) {
default_style_bag = assetmanager->GetBag(def_style_resid);
if (default_style_bag != nullptr) {
def_style_flags |= default_style_bag->type_spec_flags;
}
}
BagAttributeFinder def_style_attr_finder(default_style_bag);
const ResolvedBag* xml_style_bag = nullptr;
if (style_resid != 0) {
xml_style_bag = assetmanager->GetBag(style_resid);
if (xml_style_bag != nullptr) {
style_flags |= xml_style_bag->type_spec_flags;
}
}
BagAttributeFinder xml_style_attr_finder(xml_style_bag);
XmlAttributeFinder xml_attr_finder(xml_parser);
for (size_t ii = 0; ii < attrs_length; ii++) {
const uint32_t cur_ident = attrs[ii];
ApkAssetsCookie cookie = kInvalidCookie;
uint32_t type_set_flags = 0u;
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
config.density = 0;
const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
if (xml_attr_idx != xml_attr_finder.end()) {
xml_parser->getAttributeValue(xml_attr_idx, &value);
}
if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
if (entry != xml_style_attr_finder.end()) {
cookie = entry->cookie;
type_set_flags = style_flags;
value = entry->value;
}
}
if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
if (entry != def_style_attr_finder.end()) {
cookie = entry->cookie;
type_set_flags = def_style_flags;
value = entry->value;
}
}
uint32_t resid = 0u;
if (value.dataType != Res_value::TYPE_NULL) {
ApkAssetsCookie new_cookie =
theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
if (new_cookie != kInvalidCookie) {
cookie = new_cookie;
}
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
if (new_cookie != kInvalidCookie) {
new_cookie =
assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
if (new_cookie != kInvalidCookie) {
cookie = new_cookie;
}
}
}
if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
cookie = kInvalidCookie;
}
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
if (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) {
indices_idx++;
out_indices[indices_idx] = ii;
}
out_values += STYLE_NUM_ENTRIES;
}
out_indices[0] = indices_idx;
}
- 1.首先通過GetAttribute檢查當(dāng)前傳進來的默認的屬性,如果當(dāng)前傳進來了XML當(dāng)前塊的解析對象,則獲取style的位置之后,嘗試獲取style中的值,是引用則記錄當(dāng)前的id。如果是Attribute則通過GetAttribute獲取值。
- 2.如果默認的style的id不為0,則獲取styleID對應(yīng)的ResolveBag作為默認對象。
- 3.此時獲取從上面?zhèn)飨聛淼腶ttr引用指針,在這個情況一般是值R.styleable.xxx的一個數(shù)組,里面含有大量的屬性。開始循環(huán)傳下來的attr數(shù)組,逐一查找對應(yīng)數(shù)組中每一個資源值對應(yīng)的index。
- 4.如果xml_attr_finder找到對應(yīng)的index,則通過xml_parser->getAttributeValue(xml_attr_idx, &value);解析里面內(nèi)容,并且拷貝到outValue中。記住如果是引用,則會通過ResolveReference方法解包引用,通過GetResources找到真正的值。如果數(shù)據(jù)為空,則從默認的style中讀取。
能看到這個過程中有2個核心方法我們未曾接觸過,讓我們著重看看里面做了什么事情:
- GetAttribute
- xml_parser->getAttributeValue
GetAttribute 獲取屬性
ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
uint32_t* out_flags) const {
int cnt = 20;
uint32_t type_spec_flags = 0u;
do {
const int package_idx = get_package_id(resid);
const Package* package = packages_[package_idx].get();
if (package != nullptr) {
// The themes are constructed with a 1-based type ID, so no need to decrement here.
const int type_idx = get_type_id(resid);
const ThemeType* type = package->types[type_idx].get();
if (type != nullptr) {
const int entry_idx = get_entry_id(resid);
if (entry_idx < type->entry_count) {
const ThemeEntry& entry = type->entries[entry_idx];
type_spec_flags |= entry.type_spec_flags;
if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) {
if (cnt > 0) {
cnt--;
resid = entry.value.data;
continue;
}
return kInvalidCookie;
}
// @null is different than @empty.
if (entry.value.dataType == Res_value::TYPE_NULL &&
entry.value.data != Res_value::DATA_NULL_EMPTY) {
return kInvalidCookie;
}
*out_value = entry.value;
*out_flags = type_spec_flags;
return entry.cookie;
}
}
}
break;
} while (true);
return kInvalidCookie;
}
能看到實際上很簡單,在native層已經(jīng)保存了當(dāng)前主題中所有xml的映射關(guān)系,因此可以通過當(dāng)前Package,ThemeType找到對應(yīng)的ResTable_entry中的數(shù)據(jù)。因此我們可以得知,在obtainStyledAttributes中,設(shè)置默認的屬性不是什么都可以設(shè)置,需要設(shè)置Theme中有的才能正常運作。
getAttributeValue解析Xml數(shù)據(jù)塊中的屬性值
文件:/frameworks/base/libs/androidfw/ResourceTypes.cpp
ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const
{
if (mEventCode == START_TAG) {
const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
if (idx < dtohs(tag->attributeCount)) {
const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
(((const uint8_t*)tag)
+ dtohs(tag->attributeStart)
+ (dtohs(tag->attributeSize)*idx));
outValue->copyFrom_dtoh(attr->typedValue);
if (mTree.mDynamicRefTable != NULL &&
mTree.mDynamicRefTable->lookupResourceValue(outValue) != NO_ERROR) {
return BAD_TYPE;
}
return sizeof(Res_value);
}
}
return BAD_TYPE;
}
十分簡單,就是通過當(dāng)前保存的解析樹中ResXMLTree_attribute對應(yīng)index的屬性值。還有一個resolveAttribute的方法本質(zhì)上還是從ApplyStyle中查找方法。
到這里我們已經(jīng)理解了obtainStyledAttributes的工作流程,讓我們看看resolveAttributes。
resolveAttributes的工作原理
這個方法最終會調(diào)用native的ResolveAttrs方法。
文件:/frameworks/base/libs/androidfw/AttributeResolution.cpp
bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
AssetManager2* assetmanager = theme->GetAssetManager();
ResTable_config config;
Res_value value;
int indices_idx = 0;
// Load default style from attribute, if specified...
uint32_t def_style_flags = 0u;
if (def_style_attr != 0) {
Res_value value;
if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
def_style_res = value.data;
}
}
}
const ResolvedBag* default_style_bag = nullptr;
if (def_style_res != 0) {
default_style_bag = assetmanager->GetBag(def_style_res);
if (default_style_bag != nullptr) {
def_style_flags |= default_style_bag->type_spec_flags;
}
}
BagAttributeFinder def_style_attr_finder(default_style_bag);
for (size_t ii = 0; ii < attrs_length; ii++) {
const uint32_t cur_ident = attrs[ii];
ApkAssetsCookie cookie = kInvalidCookie;
uint32_t type_set_flags = 0;
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
config.density = 0;
if (src_values_length > 0 && src_values[ii] != 0) {
value.dataType = Res_value::TYPE_ATTRIBUTE;
value.data = src_values[ii];
} else {
const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident);
if (entry != def_style_attr_finder.end()) {
cookie = entry->cookie;
type_set_flags = def_style_flags;
value = entry->value;
}
}
uint32_t resid = 0;
if (value.dataType != Res_value::TYPE_NULL) {
ApkAssetsCookie new_cookie =
theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
if (new_cookie != kInvalidCookie) {
cookie = new_cookie;
}
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
// If we still don't have a value for this attribute, try to find it in the theme!
ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
if (new_cookie != kInvalidCookie) {
new_cookie =
assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
if (new_cookie != kInvalidCookie) {
cookie = new_cookie;
}
}
}
...
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
if (out_indices != nullptr &&
(value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) {
indices_idx++;
out_indices[indices_idx] = ii;
}
out_values += STYLE_NUM_ENTRIES;
}
if (out_indices != nullptr) {
out_indices[0] = indices_idx;
}
return true;
}
這里的邏輯和上面的obtainStyledAttributes十分相似。唯一不同的是,obtainStyledAttributes解析的是Xml中的屬性。而這里不需要解析Xml的屬性,而是直接通過BagAttributeFinder查找有沒有對應(yīng)的屬性。obtainStyledAttributes當(dāng)沒有在Xml中找到也是通過BagAttributeFinder去查找默認的屬性。而這個方法本質(zhì)上就是ResolvedBag這個數(shù)組指針,迭代查找Theme中所有的屬性中的值。
mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
resolveAttributes在Java層并沒有傳遞默認的style以及attr,因此獲取的是當(dāng)前values中index對應(yīng)的屬性值。我之前試著使用這個方法獲取的時候,也是出現(xiàn)了問題,找錯了方法,看了源碼之后才明白是怎么回事。
總結(jié)
總結(jié)資源查找的原理。對于AssetManager來說,資源大致分為兩類:
- 1.非Asset文件夾下的資源
- 2.Asset文件夾下的資源
- 3.查找Theme中的屬性
對于非Asset文件夾下的資源來說,查找過程一般遵循如下流程:
1.解析資源ID,根據(jù)packageID從package_groups中獲取到對應(yīng)的PackageGroup,接著獲取每一個Group當(dāng)中對應(yīng)的LoadPackage對象。
2.LoadPackage對象中保存著TypeSpec對象。這個對象保存著資源類型和資源類型內(nèi)容之間的映射關(guān)系。Android系統(tǒng)為了加速資源加載提前把當(dāng)前資源環(huán)境一直的資源另外放置在一個FilteredConfigGroup中。一旦發(fā)現(xiàn)是一致的環(huán)境則從這個快速通道進行查找,否則則進行全局的遍歷。最后通過GetEntryFromOffset找到偏移數(shù)組并且賦值到TypedValue中
3.最后就可以從TypedValue讀取數(shù)據(jù)。如果是布局這種大型文件,就會保存到native的Xml的解析器中,通過mmap的方式映射讀取內(nèi)存數(shù)據(jù)。
對于Asset文件夾下的文件來說,查找過程很簡單一般遵循如下步驟:
- 1.為路徑新增一個asset的路徑前綴。
- 2.把zip數(shù)據(jù)流交給AssetInputStream 這個Stream對象處理
對于Theme中的屬性,分為初始化Theme以及從Theme中查找2個步驟:
1.初始化Native下的Theme的時候,會通過ApplyStyle方法先解析當(dāng)前的資源結(jié)構(gòu)。通過FindEntry的方法和解析一個ResTable_entry對象出來.
2.拿到ResTable_entry之后,會通過起點加上該對象的大小能拿到對應(yīng)的ResTable_map對象,而這個對象中就保存著ResTable_value,也就是資源的真實內(nèi)容;接著循環(huán)從子資源一路向父資源查找所有的資源屬性,子資源將會覆蓋掉父資源并壓縮到ResloveBag中;最后保存到cached_bags_緩存中。
3.最后底層的Theme對象,將會層層緩存,根據(jù)packageid,typeid,緩存下來。并遍歷ResloveBag中所有的數(shù)據(jù)出處到Theme的entry數(shù)組中
4.當(dāng)進行查找時候,將會解析當(dāng)前Theme中保存下來的映射對象,并且返回到Java層。
后話
其實剛好這段時間在擺弄公司的基礎(chǔ)庫,抽離了一個ui公共庫。經(jīng)常接觸這些資源解析,資源查找,因此也運氣挺好的順手寫了這些東西,總結(jié)了之前的所用。
到這里,所有常規(guī)的資源查找方法就全部解析完畢。接下來,我會開啟Android渲染體系的文章系列。