天天看點

對象類Android應用程式資料總管(Asset Manager)的建立過程分析

發一下牢騷和主題無關:

        在面前一篇文章中,我們析分了Android應用程式源資的編譯和打包程序,終最失掉的應用程式源資就與應用程式代碼一同打包在一個APK檔案中。Android應用程式在行運的程序中,是通過一個稱為AssetManager的源資管理器來讀取打包在APK檔案裡頭的源資檔案的。在本文中,我們就将詳細析分Android應用程式源資管理器的創立以及初始化程序,為接上去的一篇文章析分應用程式源資的讀取程序打下基本。

    老羅的新浪微網誌:http://weibo.com/shengyangluo,歡送注關!

        從面前Android應用程式視窗(Activity)的行運上下文環境(Context)的創立程序析分一文可以道知,應用程式的個一每Activity元件都關聯有一個ContextImpl對象,這個ContextImpl對象就是用來描述Activity元件的行運上下文環境的。Activity元件是從Context類繼承上去的,而ContextImpl一樣是從Context類繼承上去的。我們在Activity元件調用的大部分成員函數都是發轉授與它所關聯的一個ContextImpl對象的對應的成員函數來理處的,其中就括包用來問訪應用程式源資的兩個成員函數getResources和getAssets。

        ContextImpl類的成員函數getResources傳回的是一個Resources對象,有了這個Resources對象後之,我們就能夠通過源資ID來問訪那些被編譯過的應用程式源資了。ContextImpl類的成員函數getAssets傳回的是一個AssetManager對象,有了這個AssetManager對象後之,我們就能夠通過檔案名來問訪那些被編譯過或者沒有被編譯過的應用程式源資檔案了。事實上,Resources類也是通過AssetManager類來問訪那些被編譯過的應用程式源資檔案的,不過在問訪之前,它會先根據源資ID找查失掉對應的源資檔案名。

        我們道知,在Android統系中,一個程序是可以同時加載多個應用程式的,也就是可以同時加載多個APK檔案。個一每APK檔案在程序中都對應有一個全局的Resourses對象以及一個全局的AssetManager對象。其中,這個全局的Resourses對象存保在一個對應的ContextImpl對象的成員變量mResources中,而這個全局的AssetManager對象存保在這個全局的Resourses對象的成員變量mAssets中。上述ContextImpl、Resourses和AssetManager的關系如圖1所示:

對象類Android應用程式資料總管(Asset Manager)的建立過程分析

    圖1 ContextImpl、Resources和AssetManager的關系圖

        Resources類有一個成員函數getAssets,通過它就能夠取得存保在Resources類的成員變量mAssets中的AssetManager,例如,ContextImpl類的成員函數getAssets就是通過調用其成員變量mResources所指向的一個Resources對象的成員函數getAssets來取得一個可以用來問訪應用程式的非編譯源資檔案的AssetManager。

        我們道知,Android應用程式除了要問訪自己的源資外之,還要需問訪統系的源資。統系的源資打包在/system/framework/framework-res.apk檔案中,它在應用程式程序中是通過一個獨自的Resources對象和一個獨自的AssetManager對象來管理的。這個獨自的Resources對象就存保在Resources類的靜态成員變量mSystem中,我們可以通過Resources類的靜态成員函數getSystem就能夠取得這個Resources對象,而這個獨自的AssetManager對象就存保在AssetManager類的靜态成員變量sSystem中,我們可以通過AssetManager類的靜态成員函數getSystem一樣可以取得這個AssetManager對象。

        AssetManager類除了在Java層有一個實作外之,在 C++層也有一個對應的實作,而Java層的AssetManager類的功能就是通過C++層的AssetManager類來實作的。Java層的個一每AssetManager對象都有一個型類為int的成員變量mObject,它存保的是便在C++層對應的AssetManager對象的位址,是以,通過這個成員變量就能夠将Java層的AssetManager對象與C++層的AssetManager對象關聯起來。

        C++層的AssetManager類有三個主要的成員變量mAssetPaths、mResources和mConfig。其中,mAssetPaths存保的是源資寄存目錄,mResources指向的是一個源資索引表,而mConfig存保的是裝置的地本置配息信,例如螢幕密度和小大、家國地域和語言等等置配息信。有了這三個成員變量後之,C++層的AssetManager類就能夠問訪應用程式的源資了。

        從面前Android應用程式動啟程序源代碼析分一文可以道知,個一每Activity元件在程序的加載程序中,都市創立一個對應的ContextImpl,并且調用這個ContextImpl對象的成員函數init來行執初始化Activity元件行運上下文環境的任務,其中就括包創立用來問訪應用程式源資的Resources對象和AssetManager對象的任務,接上去,我們就從ContextImpl類的成員函數init開始析分Resources對象和AssetManager對象的創立以及初始化程序,如圖2所示:

