Android資源載入原始碼分析
這篇文章主要介紹Android載入資源的主要流程
我們從一個簡單的函式入口進行分析
getResources().getString(resId);
看下原始碼是怎麼實現的(Resources.java)
public String getString(int id) throws NotFoundException { CharSequence res = getText(id); if (res != null) { return res.toString(); } throw new NotFoundException("String resource ID #0x" + Integer.toHexString(id)); } public CharSequence getText(int id) throws NotFoundException { CharSequence res = mAssets.getResourceText(id); if (res != null) { return res; } throw new NotFoundException("String resource ID #0x" + Integer.toHexString(id)); }
可以看到最後呼叫的是mAssets的getResourceText方法,mAssets為一個AssetManager,那我們進去這個AssetManager裡面的getResourceText看下究竟做了什麼
AssetManager.java:
/*package*/ final CharSequence getResourceText(int ident) { synchronized (this) { TypedValue tmpValue = mValue; int block = loadResourceValue(ident, (short) 0, tmpValue, true); if (block >= 0) { if (tmpValue.type == TypedValue.TYPE_STRING) { return mStringBlocks[block].get(tmpValue.data); } return tmpValue.coerceToString(); } } return null; }
這段函式主要涉及了兩個類,TypedValue和StringBlock。
TypedValue.java :
/**
* Container for a dynamically typed data value. Primarily used with
* {@link android.content.res.Resources} for holding resource values.
*/
public class TypedValue {
...
/** The type held by this value, as defined by the constants here. * This tells you how to interpret the other fields in the object. */ public int type; /** If the value holds a string, this is it. */ public CharSequence string; /** Basic data in the value, interpreted according to {@link #type} */ public int data; /** Additional information about where the value came from; only * set for strings. */ public int assetCookie; /** If Value came from a resource, this holds the corresponding resource id. */ public int resourceId; /** If Value came from a resource, these are the configurations for which * its contents can change. */ public int changingConfigurations = -1; /** * If the Value came from a resource, this holds the corresponding pixel density. * */ public int density; ... }
可以看到這個類主要是用來存和資源相關的資料的,而StringBlock Conveniences for retrieving data out of a compiled string resource。這個類主要是用來做string類的資源檢索和儲存的。每一個資源表都對應著一個資源type-value的字串資源池,StringBlock就是用來儲存這種字串的。那麼可以知道getResourceText的主要流程是
1)呼叫loadResourceValue函式,把資源資訊載入到TypedValue裡面。
2)loadResourceValue返回的block從StringBlock內去拿內容。那麼這裡面遺留一個問題,這裡面的block是什麼意思呢?
下面我們著重看下loadResourceValue這個函式,恩,這個函式是一個native函式,具體的實現在android_util_AssetManager.cpp裡面,我們去看下
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
jint ident,
jshort density,
jobject outValue,
jboolean resolve)
{
if (outValue == NULL) {
jniThrowNullPointerException(env, "outValue");
return 0;
}
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
const ResTable& res(am->getResources());
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
}
return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config) : block;
和這段程式碼相關的類是ResTable,在doc上面是這樣描述的Convenience class for accessing data in a ResTable resource。這個類和資源表是有關係的,具體的關係我們之後再闡明,那麼上面這個函式主要做的事情就是從ResTable中查詢資源,我們接著追一下里面的getResource函式。
ResourceTypes.cpp:
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
if (mError != NO_ERROR) {
return mError;
}
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
if (p < 0) {
if (Res_GETPACKAGE(resID)+1 == 0) {
ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
} else {
ALOGW("No known package when getting value for resource number 0x%08x", resID);
}
return BAD_INDEX;
}
if (t < 0) {
ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
return BAD_INDEX;
}
const Res_value* bestValue = NULL;
const Package* bestPackage = NULL;
ResTable_config bestItem;
memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
if (outSpecFlags != NULL) *outSpecFlags = 0;
// Look through all resource packages, starting with the most
// recently added.
const PackageGroup* const grp = mPackageGroups[p];
if (grp == NULL) {
ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
return BAD_INDEX;
}
// Allow overriding density
const ResTable_config* desiredConfig = &mParams;
ResTable_config* overrideConfig = NULL;
if (density > 0) {
overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
if (overrideConfig == NULL) {
ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno));
return BAD_INDEX;
}
memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
overrideConfig->density = density;
desiredConfig = overrideConfig;
}
ssize_t rc = BAD_VALUE;
size_t ip = grp->packages.size();
while (ip > 0) {
ip--;
int T = t;
int E = e;
const Package* const package = grp->packages[ip];
if (package->header->resourceIDMap) {
uint32_t overlayResID = 0x0;
status_t retval = idmapLookup(package->header->resourceIDMap,
package->header->resourceIDMapSize,
resID, &overlayResID);
if (retval == NO_ERROR && overlayResID != 0x0) {
// for this loop iteration, this is the type and entry we really want
ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
T = Res_GETTYPE(overlayResID);
E = Res_GETENTRY(overlayResID);
} else {
// resource not present in overlay package, continue with the next package
continue;
}
}
const ResTable_type* type;
const ResTable_entry* entry;
const Type* typeClass;
ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
if (offset <= 0) {
// No {entry, appropriate config} pair found in package. If this
// package is an overlay package (ip != 0), this simply means the
// overlay package did not specify a default.
// Non-overlay packages are still required to provide a default.
if (offset < 0 && ip == 0) {
ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
resID, T, E, ip, (int)offset);
rc = offset;
goto out;
}
continue;
}
if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
if (!mayBeBag) {
ALOGW("Requesting resource %p failed because it is complex\n",
(void*)resID);
}
continue;
}
TABLE_NOISY(aout << "Resource type data: "
<< HexDump(type, dtohl(type->header.size)) << endl);
if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) {
ALOGW("ResTable_item at %d is beyond type chunk data %d",
(int)offset, dtohl(type->header.size));
rc = BAD_TYPE;
goto out;
}
const Res_value* item =
(const Res_value*)(((const uint8_t*)type) + offset);
ResTable_config thisConfig;
thisConfig.copyFromDtoH(type->config);
if (outSpecFlags != NULL) {
if (typeClass->typeSpecFlags != NULL) {
*outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
} else {
*outSpecFlags = -1;
}
}
if (bestPackage != NULL &&
(bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
// Discard thisConfig not only if bestItem is more specific, but also if the two configs
// are identical (diff == 0), or overlay packages will not take effect.
continue;
}
bestItem = thisConfig;
bestValue = item;
bestPackage = package;
}
TABLE_NOISY(printf("Found result: package %p\n", bestPackage));
if (bestValue) {
outValue->size = dtohs(bestValue->size);
outValue->res0 = bestValue->res0;
outValue->dataType = bestValue->dataType;
outValue->data = dtohl(bestValue->data);
if (outConfig != NULL) {
*outConfig = bestItem;
}
TABLE_NOISY(size_t len;
printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
bestPackage->header->index,
outValue->dataType,
outValue->dataType == bestValue->TYPE_STRING
? String8(bestPackage->header->values.stringAt(
outValue->data, &len)).string()
: "",
outValue->data));
rc = bestPackage->header->index;
goto out;
}
out:
if (overrideConfig != NULL) {
free(overrideConfig);
}
return rc;
}
好吧,我也承認我看到這個函式的時候也很頭痛,但是這個函式應該是整個資源載入裡面最核心的部分了,讓我們來慢慢分析下這167行的函式裡面到底寫了什麼吧。
1)resId的解析。
#define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
#define Res_GETENTRY(id) (id&0xFFFF)
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
這個函式首先解析了resId,在android內resId分為三個部分,package,type和entry,這三部分是通過位偏移值來儲存在id資訊裡面的,resId有四個位元組,最高位元組儲存package資訊,第二個位元組儲存type資訊,最後兩個位元組儲存entry的資訊。其中具體的分析可以參考老羅的這篇文章 點選開啟連結
2)找到相應的PackageGroup。關於PackageGroup的描述如下
// A group of objects describing a particular resource package.
// The first in 'package' is always the root object (from the resource
// table that defined the package); the ones after are skins on top of it.
可以知道是用來描述一系列的資源資原始檔的,且第一個package是系統的資源。
3)迴圈PackageGroup,判斷是否是overlay包
這裡會去迴圈PackageGroup裡面的每一個Package,首先判斷這個Package是不是overlay package,overlay package的意思是該package是用來覆蓋資源的,如果是overlay package,則對id進行相應的轉換。關於overlay機制可以參考 安卓overlay機制
4)根據資源的type和entry判斷該package內是否有相應的資源。這裡面主要表現在getEntry這個函式裡面,這裡需要了解resources.arsc的資源結構,可以參考 安卓資源打包過程 這篇文章瞭解一二。
5)如果該包內無要找的資源,表現在getEntry的返回值<=0,則查詢下一個包,否則進行資源的最優匹配。每一個資源有相應的配置資訊,配置的內容儲存在ResTable_config結構體內,如果當前匹配到的資源比之前匹配的資源要優,則採用當前匹配的資源,恩,就是下面的這段程式碼了
if (bestPackage != NULL &&
(bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
// Discard thisConfig not only if bestItem is more specific, but also if the two configs
// are identical (diff == 0), or overlay packages will not take effect.
continue;
}
bestItem = thisConfig;
bestValue = item;
bestPackage = package;
6)返回。終於分析到最後一步啦。上面的步驟已經把能夠匹配到的最優資源儲存在
const Res_value* bestValue = NULL;
const Package* bestPackage = NULL;
ResTable_config bestItem;
這三個變數裡面了,最後將這三個變數資訊拷貝到要輸出的Res_value* outValue變數內,並且返回資源所在的package的索引給呼叫方,也就是android_content_AssetManager_loadResourceValue這裡面的block了。到這裡getResource這個函式就分析完成了。
回到上面的android_content_AssetManager_loadResourceValue 函式,我們獲取了返回值block,那麼這個函式還剩最後一步
if (resolve) {
block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
}
在我們getText的這個場景,resolve這個引數為true,也就是我們最後還需要對資源做解析的操作,我們來看下這個函式吧。
ResourceTypes.cpp:
ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
ResTable_config* outConfig) const
{
int count=0;
while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE
&& value->data != 0 && count < 20) {
if (outLastRef) *outLastRef = value->data;
uint32_t lastRef = value->data;
uint32_t newFlags = 0;
const ssize_t newIndex = getResource(value->data, value, true, 0, &newFlags,
outConfig);
if (newIndex == BAD_INDEX) {
return BAD_INDEX;
}
TABLE_THEME(ALOGI("Resolving reference %p: newIndex=%d, type=0x%x, data=%p\n",
(void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data));
//printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
if (newIndex < 0) {
// This can fail if the resource being referenced is a style...
// in this case, just return the reference, and expect the
// caller to deal with.
return blockIndex;
}
blockIndex = newIndex;
count++;
}
return blockIndex;
}
這個函式的作用是解析引用型別的資源,一個資源的引用也有可能是引用,如果這個資源是引用型別的話(value->dataType == value->TYPE_REFERENCE),那麼就呼叫getResource繼續進行解析,直到getResource解析的資源無效或者超過20次迴圈為止。
相關推薦
Android資源載入原始碼分析
這篇文章主要介紹Android載入資源的主要流程 我們從一個簡單的函式入口進行分析 getResources().getString(resId);看下原始碼是怎麼實現的(Resources.java) public String getString(int
【輸出文件】 Android 加密 模組原始碼分析
Android6.0 加密模組解析
【Android】Retrofit原始碼分析
Retrofit簡介 retrofit n. 式樣翻新,花樣翻新 vt. 給機器裝置裝配(新部件),翻新,改型 Retrofit 是一個 RESTful 的 HTTP 網路請求框架的封裝。注意這裡並沒有說它是網路請求框架,主要原因在於網路請求的工作並不是 Retrofit
【Android】OkHttp原始碼分析
Android為我們提供了兩種HTTP互動的方式:HttpURLConnection 和 Apache HttpClient,雖然兩者都支援HTTPS,流的上傳和下載,配置超時,IPv6和連線池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP 可以讓您的應用執行更快、更節省
Android Doze模式原始碼分析
轉自:https://www.cnblogs.com/l2rf/p/6373794.html 科技的仿生學無處不在,給予我們啟發。為了延長電池是使用壽命,google從蛇的冬眠中得到體會,那就是在某種情況下也讓手機進入類冬眠的情況,從而引入了今天的主題,Doze模式,Doze中
Android開發-從原始碼分析Fragment巢狀PagerAdapter生命週期,解決重建問題
介紹 眾所周知在Android開發中Fragment的生命週期非常複雜,複雜得甚至讓Square公司提出了我為什麼主張反對使用Android Fragment轉而提倡使用自定義View組合替代Fragment。但是沒辦法公司專案還是使用了很多Fragment巢狀
Android Wi-Fi原始碼分析之WifiService操作Wi-Fi(一):分析Wifi.c中的wifi_load_driver()函式
Wi-Fi原始碼分析之WifiService操作Wi-Fi(一) 分析Wifi.c中的wifi_load_driver()函式 int wifi_load_driver() { AL
Android Alarm驅動原始碼分析(Alarm.c)
前言: Android在Linux Kernel的基礎上增加了很多的驅動程式,Alarm驅動是其中最簡單的一個,整個檔案只有500多行。作為驅動程式碼分析的一系列文章的開始,我試圖仔細的分析此驅動的幾乎所有函式程式碼,希望籍此作為溫習Android驅動原始碼一個良好的開
資源排程原始碼分析和任務排程原始碼分析
1.資源排程原始碼分析 資源請求簡單圖 資源排程Master路徑: 路徑:spark-1.6.0/core/src/main/scala/org.apache.spark/deploy/Master/Master.scala 提交應用程式,submit的路徑: 路徑:spark-1.6
Android 屬性動畫原始碼分析 && Handler Epoll
根據屬性和Value 設定關鍵幀,然後通過AnimationHandler 呼叫 Chroeographor 去監聽VSYNC 訊號,收到Vsync訊號後,呼叫doFrame,最終回撥到AnimationHandler 設定當前時間 對應的 value。 https://www.jianshu.
Android Wi-Fi原始碼分析之wpa_supplicant初始化(四):wpa_supplicant_init_iface函式分析
wpa_config_read分析 路徑為:external\wpa_supplicant_8\wpa_supplicant\config_file.c struct wpa_config * wpa_config_read(const char *na
Android開發Handler原始碼分析
為什麼使用Handler Android中UI控制元件的訪問是執行緒不安全的,加鎖同步訪問會影響效能,因此設定只能一個執行緒更新UI,就是主執行緒,或者說是UI執行緒。在UI執行緒中不能進行耗時的操作,耗時操作需要開啟一個新的工作執行緒,工作執行緒不能更新UI
spring boot實戰(第十篇)Spring boot Bean載入原始碼分析
public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostPro
Android Init程序原始碼分析
Init 程序原始碼分析 基於Linux核心的android系統,在核心啟動完成後將建立一個Init使用者程序,實現了核心空間到使用者空間的轉變。在Android 啟動過程介紹一文中介紹了Android系統的各個啟動階段,init程序啟動後會讀取init.rc配置檔案,通
Android藍芽原始碼分析——BTA層訊息分發
這裡BTA是Bluetooth Application的縮寫,從上層下來的請求都要經過這個BTA層,通過傳送訊息的方式將請求丟到BTA層中處理。這個傳送訊息的函式定義在bta_sys_main.c中,如下: void bta_sys_sendmsg(voi
Android Wi-Fi原始碼分析之wpa_supplicant初始化(一)
一. wpa_supplicant配置編譯 將對應的平臺的wpa_supplicant包解壓改名為wpa_supplicant_8替換掉external下的wpa_supplicant_8目錄 執行: source build/envsetup.sh
Android(2.3+)原始碼分析MediaPlayer之RTSP
在前面的部落格中有簡單介紹MediaPlayer,最近又開始研究這塊東西,在此把閱讀程式碼的理解記錄下來方便以後快速查閱。 播放普通檔案傳入的url是一個本地的絕對路徑,但是流媒體的話傳入的就是一個網路地址如以"http://“開頭的流媒體和以"rtsp://"開頭的流媒體
Android 訊息機制原始碼分析
我們知道,當應用啟動的時候,android首先會開啟一個主執行緒,主執行緒管理ui控制元件,進行事件分發,當我們要做一個耗時的操作時,如聯網讀取資料,獲取讀取本地較大的檔案的時候,你應該在子執行緒中操作,因為有ui的更新,android主執行緒是執行緒不安全的,如果將更新介
android View繪製原始碼分析
在開發過程中我們經常要進行view的自定義。如果熟練掌握自定義技巧的話就能做出很多控制元件出來。這篇部落格來講講view繪製背後發生的那些事。 一, view的基礎知識 view的繪製概括 首先先說說view繪製的整體過程。 View繪製的原始碼分析
Android windowTranslucentStatus屬性原始碼分析
簡介 我們在設定系統樣式時,將windowTranslucentStatus和windowTranslucentNavigation屬性設定為true後,Activity就會顯示為如下效果: 狀態列和導航欄都會顯示成半透明的狀態。並且佈局會拓展到系統欄的