1. 程式人生 > >Android資源載入原始碼分析

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就會顯示為如下效果: 狀態列和導航欄都會顯示成半透明的狀態。並且佈局會拓展到系統欄的