對象類Android應用程式資料總管(Asset Manager)的建立過程分析

    圖2 應用程式源資管理器的創立和初始化程序

        這個程序可以分為14個步調,接上去我們就詳細析分個一每步調。

        Step 1. ContextImpl.init

class ContextImpl extends Context {
    ......

    /*package*/ LoadedApk mPackageInfo;
    private Resources mResources;
    ......

    final void init(LoadedApk packageInfo,
            IBinder activityToken, ActivityThread mainThread) {
        init(packageInfo, activityToken, mainThread, null);
    }

    final void init(LoadedApk packageInfo,
                IBinder activityToken, ActivityThread mainThread,
                Resources container) {
        mPackageInfo = packageInfo;
        mResources = mPackageInfo.getResources(mainThread);

        ......
    }

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/app/ContextImpl.java中。

        參數packageInfo指向的是一個LoadedApk對象,這個LoadedApk對象描述的是前當正在動啟的Activity組所屬的Apk。三個參數版本的成員函數init調用了四個參數版本的成員函數init來初始化前當正在動啟的Activity元件的行運上下文環境。其中,用來問訪應用程式源資的Resources對象是通過調用參數packageInfo所指向的是一個LoadedApk對象的成員函數getResources來創立的。這個Resources對象創立成完後之,就會存保在ContextImpl類的成員變量mResources中。

        接上去,我們就繼承析分LoadedApk類的成員函數getResources的實作。

        Step 2. LoadedApk.getResources

final class LoadedApk {
    ......

    private final String mResDir;
    ......

    Resources mResources;
    ......

    public Resources getResources(ActivityThread mainThread) {
        if (mResources == null) {
            mResources = mainThread.getTopLevelResources(mResDir, this);
        }
        return mResources;
    }
 
    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/app/LoadedApk.java中。

        參數mainThread指向了一個ActivityThread對象,這個ActivityThread對象描述的是前當正在行運的應用程式程序。

        LoadedApk類的成員函數getResources首先查檢其成員變量mResources的值是不是于等null。如果不于等的話,那麼就會将它所指向的是一個Resources對象傳回給調用者,否則的話,就會調用參數mainThread所指向的一個ActivityThread對象的成員函數getTopLevelResources來取得這個Resources對象,然後再傳回給調用者。

        在調用ActivityThread類的成員函數getTopLevelResources來取得一個Resources對象的時候,要需指定要取獲的Resources對象所對應的Apk檔案徑路,這個Apk檔案徑路就存保在LoadedApk類的成員變量mResDir中。例如,設假我們要取獲的Resources對象是用來問訪統系自帶的音樂播放器的源資的,那麼對應的Apk檔案徑路就為/system/app/Music.apk。

        接上去,我們就繼承析分ActivityThread類的成員函數getTopLevelResources的實作。

        Step 3. ActivityThread.getTopLevelResources

public final class ActivityThread {
    ......

    final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
            = new HashMap<ResourcesKey, WeakReference<Resources> >();
    ......

    Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
        ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
        Resources r;
        synchronized (mPackages) {
            ......

            WeakReference<Resources> wr = mActiveResources.get(key);
            r = wr != null ? wr.get() : null;
            ......

            if (r != null && r.getAssets().isUpToDate()) {
                ......
                return r;
            }
        }

        ......

        AssetManager assets = new AssetManager();
        if (assets.addAssetPath(resDir) == 0) {
            return null;
        }
        ......

        r = new Resources(assets, metrics, getConfiguration(), compInfo);
        ......

        synchronized (mPackages) {
            WeakReference<Resources> wr = mActiveResources.get(key);
            Resources existing = wr != null ? wr.get() : null;
            if (existing != null && existing.getAssets().isUpToDate()) {
                // Someone else already created the resources while we were
                // unlocked; go ahead and use theirs.
                r.getAssets().close();
                return existing;
            }

            // XXX need to remove entries when weak references go away
            mActiveResources.put(key, new WeakReference<Resources>(r));
            return r;
        }
    }

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/app/ActivityThread.java中。

        ActivityThread類的成員變量mActiveResources指向的是一個HashMap。這個HashMap用來維護在前當應用程式程序中加載的個一每Apk檔案及其對應的Resources對象的對應關系。也就是說,給定一個Apk檔案徑路,ActivityThread類的成員函數getTopLevelResources可以在成員變量mActiveResources中查檢是不是存在一個對應的Resources對象。如果不存在,那麼就會建立一個,并且存保在ActivityThread類的成員變量mActiveResources中。

