導語 Android系統內部是在什么時候創建AssetManager,又是如何創建的? AssetManager與Resource什么關系? Android插件化為什么可以通過重寫addAssetPath方法訪問插件資源? 本文參考老羅的文章,并生成自己的見解,若有錯誤之處,懇請指正。 詳見:http://blog.csdn.net/luoshengyang/article/details/8791064
1、AssetManager與Resources
Android中的資源可以分為兩類:
第一類資源,不對應文件的,如string資源;
第二類資源,對應文件的,如layout資源。
-
第一類資源只需通過資源ID查找到資源名稱即可,第二類資源需根據資源名稱打開相應文件。 通過下面這副圖片簡單的分析下資源查找的過程:

如上圖所示,Resources根據資源ID查找資源,AssetManager根據資源名稱查找資源。若Resources查找資源時的資源ID對應的資源是文件,那先根據資源ID查找到資源文件名稱(在resources.arsc中查找到),再通過AssetManager打開相應文件。 看下Resources與AssetManager的成員變量:
ContextImpl提供接口getResources可以獲得指向當前APK資源的Resources對象。
Resouces類中的mAssets是AssetManager對象,用來訪問程序的非編譯文件(第二類資源);mSystem是Resources對象,用來訪問系統資源包,系統資源包存在/system/framework/framework-res.apk。
AssetManager(Java)中的sSystem是AssetManager對象,用來打開系統資源包文件;mObject是一個int地址,保存了對應C++層的AssetManager對象地址。
AssetManager(C++)中的mAssetPaths是資源目錄,mResources為資源表,mConfig保存了設備的本地配置信息。
2、Activity啟動
調用startActivity啟動指定Activity,最后會調用ApplicationThread的scheduleLaunchActivity方法。流程如下所示:
scheduleLaunchActivity方法通過H發送消息H.LAUNCH_ACTIVITY
H的消息處理中,調用ActivityThread的getPackageInfoNoCheck創建LoadedAPK對象,調用handleLaunchActivity啟動Activity。
創建的LoadedAPK對象指定資源路徑為當前APK。
handleLaunchActivity啟動Activity,先調用makeApplication創建Application對象,并未Application關聯ContextImpl(baseContext)。
再為啟動的Activity關聯ComtextImpl(baseContext)。 該過程如下圖所示,其中AMS(ActivityManagerService)負責Activity聲明周期和Activity堆棧的管理,而ApplicationThread是App進程與AMS通信的Binder。ApplicationThread通過H(是一個Handler)實現與主線程ActivityThread的通信,進而管理者Activity的聲明周期。
在上過程中,首先會為創建的Application關聯AppContext,即為Application關聯ContextImpl;再為啟動的Activity關聯BaseContext,同樣為ContextImpl。該過程都會調用ContextImpl的構造方法,實現ContextImpl的創建,并調用init方法實現Resources、AssetManager的創建,那接著看ContextImpl的init過程。
(插一句,Application繼承ContextWrapper,ContextWrapper繼承Context;Activity繼承ContextThemeWrapper,ContextThemeWrapper繼承ContextWrapper。Application與Activity的基類Context是抽象類,其具有成員變量mBaseContext,采用代理模式,真正的操作都有mBaseContext實現。而真正實現Context方法的類只有ContextImpl,因此在創建Activity或Application都需要為其關聯一個ContextImpl對象)
3、ActivityManager創建
接著上一步的ContextImpl的創建,先看下時序圖:
該過程的主要實現了:
過程還是比較多的,挑幾步好好看下。
3.1、第1步:init
該步中調用LoadeAPk的getResources(..)方法獲得Resources對象,LoadedAPK在上面Activity啟動流程中進行創建(LoadedAPK保存了當前APK信息)。
3.2、第3步:getTopLevelResources
在第2步調用LoadedAPK的getResources方法中,它會調用ActivityThread的getTopLevelResources方法。該方法的主要內容有:
如上所示,該方法主要完成以下功能:
從Map緩存中取Resources對象,有直接返回,沒有下一步。
創建AssetManager,并接著添加系統資源文件路徑到資源目錄(mAssetPaths)中。
添加訪問的APK文件(當前APK文件)路徑到資源目錄(mAssetPaths)中。
創建Resources對象,并返回。
3.2、第7步:addAssetPath
在前面的分析中可知,通過調用addAssetPath方法將資源文件路徑添加到資源目錄中,實現資源的加載。該方法的主要內容有:
由上可知,addAssetPath主要是將資源文件路徑添加到資源目錄mAssetPaths中,并且判斷添加的apk文件是否位于/system/framework/下,如果是則會在/Vendor/overlay/frame/目錄下查找是否有同名的apk文件,有的話則用該apk文件覆蓋原有apk。(該機制一般用于廠商自定義資源覆蓋系統資源)。
4、總結
由上分析可知,ContextImpl創建過程中,會調研getResources()獲得Resources對象,而getResources最后調用getTopLevelResources方法。getTopLevelResources方法首先從緩存中拿Resources對象,沒有拿到則先創建AssetManager對象,并通過AssetManager的addAssetPath實現系統資源文件、當前APK資源文件的加載,然后再創建Resources對象返回。
5、后記
在andorid插件化機制中,關于如何訪問插件資源?,F在的一般做法是通過反射拿到AssetManager的addAssetPath方法,將插件資源路徑添加到資源目錄mAssetPath中,然后在創建Resources對象。
而另外一種方案則來自于DroidPlugin機制,該插件化機制采用Hook思想,當啟動插件Activity時,先替換啟動的插件Activity為代理Activity,再在H的消息處理中替換回插件Activity。而在H的消息處理中替換回插件Activity時,首先通過插件Acitivity信息創建LoadedAPK對象(指定資源路徑為插件路徑),再調用LoadedAPK的makeApplication創建插件Application。而關于makeApplication調用上面已經講過了,它會調用ContextImpl創建過程實現插件Resources的創建以及資源加載。