天天看點

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

     在前面一篇文章中,我們分析了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類就可以通路應用程式的資源了。

圖2 應用程式資料總管的建立和初始化過程

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

       Step 1. ContextImpl.init

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<code>class</code> <code>ContextImpl </code><code>extends</code> <code>Context {</code>

<code>    </code><code>......</code>

<code>    </code><code>/*package*/</code> <code>LoadedApk mPackageInfo;</code>

<code>    </code><code>private</code> <code>Resources mResources;</code>

<code>    </code><code>final</code> <code>void</code> <code>init(LoadedApk packageInfo,</code>

<code>            </code><code>IBinder activityToken, ActivityThread mainThread) {</code>

<code>        </code><code>init(packageInfo, activityToken, mainThread, </code><code>null</code><code>);</code>

<code>    </code><code>}</code>

<code>                </code><code>IBinder activityToken, ActivityThread mainThread,</code>

<code>                </code><code>Resources container) {</code>

<code>        </code><code>mPackageInfo = packageInfo;</code>

<code>        </code><code>mResources = mPackageInfo.getResources(mainThread);</code>

<code>        </code><code>......</code>

<code>}</code>

       這個函數定義在檔案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

<code>final</code> <code>class</code> <code>LoadedApk {</code>

<code>    </code><code>private</code> <code>final</code> <code>String mResDir;</code>

<code>    </code><code>Resources mResources;</code>

<code>    </code><code>public</code> <code>Resources getResources(ActivityThread mainThread) {</code>

<code>        </code><code>if</code> <code>(mResources == </code><code>null</code><code>) {</code>

<code>            </code><code>mResources = mainThread.getTopLevelResources(mResDir, </code><code>this</code><code>);</code>

<code>        </code><code>}</code>

<code>        </code><code>return</code> <code>mResources;</code>

<code>                                                                             </code> 

       這個函數定義在檔案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

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

<code>public</code> <code>final</code> <code>class</code> <code>ActivityThread {</code>

<code>    </code><code>final</code> <code>HashMap&lt;ResourcesKey, WeakReference&lt;Resources&gt; &gt; mActiveResources</code>

<code>            </code><code>= </code><code>new</code> <code>HashMap&lt;ResourcesKey, WeakReference&lt;Resources&gt; &gt;();</code>

<code>    </code><code>Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {</code>

<code>        </code><code>ResourcesKey key = </code><code>new</code> <code>ResourcesKey(resDir, compInfo.applicationScale);</code>

<code>        </code><code>Resources r;</code>

<code>        </code><code>synchronized</code> <code>(mPackages) {</code>

<code>            </code><code>......</code>

<code>            </code><code>WeakReference&lt;Resources&gt; wr = mActiveResources.get(key);</code>

<code>            </code><code>r = wr != </code><code>null</code> <code>? wr.get() : </code><code>null</code><code>;</code>

<code>            </code><code>if</code> <code>(r != </code><code>null</code> <code>&amp;&amp; r.getAssets().isUpToDate()) {</code>

<code>                </code><code>......</code>

<code>                </code><code>return</code> <code>r;</code>

<code>            </code><code>}</code>

<code>        </code><code>AssetManager assets = </code><code>new</code> <code>AssetManager();</code>

<code>        </code><code>if</code> <code>(assets.addAssetPath(resDir) == </code><code>0</code><code>) {</code>

<code>            </code><code>return</code> <code>null</code><code>;</code>

<code>        </code><code>r = </code><code>new</code> <code>Resources(assets, metrics, getConfiguration(), compInfo);</code>

<code>            </code><code>Resources existing = wr != </code><code>null</code> <code>? wr.get() : </code><code>null</code><code>;</code>

<code>            </code><code>if</code> <code>(existing != </code><code>null</code> <code>&amp;&amp; existing.getAssets().isUpToDate()) {</code>

<code>                </code><code>// Someone else already created the resources while we were</code>

<code>                </code><code>// unlocked; go ahead and use theirs.</code>

<code>                </code><code>r.getAssets().close();</code>

<code>                </code><code>return</code> <code>existing;</code>

<code>            </code><code>// XXX need to remove entries when weak references go away</code>

<code>            </code><code>mActiveResources.put(key, </code><code>new</code> <code>WeakReference&lt;Resources&gt;(r));</code>

<code>            </code><code>return</code> <code>r;</code>

       這個函數定義在檔案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

<code>public</code> <code>final</code> <code>class</code> <code>AssetManager {</code>

<code>    </code><code>private</code> <code>static</code> <code>AssetManager sSystem = </code><code>null</code><code>;</code>

<code>    </code><code>public</code> <code>AssetManager() {</code>

<code>        </code><code>synchronized</code> <code>(</code><code>this</code><code>) {</code>

<code>            </code><code>init();</code>

<code>            </code><code>ensureSystemAssets();</code>

<code>    </code><code>private</code> <code>static</code> <code>void</code> <code>ensureSystemAssets() {</code>

<code>        </code><code>synchronized</code> <code>(sSync) {</code>

<code>            </code><code>if</code> <code>(sSystem == </code><code>null</code><code>) {</code>

<code>                </code><code>AssetManager system = </code><code>new</code> <code>AssetManager(</code><code>true</code><code>);</code>

<code>                </code><code>system.makeStringBlocks(</code><code>false</code><code>);</code>

<code>                </code><code>sSystem = system;</code>

       這個函數定義在檔案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

<code>                                                                 </code> 

<code>    </code><code>private</code> <code>int</code> <code>mObject;</code>

<code>    </code><code>private</code> <code>native</code> <code>final</code> <code>void</code> <code>init();</code>

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

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

<code>static</code> <code>void</code> <code>android_content_AssetManager_init(JNIEnv* env, jobject clazz)</code>

<code>{</code>

<code>    </code><code>AssetManager* am = </code><code>new</code> <code>AssetManager();</code>

<code>    </code><code>.....</code>

<code>    </code><code>am-&gt;addDefaultAssets();</code>

<code>    </code><code>env-&gt;SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);</code>

        這個函數定義在檔案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

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

<code>......</code>

<code>bool AssetManager::addDefaultAssets()</code>

<code>    </code><code>const</code> <code>char</code><code>* root = getenv(</code><code>"ANDROID_ROOT"</code><code>);</code>

<code>    </code><code>String8 path(root);</code>

<code>    </code><code>path.appendPath(kSystemAssets);</code>

<code>    </code><code>return</code> <code>addAssetPath(path, NULL);</code>

      這個函數定義在檔案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

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

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

<code>bool AssetManager::addAssetPath(</code><code>const</code> <code>String8&amp; path, </code><code>void</code><code>** cookie)</code>

<code>    </code><code>AutoMutex _l(mLock);</code>

<code>    </code><code>asset_path ap;</code>

<code>    </code><code>String8 realPath(path);</code>

<code>    </code><code>if</code> <code>(kAppZipName) {</code>

<code>        </code><code>realPath.appendPath(kAppZipName);</code>

<code>    </code><code>ap.type = ::getFileType(realPath.string());</code>

<code>    </code><code>if</code> <code>(ap.type == kFileTypeRegular) {</code>

<code>        </code><code>ap.path = realPath;</code>

<code>    </code><code>} </code><code>else</code> <code>{</code>

<code>        </code><code>ap.path = path;</code>

<code>        </code><code>ap.type = ::getFileType(path.string());</code>

<code>        </code><code>if</code> <code>(ap.type != kFileTypeDirectory &amp;&amp; ap.type != kFileTypeRegular) {</code>

<code>            </code><code>return</code> <code>false</code><code>;</code>

<code>    </code><code>// Skip if we have it already.</code>

<code>    </code><code>for</code> <code>(size_t i=</code><code>0</code><code>; i&lt;mAssetPaths.size(); i++) {</code>

<code>        </code><code>if</code> <code>(mAssetPaths[i].path == ap.path) {</code>

<code>            </code><code>if</code> <code>(cookie) {</code>

<code>                </code><code>*cookie = (</code><code>void</code><code>*)(i+</code><code>1</code><code>);</code>

<code>            </code><code>return</code> <code>true</code><code>;</code>

<code>    </code><code>mAssetPaths.add(ap);</code>

<code>    </code><code>// new paths are always added at the end</code>

<code>    </code><code>if</code> <code>(cookie) {</code>

<code>        </code><code>*cookie = (</code><code>void</code><code>*)mAssetPaths.size();</code>

<code>    </code><code>// add overlay packages for /system/framework; apps are handled by the</code>

<code>    </code><code>// (Java) package manager</code>

<code>    </code><code>if</code> <code>(strncmp(path.string(), </code><code>"/system/framework/"</code><code>, </code><code>18</code><code>) == </code><code>0</code><code>) {</code>

<code>        </code><code>// When there is an environment variable for /vendor, this</code>

<code>        </code><code>// should be changed to something similar to how ANDROID_ROOT</code>

<code>        </code><code>// and ANDROID_DATA are used in this file.</code>

<code>        </code><code>String8 overlayPath(</code><code>"/vendor/overlay/framework/"</code><code>);</code>

<code>        </code><code>overlayPath.append(path.getPathLeaf());</code>

<code>        </code><code>if</code> <code>(TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == </code><code>0</code><code>) {</code>

<code>            </code><code>asset_path oap;</code>

<code>            </code><code>oap.path = overlayPath;</code>

<code>            </code><code>oap.type = ::getFileType(overlayPath.string());</code>

<code>            </code><code>bool addOverlay = (oap.type == kFileTypeRegular); </code><code>// only .apks supported as overlay</code>

<code>            </code><code>if</code> <code>(addOverlay) {</code>

<code>                </code><code>oap.idmap = idmapPathForPackagePath(overlayPath);</code>

<code>                </code><code>if</code> <code>(isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {</code>

<code>                    </code><code>addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);</code>

<code>                </code><code>}</code>

<code>                </code><code>mAssetPaths.add(oap);</code>

<code>    </code><code>return</code> <code>true</code><code>;</code>

       這個函數定義在檔案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檔案就會以名稱為@vendor@overlay@[email protected]@idmap的形式儲存在目錄/data/resource-cache/下。

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

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

       Step 8. AssetManager.addAssetPath

<code>    </code><code>/**</code>

<code>     </code><code>* Add an additional set of assets to the asset manager.  This can be</code>

<code>     </code><code>* either a directory or ZIP file.  Not for use by applications.  Returns</code>

<code>     </code><code>* the cookie of the added asset, or 0 on failure.</code>

<code>     </code><code>* {@hide}</code>

<code>     </code><code>*/</code>

<code>    </code><code>public</code> <code>native</code> <code>final</code> <code>int</code> <code>addAssetPath(String path);</code>

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

       這個函數定義在檔案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

<code>public</code> <code>class</code> <code>Resources {</code>

<code>    </code><code>/*package*/</code> <code>final</code> <code>AssetManager mAssets;</code>

<code>    </code><code>public</code> <code>Resources(AssetManager assets, DisplayMetrics metrics,</code>

<code>            </code><code>Configuration config, CompatibilityInfo compInfo) {</code>

<code>        </code><code>mAssets = assets;</code>

<code>        </code><code>updateConfiguration(config, metrics);</code>

<code>        </code><code>assets.ensureStringBlocks();</code>

<code>                                         </code> 

       這個函數定義在檔案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

<code>    </code><code>private</code> <code>final</code> <code>Configuration mConfiguration = </code><code>new</code> <code>Configuration();</code>

<code>    </code><code>public</code> <code>void</code> <code>updateConfiguration(Configuration config,</code>

<code>            </code><code>DisplayMetrics metrics) {</code>

<code>        </code><code>synchronized</code> <code>(mTmpValue) {</code>

<code>            </code><code>int</code> <code>configChanges = </code><code>0xfffffff</code><code>;</code>

<code>            </code><code>if</code> <code>(config != </code><code>null</code><code>) {</code>

<code>                </code><code>configChanges = mConfiguration.updateFrom(config);</code>

<code>            </code><code>if</code> <code>(mConfiguration.locale == </code><code>null</code><code>) {</code>

<code>                </code><code>mConfiguration.locale = Locale.getDefault();</code>

<code>            </code><code>if</code> <code>(metrics != </code><code>null</code><code>) {</code>

<code>                </code><code>mMetrics.setTo(metrics);</code>

<code>                </code><code>mMetrics.updateMetrics(mCompatibilityInfo,</code>

<code>                        </code><code>mConfiguration.orientation, mConfiguration.screenLayout);</code>

<code>            </code><code>mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;</code>

<code>            </code><code>String locale = </code><code>null</code><code>;</code>

<code>            </code><code>if</code> <code>(mConfiguration.locale != </code><code>null</code><code>) {</code>

<code>                </code><code>locale = mConfiguration.locale.getLanguage();</code>

<code>                </code><code>if</code> <code>(mConfiguration.locale.getCountry() != </code><code>null</code><code>) {</code>

<code>                    </code><code>locale += </code><code>"-"</code> <code>+ mConfiguration.locale.getCountry();</code>

<code>            </code><code>int</code> <code>width, height;</code>

<code>            </code><code>if</code> <code>(mMetrics.widthPixels &gt;= mMetrics.heightPixels) {</code>

<code>                </code><code>width = mMetrics.widthPixels;</code>

<code>                </code><code>height = mMetrics.heightPixels;</code>

<code>            </code><code>} </code><code>else</code> <code>{</code>

<code>                </code><code>//noinspection SuspiciousNameCombination</code>

<code>                </code><code>width = mMetrics.heightPixels;</code>

<code>                </code><code>height = mMetrics.widthPixels;</code>

<code>            </code><code>int</code> <code>keyboardHidden = mConfiguration.keyboardHidden;</code>

<code>            </code><code>if</code> <code>(keyboardHidden == Configuration.KEYBOARDHIDDEN_NO</code>

<code>                    </code><code>&amp;&amp; mConfiguration.hardKeyboardHidden</code>

<code>                            </code><code>== Configuration.HARDKEYBOARDHIDDEN_YES) {</code>

<code>                </code><code>keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;</code>

<code>            </code><code>mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,</code>

<code>                    </code><code>locale, mConfiguration.orientation,</code>

<code>                    </code><code>mConfiguration.touchscreen,</code>

<code>                    </code><code>(</code><code>int</code><code>)(mMetrics.density*</code><code>160</code><code>), mConfiguration.keyboard,</code>

<code>                    </code><code>keyboardHidden, mConfiguration.navigation, width, height,</code>

<code>                    </code><code>mConfiguration.screenLayout, mConfiguration.uiMode, sSdkVersion);</code>

<code>                                           </code> 

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

       接下來,我們就繼續分析AssetManager類的成員函數setConfiguration的實作,以便可以了解裝置配置資訊的設定過程。

       Step 11. AssetManager.setConfiguration

<code>     </code><code>* Change the configuation used when retrieving resources.  Not for use by</code>

<code>     </code><code>* applications.</code>

<code>    </code><code>public</code> <code>native</code> <code>final</code> <code>void</code> <code>setConfiguration(</code><code>int</code> <code>mcc, </code><code>int</code> <code>mnc, String locale,</code>

<code>            </code><code>int</code> <code>orientation, </code><code>int</code> <code>touchscreen, </code><code>int</code> <code>density, </code><code>int</code> <code>keyboard,</code>

<code>            </code><code>int</code> <code>keyboardHidden, </code><code>int</code> <code>navigation, </code><code>int</code> <code>screenWidth, </code><code>int</code> <code>screenHeight,</code>

<code>            </code><code>int</code> <code>screenLayout, </code><code>int</code> <code>uiMode, </code><code>int</code> <code>majorVersion);</code>

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

<code>static</code> <code>void</code> <code>android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,</code>

<code>                                                          </code><code>jint mcc, jint mnc,</code>

<code>                                                          </code><code>jstring locale, jint orientation,</code>

<code>                                                          </code><code>jint touchscreen, jint density,</code>

<code>                                                          </code><code>jint keyboard, jint keyboardHidden,</code>

<code>                                                          </code><code>jint navigation,</code>

<code>                                                          </code><code>jint screenWidth, jint screenHeight,</code>

<code>                                                          </code><code>jint screenLayout, jint uiMode,</code>

<code>                                                          </code><code>jint sdkVersion)</code>

<code>    </code><code>AssetManager* am = assetManagerForJavaObject(env, clazz);</code>

<code>    </code><code>if</code> <code>(am == NULL) {</code>

<code>        </code><code>return</code><code>;</code>

<code>    </code><code>ResTable_config config;</code>

<code>    </code><code>memset(&amp;config, </code><code>0</code><code>, sizeof(config));</code>

<code>    </code><code>const</code> <code>char</code><code>* locale8 = locale != NULL ? env-&gt;GetStringUTFChars(locale, NULL) : NULL;</code>

<code>    </code><code>config.mcc = (uint16_t)mcc;</code>

<code>    </code><code>config.mnc = (uint16_t)mnc;</code>

<code>    </code><code>config.orientation = (uint8_t)orientation;</code>

<code>    </code><code>config.touchscreen = (uint8_t)touchscreen;</code>

<code>    </code><code>config.density = (uint16_t)density;</code>

<code>    </code><code>config.keyboard = (uint8_t)keyboard;</code>

<code>    </code><code>config.inputFlags = (uint8_t)keyboardHidden;</code>

<code>    </code><code>config.navigation = (uint8_t)navigation;</code>

<code>    </code><code>config.screenWidth = (uint16_t)screenWidth;</code>

<code>    </code><code>config.screenHeight = (uint16_t)screenHeight;</code>

<code>    </code><code>config.screenLayout = (uint8_t)screenLayout;</code>

<code>    </code><code>config.uiMode = (uint8_t)uiMode;</code>

<code>    </code><code>config.sdkVersion = (uint16_t)sdkVersion;</code>

<code>    </code><code>config.minorVersion = </code><code>0</code><code>;</code>

<code>    </code><code>am-&gt;setConfiguration(config, locale8);</code>

<code>    </code><code>if</code> <code>(locale != NULL) env-&gt;ReleaseStringUTFChars(locale, locale8);</code>

       參數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

<code>void</code> <code>AssetManager::setConfiguration(</code><code>const</code> <code>ResTable_config&amp; config, </code><code>const</code> <code>char</code><code>* locale)</code>

<code>    </code><code>*mConfig = config;</code>

<code>    </code><code>if</code> <code>(locale) {</code>

<code>        </code><code>setLocaleLocked(locale);</code>

<code>    </code><code>} </code><code>else</code> <code>if</code> <code>(config.language[</code><code>0</code><code>] != </code><code>0</code><code>) {</code>

<code>        </code><code>char</code> <code>spec[</code><code>9</code><code>];</code>

<code>        </code><code>spec[</code><code>0</code><code>] = config.language[</code><code>0</code><code>];</code>

<code>        </code><code>spec[</code><code>1</code><code>] = config.language[</code><code>1</code><code>];</code>

<code>        </code><code>if</code> <code>(config.country[</code><code>0</code><code>] != </code><code>0</code><code>) {</code>

<code>            </code><code>spec[</code><code>2</code><code>] = </code><code>'_'</code><code>;</code>

<code>            </code><code>spec[</code><code>3</code><code>] = config.country[</code><code>0</code><code>];</code>

<code>            </code><code>spec[</code><code>4</code><code>] = config.country[</code><code>1</code><code>];</code>

<code>            </code><code>spec[</code><code>5</code><code>] = </code><code>0</code><code>;</code>

<code>        </code><code>} </code><code>else</code> <code>{</code>

<code>            </code><code>spec[</code><code>3</code><code>] = </code><code>0</code><code>;</code>

<code>        </code><code>setLocaleLocked(spec);</code>

<code>        </code><code>updateResourceParamsLocked();</code>

       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的實作如下所示:

<code>void</code> <code>AssetManager::updateResourceParamsLocked() </code><code>const</code>

<code>    </code><code>ResTable* res = mResources;</code>

<code>    </code><code>if</code> <code>(!res) {</code>

<code>    </code><code>size_t llen = mLocale ? strlen(mLocale) : </code><code>0</code><code>;</code>

<code>    </code><code>mConfig-&gt;language[</code><code>0</code><code>] = </code><code>0</code><code>;</code>

<code>    </code><code>mConfig-&gt;language[</code><code>1</code><code>] = </code><code>0</code><code>;</code>

<code>    </code><code>mConfig-&gt;country[</code><code>0</code><code>] = </code><code>0</code><code>;</code>

<code>    </code><code>mConfig-&gt;country[</code><code>1</code><code>] = </code><code>0</code><code>;</code>

<code>    </code><code>if</code> <code>(llen &gt;= </code><code>2</code><code>) {</code>

<code>        </code><code>mConfig-&gt;language[</code><code>0</code><code>] = mLocale[</code><code>0</code><code>];</code>

<code>        </code><code>mConfig-&gt;language[</code><code>1</code><code>] = mLocale[</code><code>1</code><code>];</code>

<code>    </code><code>if</code> <code>(llen &gt;= </code><code>5</code><code>) {</code>

<code>        </code><code>mConfig-&gt;country[</code><code>0</code><code>] = mLocale[</code><code>3</code><code>];</code>

<code>        </code><code>mConfig-&gt;country[</code><code>1</code><code>] = mLocale[</code><code>4</code><code>];</code>

<code>    </code><code>mConfig-&gt;size = sizeof(*mConfig);</code>

<code>    </code><code>res-&gt;setParameters(mConfig);</code>

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

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

       Step 13. AssetManager.ensureStringBlocks

<code>    </code><code>private</code> <code>StringBlock mStringBlocks[] = </code><code>null</code><code>;</code>

<code>    </code><code>/*package*/</code> <code>final</code> <code>void</code> <code>ensureStringBlocks() {</code>

<code>        </code><code>if</code> <code>(mStringBlocks == </code><code>null</code><code>) {</code>

<code>            </code><code>synchronized</code> <code>(</code><code>this</code><code>) {</code>

<code>                </code><code>if</code> <code>(mStringBlocks == </code><code>null</code><code>) {</code>

<code>                    </code><code>makeStringBlocks(</code><code>true</code><code>);</code>

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

      Step 14. AssetManager.makeStringBlocks

<code>    </code><code>private</code> <code>final</code> <code>void</code> <code>makeStringBlocks(</code><code>boolean</code> <code>copyFromSystem) {</code>

<code>        </code><code>final</code> <code>int</code> <code>sysNum = copyFromSystem ? sSystem.mStringBlocks.length : </code><code>0</code><code>;</code>

<code>        </code><code>final</code> <code>int</code> <code>num = getStringBlockCount();</code>

<code>        </code><code>mStringBlocks = </code><code>new</code> <code>StringBlock[num];</code>

<code>        </code><code>for</code> <code>(</code><code>int</code> <code>i=</code><code>0</code><code>; i&lt;num; i++) {</code>

<code>            </code><code>if</code> <code>(i &lt; sysNum) {</code>

<code>                </code><code>mStringBlocks[i] = sSystem.mStringBlocks[i];</code>

<code>                </code><code>mStringBlocks[i] = </code><code>new</code> <code>StringBlock(getNativeStringBlock(i), </code><code>true</code><code>);</code>

<code>    </code><code>private</code> <code>native</code> <code>final</code> <code>int</code> <code>getStringBlockCount();</code>

<code>    </code><code>private</code> <code>native</code> <code>final</code> <code>int</code> <code>getNativeStringBlock(</code><code>int</code> <code>block);</code>

       參數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所描述的一個數組中。

       至此,我們就分析完成Android應用程式資料總管的建立的初始化過程了,主要就是建立和初始化用來通路應用程式資源的AssetManager對象和Resources對象,其中,初始化操作包括設定AssetManager對象的資源檔案路徑以及裝置配置資訊等。有了這兩個初始化的AssetManager對象和Resources對象之後,在接下來的一篇文章中,我們就可以繼續分析應用程式資源的查找過程了,敬請關注!

本文轉自 Luoshengyang 51CTO部落格,原文連結:http://blog.51cto.com/shyluo/1229260,如需轉載請自行聯系原作者