        參數resDir即為要取獲其對應的Resources對象的Apk檔案徑路,ActivityThread類的成員函數getTopLevelResources首先根據它來創立一個ResourcesKey對象,然後再以這個ResourcesKey對象在ActivityThread類的成員變量mActiveResources中查檢是不是存在一個Resources對象。如果存在,并且這個Resources對象裡頭含包的源資檔案沒有時過,即調用這個Resources對象的成員函數getAssets所取得一個AssetManager對象的成員函數isUpToDate的傳回值于等true,那麼ActivityThread類的成員函數getTopLevelResources就能夠将該Resources對象傳回給調用者了。

        如果不存在與參數resDir對應的Resources對象,或者存在這個Resources對象,但是存在的這個Resources對象是時過的,那麼ActivityThread類的成員函數getTopLevelResources就會新創立一個AssetManager對象,并且調用這個新創立的AssetManager對象的成員函數addAssetPath來将參數resDir所描述的Apk檔案徑路作為它的源資目錄。

        創立了一個新的AssetManager對象,ActivityThread類的成員函數getTopLevelResources還要需這個AssetManager對象來創立一個新的Resources對象。這個新創立的Resources對象要需以面前所創立的ResourcesKey對象為鍵值緩存在ActivityThread類的成員變量mActiveResources所描述的一個HashMap中,以便後以可以取獲來回應用。不過,這個新創立的Resources對象在緩存到ActivityThread類的成員變量mActiveResources所描述的一個HashMap去之前,要需再次查檢該HashMap是不是已存在一個對應的Resources對象了,這是因為前當線程在創立新的AssetManager對象和Resources對象的程序中,可能有其它線程搶先一步創立了與參數resDir對應的Resources對象,并且将該Resources對象存保到該HashMap中去了。

        如果沒有其它線程搶先創立一個與參數resDir對應的Resources對象,或者其它線程搶先創立出來的Resources對象是時過的,那麼ActivityThread類的成員函數getTopLevelResources就會将面前創立的Resources對象緩存到成員變量mActiveResources所描述的一個HashMap中去,并且将面前創立的Resources對象傳回給調用者,否則擴知,就會将其它線程搶先創立的Resources對象傳回給調用者。

        接上去,我們首先析分AssetManager類的構造函數和成員函數addAssetPath的實作,接着再析分Resources類的構造函數的實作,以便可以懂得用來問訪應用程式源資的AssetManager對象和Resources對象的創立以及初始化程序。

        Step 4. new AssetManager

public final class AssetManager {
    ......

    private static AssetManager sSystem = null;
    ......

    public AssetManager() {
        synchronized (this) {
            ......
            init();
            ......
            ensureSystemAssets();
        }
    }

    private static void ensureSystemAssets() {
        synchronized (sSync) {
            if (sSystem == null) {
                AssetManager system = new AssetManager(true);
                system.makeStringBlocks(false);
                sSystem = system;
            }
        }
    }

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/AssetManager.java中。

        AssetManager類的構造函數是通過調用另外一個成員函數init來行執初始化任務的。在初始化成完前當正在創立的AssetManager對象後之,AssetManager類的構造函數還會調用另外一個成員函數ensureSystemAssets來查檢前當程序是不是已創立了一個用來問訪統系源資的AssetManager對象。

        如果用來問訪統系源資的AssetManager對象還沒有創立的話,那麼AssetManager類的成員函數ensureSystemAssets就會創立并且初始化它,并且将它存保在AssetManager類的靜态成員變量sSystem中。意注,創立用來問訪統系源資和應用程式源資的AssetManager對象的程序是一樣的,差別隻在于它們所要問訪的Apk檔案不一樣,是以,接上去我們就隻析分用來問訪應用源資的AssetManager對象的創立程序以及初始化程序。

       Step 5. AssetManager.init

public final class AssetManager {
    ......
 
    private int mObject;
    ......

    private native final void init();

    ......
}
           

       這個函數義定在檔案frameworks/base/core/java/android/content/res/AssetManager.java中。

       AssetManager類的成員函數init是一個JNI函數,它是由C++層的函數android_content_AssetManager_init來實作的:

static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
    AssetManager* am = new AssetManager();
    .....

    am->addDefaultAssets();

    ......
    env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
}
           

         這個函數義定在檔案frameworks/base/core/jni/android_util_AssetManager.cpp中。

         函數android_content_AssetManager_init首先創立一個C++層的AssetManager對象,接着調用這個C++層的AssetManager對象的成員函數addDefaultAssets來添加認默的源資徑路,最後将這個這個C++層的AssetManager對象的位址存保在參數clazz所描述的一個Java層的AssetManager對象的成員變量mObject中。

