前言
上一篇文章,聊到了資源管理中解析Package數據模塊中的LoadedPackage::Load方法開始解析Package數據塊。本文將會詳細解析Package數據塊的解析,以及AssetManager如何管理。接下來解析resource.arsc還是依照下面這幅圖進行解析:
如果遇到問題歡迎在這個地址下留言:http://www.lxweimin.com/p/02a2539890dc
正文
文件:/frameworks/base/libs/androidfw/LoadedArsc.cpp
LoadedPackage::Load有點長,我拆開兩部分聊。
解析Package數據包前的準備
const static int kAppPackageId = 0x7f;
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap,
bool system, bool load_as_shared_library) {
std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
//計算結構體的大小
constexpr size_t kMinPackageSize =
sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();
loaded_package->system_ = system;
loaded_package->package_id_ = dtohl(header->id);
if (loaded_package->package_id_ == 0 ||
(loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {
// Package ID of 0 means this is a shared library.
loaded_package->dynamic_ = true;
}
if (loaded_idmap != nullptr) {
// This is an overlay and so it needs to pretend to be the target package.
loaded_package->package_id_ = loaded_idmap->TargetPackageId();
loaded_package->overlay_ = true;
}
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
...
loaded_package->type_id_offset_ = static_cast<int>(type_id_offset);
}
util::ReadUtf16StringFromDevice(header->name, arraysize(header->name),
&loaded_package->package_name_);
...
}
這一段是為解析package數據塊的準備,首先解析Package數據塊頭部信息。實際上解析是下面這部分模塊:
首先取出當前的header的id并且獲取交換低高位作為packageId(0x7f000000交換高低位變成0x7f),如果當前的id是0x7f且打開了作為load_as_shared_library的標識位,或者id是0x00則作為動態資源加載,如果是第三方資源庫則id為0.
如果上面傳下來了loaded_idmap說明這部分的資源需要重新被覆蓋。最后設置loaded_idmap。
此時可以得知,一般的App應用中的apk包中packageID是0x7f
解析Package數據
std::unordered_map<int, std::unique_ptr<TypeSpecPtrBuilder>> type_builder_map;
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
const Chunk child_chunk = iter.Next();
switch (child_chunk.type()) {
case RES_STRING_POOL_TYPE: {
const uintptr_t pool_address =
reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
const uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
if (pool_address == header_address + dtohl(header->typeStrings)) {
// This string pool is the type string pool.
status_t err = loaded_package->type_string_pool_.setTo(
child_chunk.header<ResStringPool_header>(), child_chunk.size());
...
} else if (pool_address == header_address + dtohl(header->keyStrings)) {
// This string pool is the key string pool.
status_t err = loaded_package->key_string_pool_.setTo(
child_chunk.header<ResStringPool_header>(), child_chunk.size());
...
} else {
...
}
} break;
case RES_TABLE_TYPE_SPEC_TYPE: {
const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
...
// The data portion of this chunk contains entry_count 32bit entries,
// each one representing a set of flags.
// Here we only validate that the chunk is well formed.
const size_t entry_count = dtohl(type_spec->entryCount);
// There can only be 2^16 entries in a type, because that is the ID
// space for entries (EEEE) in the resource ID 0xPPTTEEEE.
....
// If this is an overlay, associate the mapping of this type to the target type
// from the IDMAP.
const IdmapEntry_header* idmap_entry_header = nullptr;
if (loaded_idmap != nullptr) {
idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
}
std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1];
if (builder_ptr == nullptr) {
builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
} else {
...
}
} break;
case RES_TABLE_TYPE_TYPE: {
const ResTable_type* type = child_chunk.header<ResTable_type, kResTableTypeMinSize>();
...
// Type chunks must be preceded by their TypeSpec chunks.
std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1];
if (builder_ptr != nullptr) {
builder_ptr->AddType(type);
} else {
...
}
} break;
case RES_TABLE_LIBRARY_TYPE: {
const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>();
...
loaded_package->dynamic_package_map_.reserve(dtohl(lib->count));
const ResTable_lib_entry* const entry_begin =
reinterpret_cast<const ResTable_lib_entry*>(child_chunk.data_ptr());
const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
std::string package_name;
util::ReadUtf16StringFromDevice(entry_iter->packageName,
arraysize(entry_iter->packageName), &package_name);
...
loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),
dtohl(entry_iter->packageId));
}
} break;
default:
...
break;
}
}
...
// Flatten and construct the TypeSpecs.
for (auto& entry : type_builder_map) {
uint8_t type_idx = static_cast<uint8_t>(entry.first);
TypeSpecPtr type_spec_ptr = entry.second->Build();
...
// We only add the type to the package if there is no IDMAP, or if the type is
// overlaying something.
if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
// If this is an overlay, insert it at the target type ID.
if (type_spec_ptr->idmap_entries != nullptr) {
type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
}
loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr);
}
}
return std::move(loaded_package);
上一篇文章稍微總結這部分源碼聊了什么,這本將拆開這幾個case看看里面究竟做了什么事情。
解析Package數據包中的字符串池子
case RES_STRING_POOL_TYPE: {
const uintptr_t pool_address =
reinterpret_cast<uintptr_t>(child_chunk.header<ResChunk_header>());
const uintptr_t header_address = reinterpret_cast<uintptr_t>(header);
if (pool_address == header_address + dtohl(header->typeStrings)) {
// This string pool is the type string pool.
status_t err = loaded_package->type_string_pool_.setTo(
child_chunk.header<ResStringPool_header>(), child_chunk.size());
...
} else if (pool_address == header_address + dtohl(header->keyStrings)) {
// This string pool is the key string pool.
status_t err = loaded_package->key_string_pool_.setTo(
child_chunk.header<ResStringPool_header>(), child_chunk.size());
...
} else {
...
}
} break;
在這個case中,實際上做的事情很簡單,解析的是下面這部分
和這張圖不一樣的是,在這個字符串資源池其實還有一個header,這里面疏漏了。這里面算法很簡單,如下:
資源類型字符串池地址 = ResTable_package.header + typeString (偏移量)
資源項名稱字符串池地址 = ResTable_package.header + keyStrings(偏移量)
最后通過這個地址和當前chunk的子chunk比較地址,看看和哪個相等,相等則賦值到對應的資源池。
至此,已經有三個全局資源池,global_string_pool_全局內容資源池,loaded_package->type_string_pool_ 資源類型字符串資源池,loaded_package->key_string_pool_資源項名稱字符串資源池。
解析資源類型數據
case RES_TABLE_TYPE_SPEC_TYPE: {
const ResTable_typeSpec* type_spec = child_chunk.header<ResTable_typeSpec>();
...
// The data portion of this chunk contains entry_count 32bit entries,
// each one representing a set of flags.
// Here we only validate that the chunk is well formed.
const size_t entry_count = dtohl(type_spec->entryCount);
// There can only be 2^16 entries in a type, because that is the ID
// space for entries (EEEE) in the resource ID 0xPPTTEEEE.
....
// If this is an overlay, associate the mapping of this type to the target type
// from the IDMAP.
const IdmapEntry_header* idmap_entry_header = nullptr;
if (loaded_idmap != nullptr) {
idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
}
std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1];
if (builder_ptr == nullptr) {
builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
} else {
...
}
} break;
此時解析的是下面這個數據塊:
首先來看看資源類型的結構體:
struct ResTable_typeSpec
{
struct ResChunk_header header;
// The type identifier this chunk is holding. Type IDs start
// at 1 (corresponding to the value of the type bits in a
// resource identifier). 0 is invalid.
uint8_t id;
// Must be 0.
uint8_t res0;
// Must be 0.
uint16_t res1;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
enum : uint32_t {
// Additional flag indicating an entry is public.
SPEC_PUBLIC = 0x40000000u,
// Additional flag indicating an entry is overlayable at runtime.
// Added in Android-P.
SPEC_OVERLAYABLE = 0x80000000u,
};
};
該結構體定義了每一個資源類型的id,以及可以配置的entryCount。id是逐一遞增,而entryCount的意思就是指該資源類型中,每一個資源項能跟著幾個配置。比如說,layout中可以跟著幾個layout-v21,v22等等,里面都包含著對應的具體布局文件值資源值。
如果傳下的loaded_idmap 不為空,則說明這個package需要覆蓋掉某個package的資源類型,就嘗試著通過id去找到IdmapEntry_header。做覆蓋準備。
此時就做了如下事情:
type_builder_map[typeSpec的id - 1] = 新建一個TypeSpecPtrBuilder(type_spec,IdmapEntry_header)
初步構建了每個資源類型和id之間的映射關系。
解析資源項
case RES_TABLE_TYPE_TYPE: {
const ResTable_type* type = child_chunk.header<ResTable_type, kResTableTypeMinSize>();
...
// Type chunks must be preceded by their TypeSpec chunks.
std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type->id - 1];
if (builder_ptr != nullptr) {
builder_ptr->AddType(type);
} else {
...
}
} break;
此時解析的是下面這個數據塊:
在上一節中解析每一個資源類型的時候構建了TypeSpecPtrBuilder對象。當沒遇到一個新的資源項時候,將會取出這個TypeSpecPtrBuilder,并且通過AddType到這個對象中。這樣就完成了id到資源類型到資源項的映射。
看看資源項的數據結構:
struct ResTable_type
{
struct ResChunk_header header;
enum {
NO_ENTRY = 0xFFFFFFFF
};
// The type identifier this chunk is holding. Type IDs start
// at 1 (corresponding to the value of the type bits in a
// resource identifier). 0 is invalid.
uint8_t id;
enum {
// If set, the entry is sparse, and encodes both the entry ID and offset into each entry,
// and a binary search is used to find the key. Only available on platforms >= O.
// Mark any types that use this with a v26 qualifier to prevent runtime issues on older
// platforms.
FLAG_SPARSE = 0x01,
};
uint8_t flags;
// Must be 0.
uint16_t reserved;
// Number of uint32_t entry indices that follow.
uint32_t entryCount;
// Offset from header where ResTable_entry data starts.
uint32_t entriesStart;
// Configuration this collection of entries is designed for. This must always be last.
ResTable_config config;
};
能看到每一個資源項里面包含了一個頭部,一個配置(語言環境),還有entry的偏移量,entry是指什么呢?
這個entry就是我們編程讀取到的數據。
最后讓我們看看,TypeSpecPtrBuilder
class TypeSpecPtrBuilder {
public:
explicit TypeSpecPtrBuilder(const ResTable_typeSpec* header,
const IdmapEntry_header* idmap_header)
: header_(header), idmap_header_(idmap_header) {
}
void AddType(const ResTable_type* type) {
types_.push_back(type);
}
TypeSpecPtr Build() {
// Check for overflow.
using ElementType = const ResTable_type*;
if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
types_.size()) {
return {};
}
TypeSpec* type_spec =
(TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
type_spec->type_spec = header_;
type_spec->idmap_entries = idmap_header_;
type_spec->type_count = types_.size();
memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
return TypeSpecPtr(type_spec);
}
private:
DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
const ResTable_typeSpec* header_;
const IdmapEntry_header* idmap_header_;
std::vector<const ResTable_type*> types_;
};
能看到這個數據類型很簡單。里面保存了ResTable_typeSpec對應的header,需要覆蓋的idmap_header_,以及添加進來數據項。
解析第三方資源庫資源(特指資源共享庫)
case RES_TABLE_LIBRARY_TYPE: {
const ResTable_lib_header* lib = child_chunk.header<ResTable_lib_header>();
...
loaded_package->dynamic_package_map_.reserve(dtohl(lib->count));
const ResTable_lib_entry* const entry_begin =
reinterpret_cast<const ResTable_lib_entry*>(child_chunk.data_ptr());
const ResTable_lib_entry* const entry_end = entry_begin + dtohl(lib->count);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
std::string package_name;
util::ReadUtf16StringFromDevice(entry_iter->packageName,
arraysize(entry_iter->packageName), &package_name);
...
loaded_package->dynamic_package_map_.emplace_back(std::move(package_name),
dtohl(entry_iter->packageId));
}
} break;
這里也很簡單,實際上就是把每一個ResTable_lib_entry,添加到loaded_package的dynamic_package_map_中管理。而ResTable_lib_entry數據也很簡單,只是記錄了這個資源隸屬于那個package的id以及name
{
// The package-id this shared library was assigned at build time.
// We use a uint32 to keep the structure aligned on a uint32 boundary.
uint32_t packageId;
// The package name of the shared library. \0 terminated.
uint16_t packageName[128];
};
構建LoadPackage中的映射關系
// Flatten and construct the TypeSpecs.
for (auto& entry : type_builder_map) {
uint8_t type_idx = static_cast<uint8_t>(entry.first);
TypeSpecPtr type_spec_ptr = entry.second->Build();
...
// We only add the type to the package if there is no IDMAP, or if the type is
// overlaying something.
if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
// If this is an overlay, insert it at the target type ID.
if (type_spec_ptr->idmap_entries != nullptr) {
type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
}
loaded_package->type_specs_.editItemAt(type_idx) = std::move(type_spec_ptr);
}
}
return std::move(loaded_package);
能看到,此時將會把type_builder_map中緩存的每一項數據都構建成TypeSpecPtr 對象,并且根據當前的id-1保存起來。
因此LoadPackage中就有了所有資源的映射關系。提一句,為什么第一個資源目錄anim typeid為1了吧。這是為了讓下層計算可以從下標為0開始。
小結
在整個AssetManager初始化體系中,所有的字符串資源保存在三個字符串資源池中:
- 1.global_string_pool_全局內容資源池
- 2.loaded_package->type_string_pool_ 資源類型字符串資源池
- 3.loaded_package->key_string_pool_資源項名稱字符串資源池。
接下來就會保存package數據塊的數據。所有的package數據塊都保存到loadedPackage對象中,該對象保存著所有的TypeSpec對象,這個對象就是一個資源類型,而這個TypeSpec對象中保存著大量的ResTable_type,這個對象只是用用當前具體資源entryID,還沒有具體的數據。還保存著一個提供給第三方資源庫的動態映射表
回到ApkAsset
思路離開的有點遠了,回顧一下,上文中所有的事情都是在NativeLoad完成的事情,而上述過程僅僅只是填充ApkAssets對象中loaded_arsc_對象做的事情。
完成了這個事情之后就會回到該方法,把native對象地址回傳給java層。
文件:/frameworks/base/core/java/android/content/res/ApkAssets.java
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
throws IOException {
Preconditions.checkNotNull(path, "path");
mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
接下來會通過nativeGetStringBlock再起一次回去native層,獲取native的StringBlock對象。
文件:/frameworks/base/core/jni/android_content_res_ApkAssets.cpp
static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
inline const ResStringPool* GetStringPool() const {
return &global_string_pool_;
}
能看到此時StringBlock獲取的是從resource.arsc的全局字符串資源池。內含著所有資源具體的值。
此時ApkAssets就持有兩個native對象,一個是native層對應的ApkAssets以及字符串資源池。
AssetManager的創建
上文就知道,AssetManager的創建實際上是不斷的添加ApkAssets對象到builder對象中,最后調用build創建。
public static class Builder {
private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
public Builder addApkAssets(ApkAssets apkAssets) {
mUserApkAssets.add(apkAssets);
return this;
}
public AssetManager build() {
// Retrieving the system ApkAssets forces their creation as well.
final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();
final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);
final int userApkAssetCount = mUserApkAssets.size();
for (int i = 0; i < userApkAssetCount; i++) {
apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
}
// Calling this constructor prevents creation of system ApkAssets, which we took care
// of in this Builder.
final AssetManager assetManager = new AssetManager(false /*sentinel*/);
assetManager.mApkAssets = apkAssets;
AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
false /*invalidateCaches*/);
return assetManager;
}
}
在build方法中,可以看到整個ApkAssets被劃分為兩類,一類是System的,一類是應用App的。這兩類ApkAssets都會被AssetManager 持有,并且通過nativeSetApkAssets設置到native層。
獲取System資源包
private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
static AssetManager sSystem = null;
public static AssetManager getSystem() {
synchronized (sSync) {
createSystemAssetsInZygoteLocked();
return sSystem;
}
}
private static void createSystemAssetsInZygoteLocked() {
if (sSystem != null) {
return;
}
// Make sure that all IDMAPs are up to date.
nativeVerifySystemIdmaps();
try {
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
loadStaticRuntimeOverlays(apkAssets);
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
sSystem = new AssetManager(true /*sentinel*/);
sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
} catch (IOException e) {
throw new IllegalStateException("Failed to create system AssetManager", e);
}
}
能看到此時會先構建一個靜態的AssetManager,這個AssetManager只管理一個資源包:/system/framework/framework-res.apk。而且還好根據/data/resource-cache/overlays.list的復寫資源文件,把需要重疊的資源覆蓋在系統apk上。
打開其中的resource.arsc文件,發現packageID和應用的0x7f不一樣,是0x01
AssetManager的構建
private AssetManager(boolean sentinel) {
mObject = nativeCreate();
...
}
在構造函數中只做了一件事情,通過nativeCreate創建native下的GuardedAssetManager對象。
struct GuardedAssetManager : public ::AAssetManager {
Guarded<AssetManager2> guarded_assetmanager;
};
static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
// AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and
// AssetManager2 in a contiguous block (GuardedAssetManager).
return reinterpret_cast<jlong>(new GuardedAssetManager());
}
這本質上是一個包裹著AssetManager2的AAssetManager 對象。Guarded有點像智能指針,不過這是讓對象自己持有有mutex,自己的操作保持原子性。
nativeSetApkAssets設置所有的ApkAssets給AssetManager2對象
文件:/frameworks/base/libs/androidfw/AssetManager2.cpp
Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) {
if (assetmanager == nullptr) {
return nullptr;
}
return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager;
}
static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
}
static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
jobjectArray apk_assets_array, jboolean invalidate_caches) {
const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
std::vector<const ApkAssets*> apk_assets;
apk_assets.reserve(apk_assets_len);
for (jsize i = 0; i < apk_assets_len; i++) {
jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
if (obj == nullptr) {
std::string msg = StringPrintf("ApkAssets at index %d is null", i);
jniThrowNullPointerException(env, msg.c_str());
return;
}
jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
if (env->ExceptionCheck()) {
return;
}
apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
}
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
assetmanager->SetApkAssets(apk_assets, invalidate_caches);
}
實際上邏輯很簡單,就是把Java的數組轉化為vector設置到AssetManager2.
AssetManager2構建內存中的資源表
bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
bool invalidate_caches) {
apk_assets_ = apk_assets;
BuildDynamicRefTable();
RebuildFilterList();
if (invalidate_caches) {
InvalidateCaches(static_cast<uint32_t>(-1));
}
return true;
}
從方法中可以得知整個AssetManager2會在內存中構建一個動態的資源表:
- 1.BuildDynamicRefTable構建動態的資源引用表
- 2.RebuildFilterList 構建過濾后的配置列表
- 3.InvalidateCaches刷新緩存
構建動態的資源引用表
void AssetManager2::BuildDynamicRefTable() {
package_groups_.clear();
package_ids_.fill(0xff);
// 0x01 is reserved for the android package.
int next_package_id = 0x02;
const size_t apk_assets_count = apk_assets_.size();
for (size_t i = 0; i < apk_assets_count; i++) {
const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
// Get the package ID or assign one if a shared library.
int package_id;
if (package->IsDynamic()) {
package_id = next_package_id++;
} else {
package_id = package->GetPackageId();
}
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
package_groups_.push_back({});
DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;
ref_table.mAssignedPackageId = package_id;
ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// Add the package name -> build time ID mappings.
for (const DynamicPackageEntry& entry : package->GetDynamicPackageMap()) {
String16 package_name(entry.package_name.c_str(), entry.package_name.size());
package_group->dynamic_ref_table.mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
}
}
// Now assign the runtime IDs so that we have a build-time to runtime ID map.
const auto package_groups_end = package_groups_.end();
for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
iter->dynamic_ref_table.mAssignedPackageId);
}
}
}
為什么需要構建一個動態的資源映射表?在原本的LoadArsc對象中已經構建了幾乎所有資源之間的關系。
但是有一個問題就出現,這個第三方資源的packageID編譯到這個位置的時候,實際上是根據編譯順序按順序加載并且遞增設置packageID。從第一個雙重循環看來,我們運行中還有一個packageID,是根據加載到內存的順序。
這樣就出現一個很大的問題?假如有一個第三方資源庫是0x03的packageId,此時加載順序是第1個,這樣在內存中對應的packageID就是0x02(0x01永遠給系統),這樣就會找錯對象。
因此第二個循環就是為了解決這個問題。
- 1.雙重循環做的事情實際上是收集所有保存在LoadArsc對象中的所有的package放到package_group中,并且為每一個index設置一個cookie,這個cookie本質上是一個int類型,隨著package的增大而增加。緊接著,為每一個動態資源庫加載自己的packageId。并且設置到DynamicPackageEntry的mEntries中。
- 2.獲取package_group中所有的數據,循環所有的package_group,并且調用addMapping:
status_t DynamicRefTable::addMapping(const String16& packageName, uint8_t packageId)
{
ssize_t index = mEntries.indexOfKey(packageName);
if (index < 0) {
return UNKNOWN_ERROR;
}
mLookupTable[mEntries.valueAt(index)] = packageId;
return NO_ERROR;
}
從上面能知道,mEntries保存的是編譯時packageID,mLookupTable則保存的是運行時id。這樣就能正確的通過運行時id找到編譯時id。
RebuildFilterList 構建過濾后的entry列表
void AssetManager2::RebuildFilterList() {
for (PackageGroup& group : package_groups_) {
for (ConfiguredPackage& impl : group.packages_) {
// Destroy it.
impl.filtered_configs_.~ByteBucketArray();
// Re-create it.
new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
// Create the filters here.
impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
const auto iter_end = spec->types + spec->type_count;
for (auto iter = spec->types; iter != iter_end; ++iter) {
ResTable_config this_config;
this_config.copyFromDtoH((*iter)->config);
if (this_config.match(configuration_)) {
group.configurations.push_back(this_config);
group.types.push_back(*iter);
}
}
});
}
}
}
TypeSpec這個對象就是生成ApkAssets的時候保存著所有資源映射關系。
這個方法就是通過循環篩選當前的config(如語言環境,sim卡環境)一致的config,有選擇性的獲取ResTable_type(資源項)。
這樣我們就能把所有的關系都映射到package_groups_對象中。
清除緩存所有的資源id
void AssetManager2::InvalidateCaches(uint32_t diff) {
if (diff == 0xffffffffu) {
// Everything must go.
cached_bags_.clear();
return;
}
for (auto iter = cached_bags_.cbegin(); iter != cached_bags_.cend();) {
if (diff & iter->second->type_spec_flags) {
iter = cached_bags_.erase(iter);
} else {
++iter;
}
}
}
cached_bags_實際上緩存著過去生成過資源id,如果需要則會清除,一般這種情況如AssetManager配置發生變化都會清除一下避免干擾cached_bags_。
經過著三個步驟之后,native層AssetManager就變相的通過package_group持有apk中資源映射關系。
總結
限于篇幅的原因,下一篇文章將會和大家剖析Android是如何通過初始化好的資源體系,進行資源的查找。你將會看到,本文還沒有使用過的ResTable_entry以及保存著真實數據的Res_Value是如何在資源查找中運作。
首先先上一副,囊括Java層和native層時序圖:
流程很長,也并不是很全,只是照顧到了主干。可以得知,在整個流程在頻繁的和native層不斷交流。僅僅依靠時序圖,可能總結起來是不夠好。
在這里我們可以得知如下信息:
AssetManager 在Java層會控制著ApkAsset。相對的AssetManager會對應著native層的AssetManager2,而AssetManager2控制著native層的ApkAsset對象。
換句話說,ApkAsset就是資源文件夾單位,而AssetManager只會控制到這個粒度。同時ApkAsset中存在四個十分重要的數據結構:
- resources_asset_ 象征著一個本質上是resource.arsc zip資源FileMap的Asset
- loaded_arsc_ 實際上是一個LoadedArsc,這個是resource.arsc解析資源后生成的映射關系對象。
LoadedArsc也有2個很重要的數據結構:
- global_string_pool_ 全局字符串資源池
- LoadedPackage package數據對象
LoadedPackage里面有著大量的資源對象相關信息,以及真實數據,其中也包含幾個很重要的數據結構:
- type_string_pool_ 資源類型字符串,如layout,menu,anim這些文件夾對應的名字
- key_string_pool_ 資源項字符串,資源對應的名字
- type_specs_ 里面保存著所有資源類型和資源項的映射關系
- dynamic_package_map_ 是為了處理第三方資源庫編譯的packgeID和運行時ID沖突而構建的2次映射,但是解決沖突不是在這里解決
ApkAsset有了這些信息,才能夠根據resource.arsc 完整的構建出資源之間的關系。
當然,僅僅又這些還不足,當ApkAsset設置到AssetManager2中的時候,AssetManager2為了更加快速,準確的加載內存做了如下努力:
- 1.保存著多個PackageGroup對象(內含ConfiguredPackage),里面包含著所有package數據塊。
- 2.構建動態資源表,放在package_group中,為了解決packageID運行時和編譯時沖突問題
- 3.提前篩選出符合當前環境的資源配置到FilteredConfigGroup,為了可以快速訪問。
-
4.緩存已經訪問過的BagID,也就是完整的資源ID。
AssetManager2的構成.png
所以,才叫Asset的Manager。同時我們能夠看到,在整個流程中,資源的解析流程將會以resource.arsc為引導,解析整個Apk資源。但是本質上還是zip解壓縮獲取對應的數據塊,只有訪問這些zipentry才能真正的訪問數據。當然,相關的字符串會集中控制在三個字符串緩存池中,如果遇到想要相應獲取,可以從這幾個緩存池對應的index獲取。
那么,我們繼續接著上一次的緩存話題,看看Android系統為了讀取的效率又作出什么努力,這里繼續總結一下整個資源的緩存情況:
- 1.activityResources 一個面向Resources弱引用的ArrayList
- 2.以ResourcesKey為key,ResourcesImpl的弱引用為value的Map緩存。
- 3.ApkAssets在內存中也有一層緩存,緩存拆成兩部分,mLoadedApkAssets已經加載的活躍ApkAssets,mCacheApkAssets已經加載了但是不活躍的ApkAssets
- 4.在ApkAsset保存著三個全局字符串資源池子,提供快速查找,對應到Java層的對象一般為StringBlock
- 5.為了能夠快速查找符合當前環境配置的資源(屏幕密度,語言環境等),同樣在過濾構建資源階段,有一個FilteredConfigGroup對象,提供快速查找。
-
6.緩存BagID
Android資源體系的緩存.png
分析資源管理系統,可以總結出什么Android性能優化結論呢?
1.包體積的優化,我們可以通過混淆資源文件,是的包體積變小。為什么呢?因為通過資源的混淆,就可以減少resource.arsc中字符串資源池的大小,從而縮小資源大小。
2.資源管理系統查找資源本質上是一個比較耗時的過程,因此Android系統做了6層緩存。保證資源的可以相對快速的查找。而這也是為什么在如果使用方法卡頓檢測第一次應喲啟動的時候,經常會報告資源解析方法卡頓的問題。解決的方案是打開一個線程池適當的解析提前解析資源。
3.同樣閱讀這段源碼之后,我們同樣能夠理解為什么各個插件化,熱修復只要涉及到資源的修復,就必須重新更新StringBlock。以前我沒有解釋,現在應該明白StringBlock里面保存著全局字符串資源池,如果修復之后不及時重新更新資源池,就是出現資源查找異常。當然Tinker里面所說的"系統資源提前加載需要清除,否則導致異常"話處理思路結果是正確的,但是出現錯誤的根本原因倒是出分析錯了。
4.當然,我們從這個過程中,其實可以察覺到其實整個Android資源體系其實可以進一步優化的:1.asset等資源文件并沒有壓縮,我們拿出來的其實就是apk中asset文件夾對應的ZipEntry。其實我們可以自己進行一次壓縮,拿到數據流之后進一步解壓縮。不過只是一種用時間來替代空間的策略罷了。2.在整個過程中字符串資源池保存的是完整的資源,其實我們可以用哈夫曼編碼進一步壓縮字符串資源池中的數據,當然這樣就需要入侵到編譯流程中,現在的我還沒有這種水平。
下一篇文章是資源管理系統最后一篇,探索一下Android的資源是如何查找的。