Android與資源管理相關的類Resouces和AssetManager很有必要清楚他們的創建過程。
與資源查找與加載操作相關的類
資源查找與加載主要是靠Android資源管理框架來完成的,而Android資源管理框架實際是由Resources和AssetManager兩個類來實現的。
其中,Resources類可以根據ID來查找資源,而AssetManager類根據文件名來查找資源。
事實上,從資源查找的過程來看,它們可以歸結為兩大類。第一類資源是不對應有文件的,而第二類資源是對應有文件的,例如,字符串資源是直接編譯在resources.arsc文件中的,而界面布局資源是在APK包里面是對應的單獨的文件的。
如果一個資源ID對應的是一個文件,那么Resources類是先根據ID來找到資源文件名稱,然后再將該文件名稱交給AssetManager類來打開對應的文件的。基本流程如下圖:
而且這兩個類中都有相應的緩存機制,用來緩存資源,以便下次在使用的時候,不需要在查找與加載。
App的Resources對象創建過程
既然這兩個類負責管理Android的資源,那么接下就要搞清楚這兩個類的對象在app中的創建過程以及何時創建。
Resources中會包含一個AssetManager對象,先重點關注Resources對象的創建過程,先看一張整體時序圖:
其中在LoadedApk中會緩存創建好的resources對象。,而創建context時,LoadedApk是同一個,所以在同一應用中不同的ContextImpl獲取到的是同一套資源。
ActivityThread類的成員變量mActiveResources指向的是一個HashMap。這個HashMap用來維護在當前應用程序進程中加載的每一個Apk文件及其對應的Resources對象的對應關系。
也就是說,給定一個Apk文件路徑,ActivityThread類的成員函數getTopLevelResources可以在成員變量mActiveResources中檢查是否存在一個對應的Resources對象。如果不存在,那么就會新建一個,并且保存在ActivityThread類的成員變量mActiveResources中。
Context中提供了getResources()方法用來獲取resources對象,所以在Activity中可以方便的獲取該對象:
Resources res = getResources();
Activity中的context實際是對ComtextImpl的封裝,所以最終是通過ContextImpl.getResources()獲取resources對象的:
public Resources getResources() {
return mResources;
}
而mResources是ContextImpl的一個屬性成員。且mResources是在ContextImpl的構造方法中被初始化的。
看看ContextImpl的構造方法:
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
..............
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
// 不會走此分支,因為6.0中還不支持多屏顯示,雖然已經有不少相關代碼了,7.0以及正式支持多屏操作了
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
......
}
}
mResources = resources;
..........
從ContextImpl構造方法中發現,通過傳入的LoadedApk對象的getResources()方法獲取Resources對象:
public Resources getResources(ActivityThread mainThread) {
// 緩存機制,如果LoadedApk中的mResources已經初始化則直接返回,
// 否則通過ActivityThread創建resources對象
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
LoadedApk.getResources()方法中首先判斷其mResources是否為null,為null時又是調用ActivityThread.getTopLevelResources()方法來獲取Resources對象的。
這里要說明一下,LoadedApk類有兩個構造方法:一個是給 system app使用的,一個是給普通app使用的。對于給普通app使用的構造方法中并沒有初始化mResources的值,在給system app的LoadedApk使用的構造方法中是初始化mResources值為Resources.getSystem()。
這里我們只關心普通app的LoadedApk對象創建時時沒有初始化mResources對象的。然后當第一次調用LoadedApk對象的getResources()方法時,便會通過調用下面的方法創建一個Resources對象,并緩存起來,以后再通過LoadedApk.getResources()獲取時,不需要重新創建Resources對象了,直接返回之前創建的即可。
/**
* Creates the top level resources for the given package.
*/
Resources getTopLevelResources(
String resDir,//app資源文件夾路徑,實際上是apk文件的路徑,如/data/app/包名/base.apk
String[] splitResDirs, //針對一個app由多個apk組成(將原本一個apk切片為若干apk)時,每個子apk中的資源文件夾
String[] overlayDirs,
String[] libDirs, //app依賴的共享jar/apk路徑
int displayId,
Configuration overrideConfiguration,
LoadedApk pkgInfo //代表運行的app
) {
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
}
ActivityThread.getTopLevelResources()方法又是通過ResourcesManager類的getTopLevelResources()方法創建Resources對象的:
/**
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
* @param splitResDirs split resource directories.
* @param overlayDirs the resource overlay directories.
* @param libDirs the shared library resource dirs this app references.
* @param displayId display Id.
* @param overrideConfiguration override configurations.
* @param compatInfo the compatibility info. Must not be null.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
....................
// 以apk路徑為參數創建key
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
// 檢查該apk對應的resources對象是否已經存在
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale
+ " key=" + key + " overrideConfig=" + overrideConfiguration);
return r;
}
}
// 創建一個AssetManager對象
AssetManager assets = new AssetManager();
// 將app中的資源路徑都加入到AssetManager對象中
if (resDir != null) {
if (assets.addAssetPath(resDir) == 0) {
return null;
}
}
if (splitResDirs != null) {
for (String splitResDir : splitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
return null;
}
}
}
if (overlayDirs != null) {
for (String idmapPath : overlayDirs) {
assets.addOverlayPath(idmapPath);
}
}
if (libDirs != null) {
for (String libDir : libDirs) {
// 僅僅選擇共享依賴中的apk,因為jar中不會有資源文件
if (libDir.endsWith(".apk")) {
if (assets.addAssetPath(libDir) == 0) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
}
}
}
}
................
r = new Resources(assets, dm, config, compatInfo);
...............
mActiveResources.put(key, new WeakReference<>(r));
return r
}
創建Resources對象時,會創建AssetManager對象并向其添加app資源路徑的過程。
現在可以回答何時創建Resources對象了:
當普通app對應的LoadedApk對象第一次調用LoadedApk.getResources()方法時,由于LoadedApk中還沒有緩存,會創建這個對象并緩存。后續再次調用LoadedApk.getResources()方法時,因為緩存了,不會再創建Resources對象。
創建ContextImpl對象時,并不一定會新創建Resources對象,通常一個運行著的app的Resources對象是只會創建一次的。并且緩存到LoadedApk對象中。一個運行著的app可以有多個Context,但是每個Context中都包含了同一個LoadedApk對象。
管理系統資源的Resources對象
Resources類中的關鍵數據成員有:
// 緩存zygote中預加載的資源
private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
= new LongSparseArray<>();
private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
sPreloadedColorStateLists = new LongSparseArray<>();
// 對系統Resources實例的引用
static Resources mSystem = null;
// drawable和ColorStateList的緩存
private final DrawableCache mDrawableCache = new DrawableCache(this);
private final DrawableCache mColorDrawableCache = new DrawableCache(this);
private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache =
new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
new ConfigurationBoundResourceCache<>(this);
// xml文件的緩存
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
// AssetManager實例的引用,很關鍵.
final AssetManager mAssets;
對于上述static類型的關鍵成員,在內存中只存在一份,所以一定要知道這些static成員是何時何地被初始化的.
以mSystem為例,如下所示:
在ZygoteInit的preloadResources()方法中初始化的mSystem值,也就是說mSystem是由Zygote進程初始化的,那么由其孵化的App進程肯定就繼承了這個東東了.
mSystem主要負責預加載Android系統本身提供的資源(framework-res.apk).加載完之后的資源存放在其他static成員變量中,以后App可以通過他們訪問系統資源.
framework-res.apk系統資源在zygote進程啟動時被加載的只是其中的一部分,不是加載所有資源。會加載的資源是在Framework里的res/values/arrays.xml中定義的,例如Leaner等布局資源。
對于那些非“預加載”的系統資源則認為不會被緩沖到靜態列表變量中,在這種情況下,多個應用進程如果需要一個非預裝載的資源,則會在各自的進程中保持一個資源的緩沖。
其實在App創建Resources對象時,會將framework-res.apk加入到其中,這樣能夠訪問沒有預加載的資源.
AssetManager對象的創建過程
通過前面的分析可知,Android系統中實際對資源的管理是AssetManager類.每個Resources對象都會關聯一個AssetManager對象,Resources將對資源的操作大多數委托給了AssetManager。
另外還會存在一個native層的AssetManager對象與java層的這個AssetManager對象相對應,而這個native層AssetManager對象在內存的地址存儲在java層的AssetManager.mObject中。所以在java層AssetManager的jni方法中可以快速找到它對應的native層的AssetManager對象。
創建普通app的AssetManager對象的過程如下所示:
創建AssetManager對象時,默認會把system/framework/framework-res.apk通過addAssetPath()方法加入到native層的AssetManager對象中。
AssetManager類有兩個構造方法:
一個是App創建Resources對象時,用到的public類型的構造方法:
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
一個是創建管理預加載的系統資源的Resources對象時,用到的private類型的構造方法:
private AssetManager(boolean isSystem) {
if (DEBUG_REFS) {
synchronized (this) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
}
init(true);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}
構造方法中都會調用init()方法,這是一個native方法:
源碼路徑:
frameworks/base/core/jni/android_util_AssetManager.cpp
frameworks/base/libs/androidfw/AssetManager.cpp
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}
在init()方法中創建一個native層中的AssetManager對象.
其中addDefaultAssets()方法將system/framework/framework-res.apk通過addAssetPath()方法加入到native層的AssetManager對象中.
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
String8 path(root);
// framework/framework-res.apk
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL);
}
addAssetPath()方法其實很簡單主要是將要加入的資源路徑加入到 AssetManager類的成員mAssetPaths中,當mAssetPaths中包含這個資源路徑時,不會再次加入。也就是說同一種資源是不會被重復加載的。
通過以上代碼可知在創建一個Java層的AssetManager對象時,會創建一個native層的AssetManager對象,并把system/framework/framework-res.apk加入到資源路徑集合mAssetPaths中去。
而且在前面介紹getTopLevelResources()時,也可以看到,當創建AssetManager對象之后,還會把app的資源路徑,也就是apk路徑通過ddAssetPath()方法加入到native層的AssetManager對象的mAssetPaths中去。
但是到這里為止,卻還沒有發現android對resources.arsc有任何操作,不要著急,繼續往下看。
在看Resources類的構造方法:
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
CompatibilityInfo compatInfo) {
mAssets = assets;
mMetrics.setToDefaults();
if (compatInfo != null) {
mCompatibilityInfo = compatInfo;
}
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
}
Resources類的構造函數首先將參數assets所指向的一個AssetManager對象保存在成員變量mAssets中,以便以后可以通過它來訪問應用程序的資源,接下來調用另外一個成員函數updateConfiguration來設置設備配置信息,最后調用參數assets所指向的一個AssetManager對象的成員函數ensureStringBlocks來創建字符串資源池。
AssetManager類的成員函數ensureStringBlocks首先檢查成員變量mStringBlocks的值是否等于null。如果等于null的話,那么就說明當前應用程序使用的資源表中的資源項值字符串資源池還沒有讀取出來,這時候就會調用另外一個成員函數makeStringBlocks來進行讀取。
整個過程大致過程如下所示:
在上圖中第九步中就會處理mAssetPaths路徑中apk內的resources.arsc,并把結果緩存起來。其中ResTable類負責資源管理框架中加載resources.arsc,一個ResTable可以管理app中所有的resources.arsc。
先來看java層AssetManager中的ensureStringBlocks()方法:
final void ensureStringBlocks() {
if (mStringBlocks == null) {
synchronized (this) {
if (mStringBlocks == null) {
makeStringBlocks(sSystem.mStringBlocks);
}
}
}
}
其中sSystem是AssetManager中的一個靜態屬性成員:
static AssetManager sSystem = null;
private StringBlock mStringBlocks[] = null;
前面介紹了,zygote啟動的時候,會初始化該變量,該變量指向的AssetManager對象是用來管理系統資源的。而且mStringBlocks會被初始化為系統資源中字符串值池的個數。因為系統資源為framework-res.apk,其內部只有一個resources.arsc,所以只有一個字符串資源值池。StringBlocks個數也可以理解為加載的resources.arsc的個數。
final void makeStringBlocks(StringBlock[] seed) {
// 系統預加載的resources.arsc的數量
final int seedNum = (seed != null) ? seed.length : 0;
// 這是個jni方法,該方法很重要
// 該方法中回去打開前面加入到native層AssetManager.mAssetPaths中的apk中的resources.arsc
// 至少返回2,系統資源+app自己的資源 的resources.arsc
// 返回的個數包含了seedNum
final int num = getStringBlockCount();
mStringBlocks = new StringBlock[num];
if (localLOGV) Log.v(TAG, "Making string blocks for " + this
+ ": " + num);
for (int i=0; i<num; i++) {
if (i < seedNum) {
// 系統預加載資源時,已經解析過framework-res.apk中的resources.arsc,并且存儲在AssetManager類中靜態變量sSystem中的mStringBlocks
// 所以直接 賦值
mStringBlocks[i] = seed[i];
} else {
// 除了系統預加載的之外,剩下的都是沒加載的,
// 所以 getNativeStringBlock
mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
}
}
}
這里要重點分析getStringBlockCount()和getNativeStringBlock()這兩個jni方法。
首先分析getStringBlockCount():
static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
{
// 得到與java層AssetManager對象對應的natvie層AssetManager對象
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
return am->getResources().getTableCount();
}
代碼很簡單,調用natvie層AssetManager的getResources()方法:
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}
先看看這個ResTable類中的屬性成員:
class ResTable
{
mutable Mutex mLock;
status_t mError;、
// 配置相關
ResTable_config mParams;
// 這個Header可以理解為resources.arsc中的資源索引表頭部+字符串值池
// 也就是resources.arsc中package之前的數據
// 那么這里的mHeaders是一個數組,也就是ResTable是可以包含多個resources.arsc.(至少兩個嘛)
// 所有的resources.arsc的package之前的數據都存儲在這數組里
Vector<Header*> mHeaders;
// 每一個resources.arsc里面的所有Pacakge形成一個PackageGroup
// 這個數組中存儲ResTable中加載的所有的resources.arsc中的PackageGroup
Vector<PackageGroup*> mPackageGroups;
// package ID 對應的pacakage所在的PackageGroup 在mPackageGroups數組中的索引
uint8_t mPackageMap[256];
uint8_t mNextPackageId;
}
再來看看Header:
struct ResTable::Header
{
Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
resourceIDMap(NULL), resourceIDMapSize(0) { }
~Header()
{
free(resourceIDMap);
}
// 所在的ResTable對象
const ResTable* const owner;
void* ownedData;
// 資源索引表頭部
const ResTable_header* header;
size_t size;
const uint8_t* dataEnd;
size_t index;
int32_t cookie;
// 用來操作字符串值池
ResStringPool values;
uint32_t* resourceIDMap;
size_t resourceIDMapSize;
};
再看PackageGroup:
struct ResTable::PackageGroup{
// 所在的Restable對象
const ResTable* const owner;
String16 const name;
uint32_t const id;
// 這個resources.arsc中包含的package,一般來說只有一個
Vector<Package*> packages;
........
}
struct ResTable::Package
{
// 所在ResTable對象
const ResTable* const owner;
// 他的header
const Header* const header;
// resources.arsc中的數據塊起始處
const ResTable_package* const package;
// 類型字符串池
ResStringPool typeStrings;
// 資源項名稱字符串池
ResStringPool keyStrings;
size_t typeIdOffset;
也就是說Restable類負責管理app要使用的所有的resouces.arsc.后續的操作都是對Restable的mHeades和mPackageGroups的操作,將解析的resoiurces.arsc相應數據分別存儲在mHeades和mPackageGroups。
那么接下來看getResTable()方法,其傳入的參數為true:
const ResTable* AssetManager::getResTable(bool required) const
{
// 一個native層的AssetManager對象只包含一個ResTable對象,保存在mResources中
// 如果已經創建,那么直接返回
ResTable* rt = mResources;
if (rt) {
return rt;
}
............
// 創建ResTable對象
mResources = new ResTable();
updateResourceParamsLocked();
bool onlyEmptyResources = true;
const size_t N = mAssetPaths.size();
for (size_t i=0; i<N; i++) {
// mAssetPaths中存儲了此app中用到的資源包路徑,包括系統資源包路徑
// 現在開始打開這些資源包,也就是apk中的resources.arsc
// 與resources.arsc關聯的是Asset對象,會將Asset對象加入到mResources中,
// 加入的過程中會解析resources.arsc
bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
onlyEmptyResources = onlyEmptyResources && empty;
}
if (required && onlyEmptyResources) {
ALOGW("Unable to find resources file resources.arsc");
delete mResources;
mResources = NULL;
}
return mResources;
}
核心邏輯在appendPathToResTable()方法中,不過這里要注意一點,因為mAssetPaths包括系統資源包路徑,而系統資源包已經在zygote啟動時加載了,所以其reources.arsc不需要再此被加載了。
appendPathToResTable()肯定會對系統資源包做特殊處理,參數是資源包路徑。
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
}
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
MY_TRACE_BEGIN(ap.path.string());
// 資源覆蓋機制,暫不考慮
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
// 資源包路徑不是一個文件夾,那就是一個apk文件了
if (ap.type != kFileTypeDirectory) {
// 對于app來說,第一次執行時,肯定為0,因為mResources剛創建,還沒對其操作
// 下面的分支 指揮在參數是系統資源包路徑時,才執行,
// 而且系統資源包路徑是首次被解析的
// 第二次執行appendPathToResTable,nextEntryIdx就不會為0了
if (nextEntryIdx == 0) {
// mAssetPaths中存儲的第一個資源包路徑是系統資源的路徑,
// 即framework-res.apk的路徑,它在zygote啟動時已經加載了
// 可以通過mZipSet.getZipResourceTable獲得其ResTable對象
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
// 對于APP來說,肯定不為NULL
if (sharedRes != NULL) {
// 得到系統資源包路徑中resources.arsc個數
nextEntryIdx = sharedRes->getTableCount();
}
}
// 當參數是mAssetPaths中除第一個以外的其他資源資源包路徑,
// 比如app自己的資源包路徑時,走下面的邏輯
if (sharedRes == NULL) {
// 檢查該資源包是否被其他進程加載了,這與ZipSet數據結構有關,后面在詳細介紹
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
// 對于app自己的資源包來說,一般都會都下面的邏輯
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
// 創建Asset對象,就是打開resources.arsc
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
// 只有在zygote啟動時,才會執行下面的邏輯
// 為系統資源創建 ResTable,并加入到mZipSet里。
if (nextEntryIdx == 0 && ass != NULL) {
// If this is the first resource table in the asset
// manager, then we are going to cache it so that we
// can quickly copy it out for others.
ALOGV("Creating shared resources for %s", ap.path.string());
// 創建ResTable對象,并把前面與resources.arsc關聯的Asset對象,加入到這個ResTabl中
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef HAVE_ANDROID_OS
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
String8 overlaysListPath(data);
overlaysListPath.appendPath(kResourceCache);
overlaysListPath.appendPath("overlays.list");
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
// 系統資源包時
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
mResources->add(sharedRes);
} else {
// 非系統資源包時,將與resources.arsc關聯的Asset對象加入到Restable中
// 此過程會解析resources.arsc文件。
ALOGV("Parsing resources for %s", ap.path.string());
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
ALOGV("Installing empty resources in to table %p\n", mResources);
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
MY_TRACE_END();
return onlyEmptyResources;
}
從上述代碼可以看出,實際解析resources.arsc文件的是ResTable的add()方法。
status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
// 得到resources.arsc在mmap之后內存中的地址
const void* data = asset->getBuffer(true);
...........
return addInternal(data, static_cast<size_t>(asset->getLength()),
idmapData, idmapSize, cookie, copyData);
}
解析大體過程如下:
ResTable類的成員函數add在增加一個Asset對象(非系統資源包)時,會對該Asset對象所描述的resources.arsc文件的內容進行解析,結果就是得到一個系列的Package信息。每一個Package又包含了一個資源類型字符串資源池和一個資源項名稱字符串資源池,以及一系列的資源類型規范數據塊和一系列的資源項數據塊。
還要注意的是,每一個資源包里面的所有Pacakge形成一個PackageGroup,保存ResTable對象的成員變量mPackageGroups中。
通過前面分析可知,ResTable類的成員函數add首先添加的是系統資源包,由于系統資源包已經解析過了,也就是說有Restable對象與之關聯,那么這個add方法簡單了,直接將系統資源的Restable對象中的mHeaders和mPackageGroups拷貝到這個新創建的Restable對象中即可。不需要解析了。
status_t ResTable::add(ResTable* src)
{
mError = src->mError;
// 將系統資源的Restable對象中的mHeader數組中的header都拷貝出來
for (size_t i=0; i<src->mHeaders.size(); i++) {
mHeaders.add(src->mHeaders[i]);
}
// 將系統資源的Restable對象中的mPackageGroups數組中的PackageGroup也都拷貝出來
for (size_t i=0; i<src->mPackageGroups.size(); i++) {
PackageGroup* srcPg = src->mPackageGroups[i];
PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
for (size_t j=0; j<srcPg->packages.size(); j++) {
pg->packages.add(srcPg->packages[j]);
}
for (size_t j = 0; j < srcPg->types.size(); j++) {
if (srcPg->types[j].isEmpty()) {
continue;
}
TypeList& typeList = pg->types.editItemAt(j);
typeList.appendVector(srcPg->types[j]);
}
pg->dynamicRefTable.addMappings(srcPg->dynamicRefTable);
pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId);
mPackageGroups.add(pg);
}
memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap));
return mError;
}
那么現在再次來分析getStringBlockCount()就很簡單了:
static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
{
// 得到與java層AssetManager對象對應的natvie層AssetManager對象
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
// 獲得ResTable對象
// 然后調用ResTable的getTableCount()
// 返回 mHeaders.size(),也就是resources.arsc的個數
// 包括系統資源對應的resources.arsc
return am->getResources().getTableCount();
}
再次來看makeStringBlocks:
final void makeStringBlocks(StringBlock[] seed) {
final int seedNum = (seed != null) ? seed.length : 0;
// num包括了seedNum
final int num = getStringBlockCount();
// StringBlock實際上用來操作resources.arsc的字符串值池
mStringBlocks = new StringBlock[num];
if (localLOGV) Log.v(TAG, "Making string blocks for " + this
+ ": " + num);
for (int i=0; i<num; i++) {
if (i < seedNum) {
// 系統已經加載了,直接賦值
mStringBlocks[i] = seed[i];
} else {
// 其他的調用getNativeStringBlock
mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
}
}
}
static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz,
jint block)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block));
}
const ResStringPool* ResTable::getTableStringBlock(size_t index) const
{
return &mHeaders[index]->values;
}
getTableStringBlock很簡單就是獲得reouces.arsc的字符串池。然后用來構造StringBlock對象。
最后總結下,當Resources對象創建后,就解析好了其所需要的所有的資源包的resources.arsc,并且這些resources.arsc的所有字符串池已經被保存在了與Resources對象關聯的 java層AssetManager對象的mStringBlocks數組中。
到現在為止已經搞清楚Resources和AssetManager對象的創建過程,何時解析resouces.arsc,以及解析后的數據存放在哪里了。
這為理解通過資源ID查找資源就打下了堅實基礎。