        Step 6. AssetManager.addDefaultAssets

static const char* kSystemAssets = "framework/framework-res.apk";
......

bool AssetManager::addDefaultAssets()
{
    const char* root = getenv("ANDROID_ROOT");
    ......

    String8 path(root);
    path.appendPath(kSystemAssets);

    return addAssetPath(path, NULL);
}
           

       這個函數義定在檔案frameworks/base/libs/utils/AssetManager.cpp中。

       AssetManager類的成員函數addDefaultAssets首先通過環境變量ANDROID_ROOT來取得Android的統系徑路,接着再将全局變量kSystemAssets所指向的字元串“framework/framework-res.apk”附加到這個統系徑路的面後去,這樣就能夠失掉統系源資檔案framework-res.apk的絕對徑路了。一般來說,環境變量ANDROID_ROOT所置設的Android統系徑路就是“/system”,是以,終最失掉的統系源資檔案的絕對徑路就為“/system/framework/framework-res.apk”。

       失掉了統系源資檔案framework-res.apk的絕對徑路後之,就調用AssetManager類的成員函數addAssetPath來将它添加到前當正在初始化的AssetManager對象中去。

       Step 7. AssetManager.addAssetPath

static const char* kAppZipName = NULL; //"classes.jar";
......

bool AssetManager::addAssetPath(const String8& path, void** cookie)
{
    AutoMutex _l(mLock);

    asset_path ap;

    String8 realPath(path);
    if (kAppZipName) {
        realPath.appendPath(kAppZipName);
    }
    ap.type = ::getFileType(realPath.string());
    if (ap.type == kFileTypeRegular) {
        ap.path = realPath;
    } else {
        ap.path = path;
        ap.type = ::getFileType(path.string());
        if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
            ......
            return false;
        }
    }

    // Skip if we have it already.
    for (size_t i=0; i<mAssetPaths.size(); i++) {
        if (mAssetPaths[i].path == ap.path) {
            if (cookie) {
                *cookie = (void*)(i+1);
            }
            return true;
        }
    }

    ......

    mAssetPaths.add(ap);

    // new paths are always added at the end
    if (cookie) {
        *cookie = (void*)mAssetPaths.size();
    }

    // add overlay packages for /system/framework; apps are handled by the
    // (Java) package manager
    if (strncmp(path.string(), "/system/framework/", 18) == 0) {
        // When there is an environment variable for /vendor, this
        // should be changed to something similar to how ANDROID_ROOT
        // and ANDROID_DATA are used in this file.
        String8 overlayPath("/vendor/overlay/framework/");
        overlayPath.append(path.getPathLeaf());
        if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
            asset_path oap;
            oap.path = overlayPath;
            oap.type = ::getFileType(overlayPath.string());
            bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
            if (addOverlay) {
                oap.idmap = idmapPathForPackagePath(overlayPath);

                if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
                    addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
                }
            }
            if (addOverlay) {
                mAssetPaths.add(oap);
            } 
            ......
        }
    }

    return true;
}
           

        這個函數義定在檔案frameworks/base/libs/utils/AssetManager.cpp中。

        如果全局變量kAppZipName的值不于等NULL的話,那麼它的值一般就是被置設為“classes.jar”,這時候就表示應用程式的源資檔案是存保在參數path所描述的一個目錄下的一個classes.jar檔案中。全局變量kAppZipName的值一般被置設為NULL,并且參數path指向的是一個Apk檔案,是以,接上去我們隻考慮應用程式源資不是存保在一個classes.jar檔案的情況。

        AssetManager類的成員函數addAssetPath首先是要查檢參數path指向的是一個檔案或者目錄,并且該檔案或者目錄存在,否則的話,它就會直接傳回一個false值,而不會再繼承往下理處了。

        AssetManager類的成員函數addAssetPath接着再查檢在其成員變量mAssetPaths所描述的一個型類為asset_path的Vector中是不是已添加過參數path所描述的一個Apk檔案徑路了。如果已添加過了,那麼AssetManager類的成員函數addAssetPath就不會再繼承往下理處了,而是将與參數path所描述的一個Apk檔案徑路所對應的一個Cookie傳回給調用者,即存保在輸出參數cookie中,前提是參數cookie的值不于等NULL。一個Apk檔案徑路所對應的Cookie實際上隻是一個整數,這個整數表示該Apk檔案徑路所對應的一個asset_path對象在成員變量mAssetPaths所描述的一個Vector中的索引再加上1。

        經過上面的查檢後之,AssetManager類的成員函數addAssetPath確定參數path所描述的一個Apk檔案徑路之前沒有被添加過,于是接上去就會将與該Apk檔案徑路所對應的一個asset_path對象存保在成員變量mAssetPaths所描述的一個Vector的最末尾位置上,并且将這時候失掉的Vector的小大作為一個Cookie值存保在輸出參數cookie中傳回給調用者。

        AssetManager類的成員函數addAssetPath的最後一個任務是查檢剛剛添加的Apk檔案徑路是不是是存保在/system/framework/目錄下面的。如果是的話,那麼就會在/vendor/overlay/framework/目錄下找到一個同名的Apk檔案,并且也會将該Apk檔案添加到成員變量mAssetPaths所描述的一個Vector中去。這是一種源資覆寫機制,手機廠商可以利用它來自義定的統系源資,即用自義定的統系源資來覆寫統系認默的統系源資,以達到個性化統系界面的目的。

    每日一道理

