1. 程式人生 > >轉自 老羅 Android應用程式資源管理器(Asset Manager)的建立過程分析

轉自 老羅 Android應用程式資源管理器(Asset Manager)的建立過程分析

原文地址在 http://blog.csdn.net/luoshengyang/article/details/8791064 原創老羅,轉載請說明

 

 

在前面一篇文章中,我們分析了Android應用程式資源的編譯和打包過程,最終得到的應用程式資源就與應用程式程式碼一起打包在一個APK檔案中。Android應用程式在執行的過程中,是通過一個稱為AssetManager的資源管理器來讀取打包在APK檔案裡面的資原始檔的。在本文中,我們就將詳細分析Android應用程式資源管理器的建立以及初始化過程,為接下來的一篇文章分析應用程式資源的讀取過程打下基礎。

        從前面

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所示:


圖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所示:

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

        這個過程可以分為14個步驟,接下來我們就詳細分析每一個步驟。

        Step 1. ContextImpl.init

[java] view plain copy print ?
  1. class ContextImpl extends Context { 
  2.     ...... 
  3.  
  4.     /*package*/ LoadedApk mPackageInfo; 
  5.     private Resources mResources; 
  6.     ...... 
  7.  
  8.     final void init(LoadedApk packageInfo, 
  9.             IBinder activityToken, ActivityThread mainThread) { 
  10.         init(packageInfo, activityToken, mainThread, null); 
  11.     } 
  12.  
  13.     final void init(LoadedApk packageInfo, 
  14.                 IBinder activityToken, ActivityThread mainThread, 
  15.                 Resources container) { 
  16.         mPackageInfo = packageInfo; 
  17.         mResources = mPackageInfo.getResources(mainThread); 
  18.  
  19.         ...... 
  20.     } 
  21.  
  22.     ...... 
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

[java] view plain copy print ?
  1. final class LoadedApk { 
  2.     ...... 
  3.  
  4.     private final String mResDir; 
  5.     ...... 
  6.  
  7.     Resources mResources; 
  8.     ...... 
  9.  
  10.     public Resources getResources(ActivityThread mainThread) { 
  11.         if (mResources == null) { 
  12.             mResources = mainThread.getTopLevelResources(mResDir, this); 
  13.         } 
  14.         return mResources; 
  15.     } 
  16.   
  17.     ...... 
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

[java] view plain copy print ?
  1. public final class ActivityThread { 
  2.     ...... 
  3.  
  4.     final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources 
  5.             = new HashMap<ResourcesKey, WeakReference<Resources> >(); 
  6.     ...... 
  7.  
  8.     Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { 
  9.         ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); 
  10.         Resources r; 
  11.         synchronized (mPackages) { 
  12.             ...... 
  13.  
  14.             WeakReference<Resources> wr = mActiveResources.get(key); 
  15.             r = wr != null ? wr.get() : null
  16.             ...... 
  17.  
  18.             if (r != null && r.getAssets().isUpToDate()) { 
  19.                 ...... 
  20.                 return r; 
  21.             } 
  22.         } 
  23.  
  24.         ...... 
  25.  
  26.         AssetManager assets = new AssetManager(); 
  27.         if (assets.addAssetPath(resDir) == 0) { 
  28.             return null
  29.         } 
  30.         ...... 
  31.  
  32.         r = new Resources(assets, metrics, getConfiguration(), compInfo); 
  33.         ...... 
  34.  
  35.         synchronized (mPackages) { 
  36.             WeakReference<Resources> wr = mActiveResources.get(key); 
  37.             Resources existing = wr != null ? wr.get() : null
  38.             if (existing != null && existing.getAssets().isUpToDate()) { 
  39.                 // Someone else already created the resources while we were 
  40.                 // unlocked; go ahead and use theirs. 
  41.                 r.getAssets().close(); 
  42.                 return existing; 
  43.             } 
  44.  
  45.             // XXX need to remove entries when weak references go away 
  46.             mActiveResources.put(key, new WeakReference<Resources>(r)); 
  47.             return r; 
  48.         } 
  49.     } 
  50.  
  51.     ...... 
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

[java] view plain copy print ?
  1. public final class AssetManager { 
  2.     ...... 
  3.  
  4.     private static AssetManager sSystem = null
  5.     ...... 
  6.  
  7.     public AssetManager() { 
  8.         synchronized (this) { 
  9.             ...... 
  10.             init(); 
  11.             ...... 
  12.             ensureSystemAssets(); 
  13.         } 
  14.     } 
  15.  
  16.     private static void ensureSystemAssets() { 
  17.         synchronized (sSync) { 
  18.             if (sSystem == null) { 
  19.                 AssetManager system = new AssetManager(true); 
  20.                 system.makeStringBlocks(false); 
  21.                 sSystem = system; 
  22.             } 
  23.         } 
  24.     } 
  25.  
  26.     ...... 
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

[java] view plain copy print ?
  1. public final class AssetManager { 
  2.     ...... 
  3.   
  4.     private int mObject; 
  5.     ...... 
  6.  
  7.     private native final void init(); 
  8.  
  9.     ...... 
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來實現的:

[cpp] view plain copy print ?
  1. static void android_content_AssetManager_init(JNIEnv* env, jobject clazz) 
  2.     AssetManager* am = new AssetManager(); 
  3.     ..... 
  4.  
  5.     am->addDefaultAssets(); 
  6.  
  7.     ...... 
  8.     env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am); 
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

[cpp] view plain copy print ?
  1. static const char* kSystemAssets = "framework/framework-res.apk"
  2. ...... 
  3.  
  4. bool AssetManager::addDefaultAssets() 
  5.     const char* root = getenv("ANDROID_ROOT"); 
  6.     ...... 
  7.  
  8.     String8 path(root); 
  9.     path.appendPath(kSystemAssets); 
  10.  
  11.     return addAssetPath(path, NULL); 
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

[cpp] view plain copy print ?
  1. static const char* kAppZipName = NULL; //"classes.jar"; 
  2. ...... 
  3.  
  4. bool AssetManager::addAssetPath(const String8& path, void** cookie) 
  5.     AutoMutex _l(mLock); 
  6.  
  7.     asset_path ap; 
  8.  
  9.     String8 realPath(path); 
  10.     if (kAppZipName) { 
  11.         realPath.appendPath(kAppZipName); 
  12.     } 
  13.     ap.type = ::getFileType(realPath.string()); 
  14.     if (ap.type == kFileTypeRegular) { 
  15.         ap.path = realPath; 
  16.     } else
  17.         ap.path = path; 
  18.         ap.type = ::getFileType(path.string()); 
  19.         if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) { 
  20.             ...... 
  21.             return false
  22.         } 
  23.     } 
  24.  
  25.     // Skip if we have it already. 
  26.     for (size_t i=0; i<mAssetPaths.size(); i++) { 
  27.         if (mAssetPaths[i].path == ap.path) { 
  28.             if (cookie) { 
  29.                 *cookie = (void*)(i+1); 
  30.             } 
  31.             return true
  32.         } 
  33.     } 
  34.  
  35.     ...... 
  36.  
  37.     mAssetPaths.add(ap); 
  38.  
  39.     // new paths are always added at the end 
  40.     if (cookie) { 
  41.         *cookie = (void*)mAssetPaths.size(); 
  42.     } 
  43.  
  44.     // add overlay packages for /system/framework; apps are handled by the 
  45.     // (Java) package manager 
  46.     if (strncmp(path.string(), "/system/framework/", 18) == 0) { 
  47.         // When there is an environment variable for /vendor, this 
  48.         // should be changed to something similar to how ANDROID_ROOT 
  49.         // and ANDROID_DATA are used in this file. 
  50.         String8 overlayPath("/vendor/overlay/framework/"); 
  51.         overlayPath.append(path.getPathLeaf()); 
  52.         if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) { 
  53.             asset_path oap; 
  54.             oap.path = overlayPath; 
  55.             oap.type = ::getFileType(overlayPath.string()); 
  56.             bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay 
  57.             if (addOverlay) { 
  58.                 oap.idmap = idmapPathForPackagePath(overlayPath); 
  59.  
  60.                 if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) { 
  61.                     addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap); 
  62.                 } 
  63.             } 
  64.             if (addOverlay) { 
  65.                 mAssetPaths.add(oap); 
  66.             }  
  67.             ...... 
  68.         } 
  69.     } 
  70.  
  71.     return true
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

[java] view plain copy print ?
  1. public final class AssetManager { 
  2.     ...... 
  3.  
  4.     /**
  5.      * Add an additional set of assets to the asset manager.  This can be
  6.      * either a directory or ZIP file.  Not for use by applications.  Returns
  7.      * the cookie of the added asset, or 0 on failure.
  8.      * {@hide}
  9.      */ 
  10.     public native final int addAssetPath(String path); 
  11.  
  12.     ...... 
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來實現的,如下所示:

[cpp] view plain copy print ?
  1. static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, 
  2.                                                        jstring path) 
  3.     ...... 
  4.  
  5.     AssetManager* am = assetManagerForJavaObject(env, clazz); 
  6.     ...... 
  7.  
  8.     const char* path8 = env->GetStringUTFChars(path, NULL); 
  9.  
  10.     void* cookie; 
  11.     bool res = am->addAssetPath(String8(path8), &cookie); 
  12.  
  13.     env->ReleaseStringUTFChars(path, path8); 
  14.  
  15.     return (res) ? (jint)cookie : 0; 
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

[java] view plain copy print ?