書籍好比一架梯子,它能引領人們登上文化的殿堂;書籍如同一把鑰匙,它将幫助我們開啟心靈的智慧之窗;書籍猶如一條小船,它會載着我們駛向知識的海洋。

        如果手機廠商要利用上述的源資覆寫機制來自義定自己的統系源資,那麼還要需提供一個idmap檔案,用來說明它在/vendor/overlay/framework/目錄提供的Apk檔案要覆寫統系的哪些認默源資,應用源資ID來描述,是以,這個idmap檔案實際上就是一個源資ID映射檔案。這個idmap檔案終最存保在/data/resource-cache/目錄下,并且按照一定的格式來指令,例如,設假手機廠商提供的覆寫源資檔案為/vendor/overlay/framework/framework-res.apk,那麼對應的idmap檔案就會以名稱為@[email protected]@[email protected]@idmap的形式存保在目錄/data/resource-cache/下。

        關于Android統系的源資覆寫(Overlay)機制,可以參考frameworks/base/libs/utils目錄下的READ檔案。

        這一步行執成完後之,回到面前的Step 3中,即ActivityThread類的成員函數getTopLevelResources中,接上去它就會調用面前所創立的Java層的AssetManager對象的成員函數addAssetPath來添加指定的應用程式源資檔案徑路。

        Step 8. AssetManager.addAssetPath

public final class AssetManager {
    ......

    /**
     * Add an additional set of assets to the asset manager.  This can be
     * either a directory or ZIP file.  Not for use by applications.  Returns
     * the cookie of the added asset, or 0 on failure.
     * {@hide}
     */
    public native final int addAssetPath(String path);

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/AssetManager.java中。

        AssetManager類的成員函數addAssetPath是一個JNI方法,它是由C++層的函數android_content_AssetManager_addAssetPath來實作的,如下所示:

static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
                                                       jstring path)
{
    ......

    AssetManager* am = assetManagerForJavaObject(env, clazz);
    ......

    const char* path8 = env->GetStringUTFChars(path, NULL);

    void* cookie;
    bool res = am->addAssetPath(String8(path8), &cookie);

    env->ReleaseStringUTFChars(path, path8);

    return (res) ? (jint)cookie : 0;
}
           

        這個函數義定在檔案frameworks/base/core/jni/android_util_AssetManager.cpp中。

        參數clazz指向的是Java層的一個AssetManager對象,函數android_content_AssetManager_addAssetPath首先調用另外一個函數assetManagerForJavaObject來将它的成員函數mObject轉換為一個C++層的AssetManager對象。有了這個C++層的AssetManager對象後之,就能夠調用它的成員函數addAssetPath來将參數path所描述的一個Apk檔案徑路添加到它裡頭去了,這個程序可以參考面前的Step 7。

        這一步行執成完後之,回到面前的Step 3中,即ActivityThread類的成員函數getTopLevelResources中,接上去就會根據面前所創立的Java層的AssetManager對象來創立一個Resources對象。

        Step 9. new Resources

public class Resources {
    ......

    /*package*/ final AssetManager mAssets;
    ......

    public Resources(AssetManager assets, DisplayMetrics metrics,
            Configuration config, CompatibilityInfo compInfo) {
        mAssets = assets;
        ......

        updateConfiguration(config, metrics);
        assets.ensureStringBlocks();
    }
 
    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/Resources.java中。

        Resources類的構造函數首先将參數assets所指向的一個AssetManager對象存保在成員變量mAssets中,以便後以可以通過它來問訪應用程式的源資,接上去調用另外一個成員函數updateConfiguration來置設裝置置配息信,最後調用參數assets所指向的一個AssetManager對象的成員函數ensureStringBlocks來創立字元串源資池。

        接上去,我們就首先析分Resources類的成員函數updateConfiguration的實作,接着再析分AssetManager類的成員函數ensureStringBlocks的實作。

        Step 10. Resources.updateConfiguration

public class Resources {
    ......

    private final Configuration mConfiguration = new Configuration();
    ......

    public void updateConfiguration(Configuration config,
            DisplayMetrics metrics) {
        synchronized (mTmpValue) {
            int configChanges = 0xfffffff;
            if (config != null) {
                configChanges = mConfiguration.updateFrom(config);
            }
            if (mConfiguration.locale == null) {
                mConfiguration.locale = Locale.getDefault();
            }
            if (metrics != null) {
                mMetrics.setTo(metrics);
                mMetrics.updateMetrics(mCompatibilityInfo,
                        mConfiguration.orientation, mConfiguration.screenLayout);
            }
            mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;

            String locale = null;
            if (mConfiguration.locale != null) {
                locale = mConfiguration.locale.getLanguage();
                if (mConfiguration.locale.getCountry() != null) {
                    locale += "-" + mConfiguration.locale.getCountry();
                }
            }
            int width, height;
            if (mMetrics.widthPixels >= mMetrics.heightPixels) {
                width = mMetrics.widthPixels;
                height = mMetrics.heightPixels;
            } else {
                //noinspection SuspiciousNameCombination
                width = mMetrics.heightPixels;
                //noinspection SuspiciousNameCombination
                height = mMetrics.widthPixels;
            }
            int keyboardHidden = mConfiguration.keyboardHidden;
            if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
                    && mConfiguration.hardKeyboardHidden
                            == Configuration.HARDKEYBOARDHIDDEN_YES) {
                keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
            }
            mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
                    locale, mConfiguration.orientation,
                    mConfiguration.touchscreen,
                    (int)(mMetrics.density*160), mConfiguration.keyboard,
                    keyboardHidden, mConfiguration.navigation, width, height,
                    mConfiguration.screenLayout, mConfiguration.uiMode, sSdkVersion);

            ......
        }
       
        ...... 
    }

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/Resources.java中。

        Resources類的成員變量mConfiguration指向的是一個Configuration對象,用來描述裝置前當的置配息信,這些置配息信對應的就是在面前Android源資管理架構(Asset Manager)簡要介紹和學習計劃一文中提到18個源資次元。

        Resources類的成員函數updateConfiguration首先是根據參數config和metrics來更新裝置的前當置配息信,例如,螢幕小大和密碼、家國地域和語言、鍵盤置配情況等等,接着再調用成員變量mAssets所指向的一個Java層的AssetManager對象的成員函數setConfiguration來将這些置配息信置設到與之關聯的C++層的AssetManager對象中去。

        接上去,我們就繼承析分AssetManager類的成員函數setConfiguration的實作,以便可以懂得裝置置配息信的置設程序。

        Step 11. AssetManager.setConfiguration

public final class AssetManager {
    ......

    /**
     * Change the configuation used when retrieving resources.  Not for use by
     * applications.
     * {@hide}
     */
    public native final void setConfiguration(int mcc, int mnc, String locale,
            int orientation, int touchscreen, int density, int keyboard,
            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
            int screenLayout, int uiMode, int majorVersion);

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/AssetManager.java中。

        AssetManager類的成員函數setConfiguration是一個JNI方法,它是由C++層的函數android_content_AssetManager_setConfiguration來實作的,如下所示:

static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
                                                          jint mcc, jint mnc,
                                                          jstring locale, jint orientation,
                                                          jint touchscreen, jint density,
                                                          jint keyboard, jint keyboardHidden,
                                                          jint navigation,
                                                          jint screenWidth, jint screenHeight,
                                                          jint screenLayout, jint uiMode,
                                                          jint sdkVersion)
{
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return;
    }

    ResTable_config config;
    memset(&config, 0, sizeof(config));

    const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;

    config.mcc = (uint16_t)mcc;
    config.mnc = (uint16_t)mnc;
    config.orientation = (uint8_t)orientation;
    config.touchscreen = (uint8_t)touchscreen;
    config.density = (uint16_t)density;
    config.keyboard = (uint8_t)keyboard;
    config.inputFlags = (uint8_t)keyboardHidden;
    config.navigation = (uint8_t)navigation;
    config.screenWidth = (uint16_t)screenWidth;
    config.screenHeight = (uint16_t)screenHeight;
    config.screenLayout = (uint8_t)screenLayout;
    config.uiMode = (uint8_t)uiMode;
    config.sdkVersion = (uint16_t)sdkVersion;
    config.minorVersion = 0;
    am->setConfiguration(config, locale8);

    if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
}
           

        這個函數義定在檔案frameworks/base/core/jni/android_util_AssetManager.cpp中。

        參數clazz指向的是一個Java層的AssetManager對象,函數android_content_AssetManager_setConfiguration首先調用另外一個函數assetManagerForJavaObject将它的成員變量mObject轉換為一個C++層的AssetManager對象。

        函數android_content_AssetManager_setConfiguration接上去再根據其它參數來創立一個ResTable_config對象,這個ResTable_config對象就用來描述裝置的前當置配息信。

        函數android_content_AssetManager_setConfiguration最後調用面前取得C++層的AssetManager對象的成員函數setConfiguration來将面前創立的ResTable_config對象置設到它内部去,以便C++層的AssetManager對象可以根據裝置的前當置配息信來找到最合适的源資。

        Step 12. AssetManager.setConfiguration

void AssetManager::setConfiguration(const ResTable_config& config, const char* locale)
{
    AutoMutex _l(mLock);
    *mConfig = config;
    if (locale) {
        setLocaleLocked(locale);
    } else if (config.language[0] != 0) {
        char spec[9];
        spec[0] = config.language[0];
        spec[1] = config.language[1];
        if (config.country[0] != 0) {
            spec[2] = '_';
            spec[3] = config.country[0];
            spec[4] = config.country[1];
            spec[5] = 0;
        } else {
            spec[3] = 0;
        }
        setLocaleLocked(spec);
    } else {
        updateResourceParamsLocked();
    }
}
           

        這個函數義定在檔案frameworks/base/libs/utils/AssetManager.cpp中。

        AssetManager類的成員變量mConfig指向的是一個ResTable_config對象,用來描述裝置的前當置配息信,AssetManager類的成員函數setConfiguration首先将參數config所描述的裝置置配息信拷貝到它裡頭去。

        如果參數local的值不于等NULL,那麼它指向的字元串就是用來描述裝置的家國、地域和語言息信的,這時候AssetManager類的成員函數setConfiguration就會調用另外一個成員函數setLocalLocked來将它們置設到AssetManager類的另外一個成員變量mLocale中去。

        如果參數local的值于等NULL,并且參數config指向的一個ResTable_config對象含包了裝置的家國、地域和語言息信,那麼AssetManager類的成員函數setConfiguration一樣會調用另外一個成員函數setLocalLocked來将它們置設到AssetManager類的另外一個成員變量mLocale中去。

        如果參數local的值于等NULL,并且參數config指向的一個ResTable_config對象沒有含包裝置的家國、地域和語言息信,那麼就說明裝置的家國、地域和語言等息信不要需更新,這時候AssetManager類的成員函數setConfiguration就會直接調用另外一個成員函數updateResourceParamsLocked來更新源資表中的裝置置配息信。

        意注,AssetManager類的成員函數setLocalLocked來更新了成員變量mLocale的内容後之,一樣會調用另外一個成員函數updateResourceParamsLocked來更新源資表中的裝置置配息信。

        AssetManager類的成員函數updateResourceParamsLocked的實作如下所示:

void AssetManager::updateResourceParamsLocked() const
{
    ResTable* res = mResources;
    if (!res) {
        return;
    }

    size_t llen = mLocale ? strlen(mLocale) : 0;
    mConfig->language[0] = 0;
    mConfig->language[1] = 0;
    mConfig->country[0] = 0;
    mConfig->country[1] = 0;
    if (llen >= 2) {
        mConfig->language[0] = mLocale[0];
        mConfig->language[1] = mLocale[1];
    }
    if (llen >= 5) {
        mConfig->country[0] = mLocale[3];
        mConfig->country[1] = mLocale[4];
    }
    mConfig->size = sizeof(*mConfig);

    res->setParameters(mConfig);
}
           

        這個函數義定在檔案frameworks/base/libs/utils/AssetManager.cpp中。

        AssetManager類的成員變量mResources指向的是一個ResTable對象,這個ResTable對象描述的就是一個源資索引表。Android應用程式的源資索引表的格式以及生成程序可以參考面前Android應用程式源資的編譯和打包程序析分一文。

        AssetManager類的成員函數updateResourceParamsLocked首先是将成員變量mLocale所描述的家國、地域和語言息信更新到另外一個成員變量mConfig中去,接着再将成員變量mConfig所含包的裝置置配息信置設到成員變量mResources所描述的一個源資索引表中去,這是通過調用成員變量mResources所指向的一個ResTable對象的成員函數setParameters來實作的。

        這一步行執成完後之,傳回到面前的Step 9中,即Resources類的構造函數,接上去它就會調用AssetManager類的成員函數ensureStringBlocks來創立字元串源資池。

        Step 13. AssetManager.ensureStringBlocks

public final class AssetManager {
    ......

    private StringBlock mStringBlocks[] = null;
    ......

    /*package*/ final void ensureStringBlocks() {
        if (mStringBlocks == null) {
            synchronized (this) {
                if (mStringBlocks == null) {
                    makeStringBlocks(true);
                }
            }
        }
    }

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/AssetManager.java中。

        AssetManager類的成員變量mStringBlocks指向的是一個StringBlock數組,其中,個一每StringBlock對象都是用來描述一個字元串源資池。從面前Android應用程式源資的編譯和打包程序析分一文可以道知,個一每源資表都含包有一個源資項值字元串源資池,AssetManager類的成員變量mStringBlocks就是用來存保所有的源資表中的源資項值字元串源資池的。

        AssetManager類的成員函數ensureStringBlocks首先查檢成員變量mStringBlocks的值是不是于等null。如果于等null的話,那麼就說明前當應用程式應用的源資表中的源資項值字元串源資池還沒有讀取出來,這時候就會調用另外一個成員函數makeStringBlocks來進行讀取。

       Step 14. AssetManager.makeStringBlocks

public final class AssetManager {
    ......

    private final void makeStringBlocks(boolean copyFromSystem) {
        final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0;
        final int num = getStringBlockCount();
        mStringBlocks = new StringBlock[num];
        ......
        for (int i=0; i<num; i++) {
            if (i < sysNum) {
                mStringBlocks[i] = sSystem.mStringBlocks[i];
            } else {
                mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
            }
        }
    }

    ......

    private native final int getStringBlockCount();
    private native final int getNativeStringBlock(int block);

    ......
}
           

        這個函數義定在檔案frameworks/base/core/java/android/content/res/AssetManager.java中。

        參數copyFromSystem表示是不是要将統系源資表裡頭的源資項值字元串源資池也一同拷貝到成員變量mStringBlokcs所描述的一個數組中去。如果它的值于等true的時候,那麼AssetManager就會首先取得makeStringBlocks首先取得統系源資表的個數sysNum,接着再取得總的源資表個數num,這是通過調用JNI方法getStringBlockCount來實作的。意注,總的源資表個數num是含包了統系源資表的個數sysNum的。

        從面前的Step 4可以道知,用來問訪統系源資包的AssetManager對象就存保在AssetManager類的靜态成員變量sSystem中,并且這個AssetManager對象是最先被創立以及初始化的。也就是說,當行執到這一步的時候,所有統系源資表的源資項值字元串源資池已讀取出來,它們就存保在AssetManager類的靜态成員變量sSystem所描述的一個AssetManager對象的成員變量mStringBlocks中,是以,隻将它們拷貝到前當正在理處的AssetManager對象的成員變量mStringBlokcs的前sysNum個位置上去就能夠了。

        最後,AssetManager類的成員函數makeStringBlocks就調用另外一個JNI方法getNativeStringBlock來讀取剩餘的其它源資表的源資項值字元串源資池,并且分别将它們封裝在一個StringBlock對象存保在成員變量mStringBlokcs所描述的一個數組中。

        AssetManager類的JNI方法getNativeStringBlock實際上就是将個一每源資包裡頭的resources.arsc檔案的源資項值字元串源資池資料塊讀取出來,并且封裝在一個C++層的StringPool對象中,然後AssetManager類的成員函數makeStringBlocks再将該StringPool對象封裝成一個Java層的StringBlock中。關于源資表中的源資項值字元串源資池的更多息信,可以參考面前Android應用程式源資的編譯和打包程序析分一文。

        至此,我們就析分成完Android應用程式源資管理器的創立的初始化程序了,主要就是創立和初始化用來問訪應用程式源資的AssetManager對象和Resources對象,其中,初始化操作括包置設AssetManager對象的源資檔案徑路以及裝置置配息信等。有了這兩個初始化的AssetManager對象和Resources對象後之,在接上去的一篇文章中,我們就能夠繼承析分應用程式源資的找查程序了,敬請注關!

    老羅的新浪微網誌:http://weibo.com/shengyangluo,歡送注關!

文章結束給大家分享下程式員的一些笑話語錄: Bphone之你們聊,我先走了!移動說:我在phone前加o,我叫o縫;蘋果說:我在phone前i,我是i縫;微軟說:我在phone前加w,我叫w縫;三星說:你們聊,我先走了!

将來王建宙寫回憶錄的時候,一定要有一句“常小兵為中國移動的發展做出了不可磨滅的貢獻”。